Chinaunix首页 | 论坛 | 博客
  • 博客访问: 65990
  • 博文数量: 11
  • 博客积分: 186
  • 博客等级: 入伍新兵
  • 技术积分: 95
  • 用 户 组: 普通用户
  • 注册时间: 2011-04-07 17:22
文章分类
文章存档

2012年(4)

2011年(7)

我的朋友

分类: LINUX

2011-12-13 15:54:19

以下部分资料是从网上各个博客收集到的:

1、目前,发送短消息常用Text和PDU(Protocol Data Unit,协议数据单元)模式。
   1>使用Text模式收发短信代码简单,实现起来十分容易,但最大的缺点是不能收发中文短信;
   2>使用PDU模式不仅支持中文短信,也能发送英文短信。

2、PDU模式收发短信可以使用3种编码:
   1>7-bit(用于发送普通的ASCII字符,它将一串7-bit的字符(最高位为0)编码成8-bit的数据,每8个字符可   “压缩”成7个)
   2>8-bit(通常用于发送数据消息,如:图片了,铃声等等)
   3>UCS2编码(用于发送Unicode字符)

在以上这三种编码方式下,PDU串的用户信息(TP-UD)段最大容量(可以发送的短消息的最大字符数)分别是160、140和70。这里,将一个英文字母、一个汉字和一个数据字节都视为一个字符。

PDU串的用户信息长度(TP-UDL),在各种编码方式下意义有所不同。7-bit编码时,指原始短消息的字符个数,而不是编码后的字节数。8-bit编码时,就是字节数。UCS2编码时,也是字节数,

等于原始短消息的字符数的两倍。如果用户信息(TP-UD)中存在一个头(基本参数的TP-UDHI为1),在所有编码方式下,用户信息长度(TP-UDL)都等于头长度与编码后字节数之和。如果采用GSM

03.42所建议的压缩算法(TP-DCS的高3位为001),则该长度也是压缩编码后字节数或头长度与压缩编码后字节数之和。

3、那ucs2编码是什么呢?由此我们去看看计算机对不同字符的编码是怎么处理的:

<1>Unicode也是一种字符编码方法,不过它是由国际组织设计,可以容纳全世界所有语言文字的编码方案。Unicode的学名是"Universal Multiple-Octet Coded Character Set",简称为UCS

。UCS可以看作是"Unicode Character Set"的缩写。

根据维基百科全书()的记载:历史上存在两个试图独立设计Unicode的组织,即国际标准化组织(ISO)和一个软件制造商的协会(unicode.org)。ISO开发了

ISO 10646项目,Unicode协会开发了Unicode项目。

在1991年前后,双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode2.0开始,Unicode项目采用了与ISO

10646-1相同的字库和字码。

目前两个项目仍都存在,并独立地公布各自的标准。Unicode协会现在的最新版本是2005年的Unicode 4.1.0。ISO的最新标准是10646-3:2003。

<2>UCS-2、UCS-4、BMP
UCS规定了怎么用多个字节表示各种文字。怎样传输这些编码,是由UTF(UCS Transformation Format)规范规定的,常见的UTF规范包括UTF-8、UTF-7、UTF-16。

UCS有两种格式:UCS-2和UCS-4。顾名思义,UCS-2就是用两个字节编码,UCS-4就是用4个字节(实际上只用了31位,最高位必须为0)编码。下面让我们做一些简单的数学游戏:

UCS-2有2^16=65536个码位,UCS-4有2^31=2147483648个码位。

UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。每个plane根据第3个字节分为256行 (rows),每行包含256个cells。当然同一行的cells只

是最后一个字节不同,其余都相同。

group 0的plane 0被称作Basic Multilingual Plane, 即BMP。或者说UCS-4中,高两个字节为0的码位被称作BMP。

将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节,就得到了UCS-4的BMP。而目前的UCS-4规范中还没有任何字符被分配在BMP之外

<3>UTF编码

UTF-8就是以8位为单元对UCS进行编码。从UCS-2到UTF-8的编码方式如下:
UCS-2编码(16进制)     UTF-8 字节流(二进制)
0000 - 007F         0xxxxxxx
0080 - 07FF         110xxxxx 10xxxxxx
0800 - FFFF         1110xxxx 10xxxxxx 10xxxxxx

例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 110001 001001, 用这个比特流依次代

替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。

读者可以用记事本测试一下我们的编码是否正确。

UTF-16以16位为单元对UCS进行编码。对于小于0x10000的UCS码,UTF-16编码就等于UCS码对应的16位无符号整数。对于不小于0x10000的UCS码,定义了一个算法。不过由于实际使用的UCS2,

或者UCS4的BMP必然小于0x10000,所以就目前而言,可以认为UTF-16和UCS-2基本相同。但UCS-2只是一个编码方案,UTF-16却要用于实际的传输,所以就不得不考虑字节序的问题。

<4>UTF的字节序和BOM
UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。例如收到一个“奎”的Unicode编码是594E,

“乙”的Unicode编码是4E59。如果我们收到UTF-16字节流“594E”,那么这是“奎”还是“乙”?

4、了解了以上的字符编码,就更容易让我们去组织发送短信时的PDU串:

(1)用户要的内容是以UTF8传输的,我们应该把这些字符转化成unicode编码(因为短信发送的信息必须是以unicode编码)。转化编码方法有三种:
1>、通过mbstowcs()和wcstombs()这俩个函数,可以将ASCII码字符串和宽字符(Unicode)互相转化,mbstowcs函数将汉字视作两个ASCII字符,这样一个汉字就变成了两个wchar_t。原因是

mbstowcs需要我们明确的告诉他要转换的字符语言。这里需要使用setlocale函数。

要在调用以上函数前,使用setlocale()设置。如何设置呢?我们在了解一下这个函数:


char* setlocale(int category, const char* locale);

说明:

category:为locale分类,表达一种locale的领域方面,通常有下面这些预定义常量:LC_ALL、LC_COLLATE、LC_CTYPE、LC_MESSAGES、LC_MONETARY、LC_NUMERIC、LC_TIME,其中 LC_ALL 表

示所有其它locale分类的并集。


LC_COLLATE    排序信息

LC_CTYPE    字符分类信息

LC_MONETARY    货币打印信息

LC_NUMERIC    数值打印信息

LC_TIME        日期和时间打印信息
 
locale:为期望设定的locale名称字符串,在Linux/Unix环境下,通常以下面格式表示locale名称:language[_territory][.codeset][@modifier],language 为 ISO 639 中规定的语言代码

,territory 为 ISO 3166 中规定的国家/地区代码,codeset 为字符集名称。

    在Linux下,可以使用 locale -a 命令查看系统中所有已配置的 locale。用不带选项的 locale 命令查看当前 Shell 中活动的 locale。用 locale -m 命令查看locale系统支持的所有

可用的字符集编码。

    和locale相关的包叫做:locales,locale系统支持的所有可用locale在文件:/usr/share/i18n/SUPPORTED 中列出。

    在Debian下,可用 dpkg-reconfigure locales 命令重新配置 locale,也可以手工修改 /etc/locale.gen 文件,然后运行 locale-gen 命令。

    在Ubuntu下,修改 /var/lib/locales/supported.d/local 文件,配置新的 locale,然后运行 locale-gen 命令。

当 locale 为 NULL 时,函数只做取回当前 locale 操作,通过返回值传出,并不改变当前 locale。

当 locale 为 "" 时,根据环境的设置来设定 locale,检测顺序是:环境变量 LC_ALL,每个单独的locale分类LC_*,最后是 LANG 变量。为了使程序可以根据环境来改变活动 locale,一般

都在程序的初始化阶段加入下面代码:setlocale(LC_ALL, "")。

当C语言程序初始化时(刚进入到 main() 时),locale 被初始化为默认的 C locale,其采用的字符编码是所有本地 ANSI 字符集编码的公共部分,是用来书写C语言源程序的最小字符集(

所以才起locale名叫:C)。

当用 setlocale() 设置活动 locale 时,如果成功,会返回当前活动 locale 的全名称;如果失败,会返回 NULL。

通过这一组函数就能实现字符编码转换,但移植性不强,他涉及了系统支持的字符编码不容易实现我们想的编码。

2>、在LINUX上进行编码转换时,既可以利用iconv函数族编程实现,也可以利用iconv命令来实现,只不过后者是针对文件的,即将指定文件从一种编码转换为另一种编码。
iconv函数族的头文件是iconv.h,使用前需包含之。
#include
iconv函数族有三个函数,原型如下:
(1) iconv_t iconv_open(const char *tocode, const char *fromcode);
此函数说明将要进行哪两种编码的转换,tocode是目标编码,fromcode是原编码,该函数返回一个转换句柄,供以下两个函数使用。
(2) size_t iconv(iconv_t cd,char **inbuf,size_t *inbytesleft,char **outbuf,size_t *outbytesleft);
此函数从inbuf中读取字符,转换后输出到outbuf中,inbytesleft用以记录还未转换的字符数,outbytesleft用以记录输出缓冲的剩余空间。
(3) int iconv_close(iconv_t cd); 此函数用于关闭转换句柄,释放资源。

3>、通过自己实现编码转化,这样对平台没有依赖性,只是对编码之前的传输过来的字符做了限定。
我现在实现的是编码之前过来的字符必须是UTF8编码,这样才能通过我自己实现的程序转化成unicode编码。

(2)上面的一切准备只是为我们组织PDU串:
1>初始化:ATZ\r
2>设置文本模式:AT+CMGF=0\r(0:pdu模式,1:text模式)
3>设置PDU头:
typedef struct {
 23     char SCA[16];       // 短消息服务中心号码(SMSC地址)
 24     char TPA[16];       // 目标号码或回复号码(TP-DA或TP-RA)
 25     char TP_PID;        // 用户信息协议标识(TP-PID)
 26     char TP_DCS;        // 用户信息编码方式(TP-DCS)
 27     char TP_SCTS[16];   // 服务时间戳字符串(TP_SCTS), 接收时用到
 28     char TP_UD[16];     // 原始用户信息
    char index;        //短消息序号,在读取时用到
}SM_PARAM;

417     // SMSC地址信息段
418     memset(buf, 0, sizeof(buf));
419     nLength = strlen(pSrc->SCA);    // SMSC地址字符串的长度   
420     buf[0] = (char)((nLength & 1) == 0 ? nLength : nLength + 1) / 2 + 1;    // SMSC地址信息长度
421     buf[1] = 0x91;                // 固定: 用国际格式号码
422     nDstLength = gsmBytes2String(buf, pDst, 2);        // 转换2个字节到目标PDU串
423     nDstLength += gsmInvertNumbers(pSrc->SCA, &pDst[nDstLength], nLength);    // 转换SMSC到目标PDU串
424
425     // TPDU段基本参数、目标地址等
426     memset(buf, 0, sizeof(buf));
427     nLength = strlen(pSrc->TPA);    // TP-DA地址字符串的长度
428     buf[0] = 0x11;            // 是发送短信(TP-MTI=01),TP-VP用相对格式(TP-VPF=10)
429     buf[1] = 0;               // TP-MR=0
430     buf[2] = (char)nLength;   // 目标地址数字个数(TP-DA地址字符串真实长度)
431     buf[3] = 0x91;            // 固定: 用国际格式号码
432     nDstLength += gsmBytes2String(buf, &pDst[nDstLength], 4); // 转换4个字节到目标PDU串
433     nDstLength += gsmInvertNumbers(pSrc->TPA, &pDst[nDstLength], nLength); // 转换TP-DA到目标PDU串
434
435     // TPDU段协议标识、编码方式、用户信息等
436     //nLength = strlen(pSrc->TP_UD);    // 用户信息字符串的长度
437     memset(buf, 0, sizeof(buf));
438     buf[0] = pSrc->TP_PID;        // 协议标识(TP-PID)
439     buf[1] = pSrc->TP_DCS;        // 用户信息编码方式(TP-DCS)
440     buf[2] = 1;                   // 有效期(TP-VP)为5分钟
    buf[3] = str2hex(user_pdu, pSrc->TP_UD); //用户信息的长度
465     nDstLength += gsmBytes2String(buf, &pDst[nDstLength], 4);        // 转换该段数据到目标PDU串

    strcat(pdu, "\x01a");  //以CTRL-A结束
     gsmString2Bytes(pdu, &nSmscLength, 2);    // 取PDU串中的SMSC信息长度
4>发送信息长度:AT+CMGS=长度,这步重要长度不对是发不出去的。
5>发送用户信息

如对PDU不怎么了解,网上有很多关于PDU资料,加上我这些应该很容易就可以实现发短信了。
阅读(12097) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~