先来看两张图:
1. 发送方PDU格式SMS-SUBMIT-PDU
*1Message type (1 octet) PDUType
* 2. A message ref (1 octet) MR
* 3. The length of the SMS dest number (1 octet)
* 4. The address format (1 octet) DA
* 5. The destination number (length/2 octets)
* 6. Protocol identifier (1 octet) PID
* 7. Data coding scheme (1 octet) DCS
* 8. Validity Period (relative: 1 octet) VP
* 9. The actual user data length (header + SMS message body) (1 octet) UDL
* 10. The actual user data (i.e. the SMS message body) UD
2. 接受方PDU格式 SMS-DELIVER-PDU
各个字段含义:
1. SCA:Service Center Address,服务中心地址
包含三个部分:
A、Len:短消息中心地址长度(不包含该位)。如果Len被设置为00&h,并不提供后面的部分,那么终端设备将读取SIM中设置的SCA填充到SMS-PUD中,通过“AT+CSCA=xxxxx”指令可以设置SIM卡中存储的SCA值。
B、Type:短消息中心地址的类型,是国际号码还是国内号码(81&h表示国内,91&h表示国际的)。
91&h是TON/NPI遵守International/E.164标准,指在号码前需加‘+’号;此外还有其它数值,但91&h最常用。
C、Add:短消息中心地址。
2. PDUType
PDUType是SMS-SUBMIT、SMS-DELIVER的第一个八位位组,在两个PDU中组成如下:
A、 RP 应答路径(Reply Paht),1表示设置,0表示未设置
B、 UDHI 用户数据头标识(User Data Header Indicator),0表示用户数据(UD)部分不包含头信息,1表示用户数据(UD)开始部分包含用户头信息
C、 SRR 请求状态报告(Status Report Request),1表示需要状态报告,0表示不需要
D、 SRI 状态报告指示(Status Report Indication),此值仅被短消息服务中心(SMSC)设置,1表示状态报告将返回给短消息实体(SME),0表示不返回状态报告
E、 VPF 有效期格式(Validity Period Format),00 –VP 段没有提供(长度为0 ),01 –保留,10 –VP 段以整型形式提供(相对的),11 –VP 段以8位组的一半(semi-octet)形式提供(绝对的)
F、 RD 拒绝复本(Reject Duplicate),0 –通知短消息服务中心(SMSC)接受一个SMS-SUBMIT,即使该消息是先前已提交过的,并还存在于
SMSC中未发送出去。 1 –通知SMSC拒绝一个重复的SMS
G、 MMS 有更多的信息需要发送(More Messages to Send),此值仅被SMSC设置,0表示在 SMSC 中有更多的信息等待 MS,1表示在SMSC 中没有更多的信息
H、 MTI 信息类型指示(Message Type Indicator),
00 – SMS-DELIVER(SMSC -> MS)
00 – SMS-DELIVER REPORT(MS -> SMSC),当手机接收到SMS-DELIVER 时自动产生
01 – SMS-SUBMIT(MS -> SMSC)
01 – SMS-SUBMIT REPORT(SMSC -> MS)
10 – SMS-STATUS REPORT (SMSC -> MS)
10 – SMS-COMMAND(MS -> SMSC)
11 – 保留
目前,发送短消息常用Text和PDU(Protocol Data Unit,协议数据单元)模式。使用Text模式收发短信代码简单,实现起来十分容易,但最大的缺点是不能收发中文短信;而PDU模式不仅支持中文短信,也能发送英文短信。PDU模式收发短信可以使用3种编码:7-bit、8-bit和UCS2编码。7-bit编码用于发送普通的ASCII字符,8-bit编码通常用于发送数据消息,UCS2编码用于发送Unicode字符。一般的PDU编码由A B C D E F G H I J K L M十三项组成。
A:短信息中心地址长度,2位十六进制数(1字节)。
B:短信息中心号码类型,2位十六进制数。
C:短信息中心号码,B+C的长度将由A中的数据决定。
D:pduType,2位十六进制数。
E:Message Reference,2位十六进制数。
F:被叫号码长度,2位十六进制数。
G:被叫号码类型,2位十六进制数,取值同B。
H:被叫号码,长度由F中的数据决定。
I:协议标识,2位十六进制数。
J:数据编码方案,2位十六进制数。
K:有效期,2位十六进制数。
L:用户数据长度,2位十六进制数。
M:用户数据,其长度由L中的数据决定。J中设定采用UCS2编码,这里是中英文的Unicode字符。
PDU编码协议简单说明
例1 发送:SMSC号码是+8613800250500,对方号码是13693092030,消息内容是“Hello!”。从手机发出的PDU串可以是
08 91 68 31 08 20 05 05 F0 11 00 0D 91 68 31 96 03 29 30 F0 00 00 00 06 C8 32 9B FD 0E 01
对照规范,具体分析:
分段 含义 说明
08 SMSC地址信息的长度 共8个八位字节(包括91)
91 SMSC地址格式(TON/NPI) 用国际格式号码(在前面加‘+’)
68 31 08 20 05 05 F0 SMSC地址 8613800250500,补‘F’凑成偶数个
11 基本参数(TP-MTI/VFP) 发送,TP-VP用相对格式
00 消息基准值(TP-MR) 0
0D 目标地址数字个数 共13个十进制数(不包括91和‘F’)
91 目标地址格式(TON/NPI) 用国际格式号码(在前面加‘+’)
68 31 96 03 29 30 F0 目标地址(TP-DA) 8613693092030,补‘F’凑成偶数个
00 协议标识(TP-PID) 是普通GSM类型,点到点方式
00 用户信息编码方式(TP-DCS) 7-bit编码
00 有效期(TP-VP) 5分钟
06 用户信息长度(TP-UDL) 实际长度6个字节
C8 32 9B FD 0E 01 用户信息(TP-UD) “Hello!”
例2 接收:SMSC号码是+8613800250500,对方号码是13693092030,消息内容是“你好!”。手机接收到的PDU串可以是
08 91 68 31 08 20 05 05 F0 84 0D 91 68 31 96 03 29 30 F0 00 08 30 30 21 80 63 54 80 06 4F 60 59 7D 00 21
对照规范,具体分析:
分段 含义 说明
08 地址信息的长度 个八位字节(包括91)
91 SMSC地址格式(TON/NPI) 用国际格式号码(在前面加‘+’)
68 31 08 20 05 05 F0 SMSC地址 8613800250500,补‘F’凑成偶数个
84 基本参数(TP-MTI/MMS/RP) 接收,无更多消息,有回复地址
0D 回复地址数字个数 共13个十进制数(不包括91和‘F’)
91 回复地址格式(TON/NPI) 用国际格式号码(在前面加‘+’)
68 31 96 03 29 30 F0 回复地址(TP-RA) 8613693092030,补‘F’凑成偶数个
00 协议标识(TP-PID) 是普通GSM类型,点到点方式
08 用户信息编码方式(TP-DCS) UCS2编码
30 30 21 80 63 54 80 时间戳(TP-SCTS) 2003-3-12 08:36:45 +8时区
06 用户信息长度(TP-UDL) 实际长度6个字节
4F 60 59 7D 00 21 用户信息(TP-UD) “你好!”
若基本参数的最高位(TP-RP)为0,则没有回复地址的三个段。从Internet上发出的短消息常常是这种情形。
注意号码和时间的表示方法,不是按正常顺序顺着来的,而且要以‘F’将奇数补成偶数。
在PDU Mode中,可以采用三种编码方式来对发送的内容进行编码,它们是7-bit、8-bit和UCS2编码。7-bit编码用于发送普通的ASCII字符,它将一串7-bit的字符(最高位为0)编码成8-bit的数据,每8个字符可“压缩”成7个;8-bit编码通常用于发送数据消息,比如图片和铃声等;而UCS2编码用于发送Unicode字符。PDU串的用户信息(TP-UD)段最大容量是140字节,所以在这三种编码方式下,可以发送的短消息的最大字符数分别是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),则该长度也是压缩编码后字节数或头长度与压缩编码后字节数之和。
2 参见详细英文说明:http://www.dreamfabric.com/sms/
3 delphi代码
{ --------------------------------------------------------- }
{ Purposes : }
{ 1. Convert ASCII to 7-bit PDU }
{ 2. Convert 7-bit, 8-bit and 16-bit PDU to ASCII }
{ 3. Decode/Parsing the hexadecimal (PDU) of SMS message }
{ 4. Encode ASCII characters to be sent as SMS ready string }
{ }
{ --------------------------------------------------------- }
{ Coder : Daniel Eka Nugraha }
{ URL : http://www.averagecoder.com/ }
{ Email : [email protected] }
{ --------------------------------------------------------- }
{ This is not a perfect one, so Use it at your own risk ! }
{ --------------------------------------------------------- }
unit SMSCODEC;
interface
uses
Windows, Messages, SysUtils, Classes, StrUtils, Math, Dialogs;
type
TSMSCODEC = class(TComponent)
private
{ Private declarations }
protected
{ Protected declarations }
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function Binerkan(Angka: integer): string;
function Bin2Long(StrBiner: string): integer;
function PDU7BIT2ASCII(StrPDU: string; PanjangUDH, PanjangData: integer): string;
function PDU8BIT2ASCII(StrPDU: string; PanjangUDH, PanjangData: integer): string;
function PDU16BIT2ASCII(StrPDU: string; PanjangUDH, PanjangData: integer): string;
function ASCII2PDU7BIT(StrASCII: string): string;
function DecodeSMS(StrSMS : string) : TStringList;
function EncodeSMS(StrTujuan, StrPesan: string): string;
published
{ Published declarations }
end;
procedure Register;
implementation
var Nono: integer;
EQ7BIT2ASCII : array [0..127] of integer; // Table persamaan 7-bit ke ASCII
EQASCII27BIT : array [0..255] of integer; // Table persamaan ASCII ke 7-bit
procedure Register;
begin
RegisterComponents('averagecoder', [TSMSCODEC]);
end;
constructor TSMSCODEC.Create(AOwner: TComponent);
var i : integer;
begin
inherited Create(AOwner);
// This is the transition table from 7-bit to ASCII
Nono := -1;
EQ7BIT2ASCII[0] := 64;
EQ7BIT2ASCII[1] := 163;
EQ7BIT2ASCII[2] := 36;
EQ7BIT2ASCII[3] := 165;
EQ7BIT2ASCII[4] := 232;
EQ7BIT2ASCII[5] := 223;
EQ7BIT2ASCII[6] := 249;
EQ7BIT2ASCII[7] := 236;
EQ7BIT2ASCII[8] := 242;
EQ7BIT2ASCII[9] := 199;
EQ7BIT2ASCII[10] := 10;
EQ7BIT2ASCII[11] := 216;
EQ7BIT2ASCII[12] := 248;
EQ7BIT2ASCII[13] := 13;
EQ7BIT2ASCII[14] := 197;
EQ7BIT2ASCII[15] := 229;
EQ7BIT2ASCII[16] := Nono;
EQ7BIT2ASCII[17] := 95;
EQ7BIT2ASCII[18] := Nono;
EQ7BIT2ASCII[19] := Nono;
EQ7BIT2ASCII[20] := Nono;
EQ7BIT2ASCII[21] := Nono;
EQ7BIT2ASCII[22] := Nono;
EQ7BIT2ASCII[23] := Nono;
EQ7BIT2ASCII[24] := Nono;
EQ7BIT2ASCII[25] := Nono;
EQ7BIT2ASCII[26] := Nono;
EQ7BIT2ASCII[27] := Nono;
EQ7BIT2ASCII[28] := 198;
EQ7BIT2ASCII[29] := 230;
EQ7BIT2ASCII[30] := 223;
EQ7BIT2ASCII[31] := 201;
EQ7BIT2ASCII[32] := 32;
EQ7BIT2ASCII[33] := 33;
EQ7BIT2ASCII[34] := 34;
EQ7BIT2ASCII[35] := 35;
EQ7BIT2ASCII[36] := 164;
EQ7BIT2ASCII[37] := 37;
EQ7BIT2ASCII[38] := 38;
EQ7BIT2ASCII[39] := 39;
EQ7BIT2ASCII[40] := 40;
EQ7BIT2ASCII[41] := 41;
EQ7BIT2ASCII[42] := 42;
EQ7BIT2ASCII[43] := 43;
EQ7BIT2ASCII[44] := 44;
EQ7BIT2ASCII[45] := 45;
EQ7BIT2ASCII[46] := 46;
EQ7BIT2ASCII[47] := 47;
EQ7BIT2ASCII[48] := 48;
EQ7BIT2ASCII[49] := 49;
EQ7BIT2ASCII[50] := 50;
EQ7BIT2ASCII[51] := 51;
EQ7BIT2ASCII[52] := 52;
EQ7BIT2ASCII[53] := 53;
EQ7BIT2ASCII[54] := 54;
EQ7BIT2ASCII[55] := 55;
EQ7BIT2ASCII[56] := 56;
EQ7BIT2ASCII[57] := 57;
EQ7BIT2ASCII[58] := 58;
EQ7BIT2ASCII[59] := 59;
EQ7BIT2ASCII[60] := 60;
EQ7BIT2ASCII[61] := 61;
EQ7BIT2ASCII[62] := 62;
EQ7BIT2ASCII[63] := 63;
EQ7BIT2ASCII[64] := 161;
EQ7BIT2ASCII[65] := 65;
EQ7BIT2ASCII[66] := 66;
EQ7BIT2ASCII[67] := 67;
EQ7BIT2ASCII[68] := 68;
EQ7BIT2ASCII[69] := 69;
EQ7BIT2ASCII[70] := 70;
EQ7BIT2ASCII[71] := 71;
EQ7BIT2ASCII[72] := 72;
EQ7BIT2ASCII[73] := 73;
EQ7BIT2ASCII[74] := 74;
EQ7BIT2ASCII[75] := 75;
EQ7BIT2ASCII[76] := 76;
EQ7BIT2ASCII[77] := 77;
EQ7BIT2ASCII[78] := 78;
EQ7BIT2ASCII[79] := 79;
EQ7BIT2ASCII[80] := 80;
EQ7BIT2ASCII[81] := 81;
EQ7BIT2ASCII[82] := 82;
EQ7BIT2ASCII[83] := 83;
EQ7BIT2ASCII[84] := 84;
EQ7BIT2ASCII[85] := 85;
EQ7BIT2ASCII[86] := 86;
EQ7BIT2ASCII[87] := 87;
EQ7BIT2ASCII[88] := 88;
EQ7BIT2ASCII[89] := 89;
EQ7BIT2ASCII[90] := 90;
EQ7BIT2ASCII[91] := 196;
EQ7BIT2ASCII[92] := 204;
EQ7BIT2ASCII[93] := 209;
EQ7BIT2ASCII[94] := 220;
EQ7BIT2ASCII[95] := 167;
EQ7BIT2ASCII[96] := 191;
EQ7BIT2ASCII[97] := 97;
EQ7BIT2ASCII[98] := 98;
EQ7BIT2ASCII[99] := 99;
EQ7BIT2ASCII[100] := 100;
EQ7BIT2ASCII[101] := 101;
EQ7BIT2ASCII[102] := 102;
EQ7BIT2ASCII[103] := 103;
EQ7BIT2ASCII[104] := 104;
EQ7BIT2ASCII[105] := 105;
EQ7BIT2ASCII[106] := 106;
EQ7BIT2ASCII[107] := 107;
EQ7BIT2ASCII[108] := 108;
EQ7BIT2ASCII[109] := 109;
EQ7BIT2ASCII[110] := 110;
EQ7BIT2ASCII[111] := 111;
EQ7BIT2ASCII[112] := 112;
EQ7BIT2ASCII[113] := 113;
EQ7BIT2ASCII[114] := 114;
EQ7BIT2ASCII[115] := 115;
EQ7BIT2ASCII[116] := 116;
EQ7BIT2ASCII[117] := 117;
EQ7BIT2ASCII[118] := 118;
EQ7BIT2ASCII[119] := 119;
EQ7BIT2ASCII[120] := 120;
EQ7BIT2ASCII[121] := 121;
EQ7BIT2ASCII[122] := 122;
EQ7BIT2ASCII[123] := 228;
EQ7BIT2ASCII[124] := 246;
EQ7BIT2ASCII[125] := 241;
EQ7BIT2ASCII[126] := 252;
EQ7BIT2ASCII[127] := 224;
// This is the transition table from ASCII to 7-BIT; index reversing :)
for i := 0 to High(EQ7BIT2ASCII) do
begin
if EQ7BIT2ASCII[i] <> Nono then
EQASCII27BIT[EQ7BIT2ASCII[i]] := i;
end;
end; { TSMSCODEC.Create }
destructor TSMSCODEC.Destroy;
begin
inherited Destroy;
end; { TSMSCODEC.Destroy }
{
--------------------------------------------------------------------------------
function Binerkan
-> Convert integer to binary
-> parameters:
Angka: integer; -> the integer value
-> Return value: string of 1 and 0 (nothing genius at all :D)
--------------------------------------------------------------------------------
}
function TSMSCODEC.Binerkan(Angka : integer): string;
var i : Integer;
hasil : array [0..7] of Integer;
SISA : array [0..7] of Integer;
StrHasil : string;
begin
result := '';
if angka > 1 Then
begin
i := 1;
repeat
hasil[i] := Trunc(angka / 2);
SISA[i] := angka mod 2;
angka := hasil[i];
i := i + 1;
StrHasil := IntToStr(SISA[i - 1]) + StrHasil;
until hasil[i - 1] < 2;
StrHasil := IntToStr(hasil[i - 1]) + StrHasil;
end
else if angka = 1 then
StrHasil := '00000001'
else if angka = 0 Then
StrHasil := '00000000';
for i := 1 to (8 - Length(StrHasil)) do
StrHasil := '0' + StrHasil;
result := StrHasil;
end; { TSMSCODEC.Binerkan }
{
--------------------------------------------------------------------------------
function Bin2Long
-> Convert binaty string to integer
-> Parameter:
StrBiner: string; -> the binary string;
-> Return value: integer;
--------------------------------------------------------------------------------
}
function TSMSCODEC.Bin2Long(StrBiner : string): integer;
var i, x : integer;
begin
x := 0;
for i := 1 to Length(StrBiner) do
begin
x := x + StrToInt(Copy(StrBiner, i, 1)) * Trunc(Power(2,(Length(StrBiner) - i)));
end;
result := x;
end; { TSMSCODEC.Bin2Long }
{
--------------------------------------------------------------------------------
function PDU7BIT2ASCII
-> Convert PDU (string 7-bit PDU) to ASCII
-> Parameter:
StrPDU: string -> the PDUString
PanjangUDH: integer; -> the length of the UDH
PanjangData: integer; -> the length of the data
-> return value: ASCII string
--------------------------------------------------------------------------------
}
function TSMSCODEC.PDU7BIT2ASCII(StrPDU : string; PanjangUDH, PanjangData: integer) : string;
var i,j,x,z : integer;
y, StrASCII : string;
ArrPDU : array of string;
Arr7Bit : array of string;
ArrASCII : array of string;
begin
j := 1;
z := 0;
// 2 PDU chars represent 1 ASCII chars, so the length of the
// ASCII chars sequence is 1/2 of the string PDU length
SetLength(ArrPDU,Length(StrPDU) div 2);
// Load each pair of PDU string to the appropriate index of ArrPDU
while j < Length(StrPDU) do
begin
ArrPDU[z] := '$' + Copy(StrPDU,j,2); // '$' + string = hexadecimal
j := j + 2;
Inc(z);
end;
// Convert each ArrPDU item to binary for decoding
for i := 0 to High(ArrPDU) do
begin
y := ArrPDU[i];
ArrPDU[i] := '';
ArrPDU[i] := Binerkan(StrToInt(y));
end;
x := 0;
SetLength(Arr7BIT,x+1);
// The core process - the bit mutation
for i := 0 to High(ArrPDU) do
begin
if i mod 7 = 0 then
begin
Arr7BIT[x] := RightStr(ArrPDU[i],7);
end
else
begin
if i mod 7 = 6 then
begin
Arr7BIT[x] := RightStr(ArrPDU[i],1) + LeftStr(ArrPDU[i-1],6);
x := x + 1;
SetLength(Arr7BIT,x+1);
Arr7BIT[x] := LeftStr(ArrPDU[i],7);
end
else
Arr7BIT[x] := RightStr(ArrPDU[i],Length(ArrPDU[i])-(i mod 7)-1) + LeftStr(ArrPDU[i-1],i mod 7);
end;
x := x + 1;
SetLength(Arr7BIT,x+1);
end; { for }
j := 0;
while j <= High(Arr7BIT) do
begin
SetLength(ArrASCII,Length(ArrASCII)+1);
if Bin2Long(Arr7BIT[j]) = 27 then
begin
inc(j);
case Bin2Long(Arr7BIT[j]) of
10 : ArrASCII[High(ArrASCII)] := Chr(12);
20 : ArrASCII[High(ArrASCII)] := Chr(94);
40 : ArrASCII[High(ArrASCII)] := Chr(123);
41 : ArrASCII[High(ArrASCII)] := Chr(125);
47 : ArrASCII[High(ArrASCII)] := Chr(92);
60 : ArrASCII[High(ArrASCII)] := Chr(91);
61 : ArrASCII[High(ArrASCII)] := Chr(126);
62 : ArrASCII[High(ArrASCII)] := Chr(93);
64 : ArrASCII[High(ArrASCII)] := Chr(124);
else
ArrASCII[High(ArrASCII)] := Chr(0);
end;
end
else
begin
ArrASCII[High(ArrASCII)] := Chr(EQ7BIT2ASCII[Bin2Long(Arr7BIT[j])]);
end;
Inc(j);
end; { while }
SetLength(ArrASCII,PanjangData);
for i := 0 to High(ArrASCII) do
StrASCII := StrASCII + (ArrASCII[i]);
ArrPDU := nil; Arr7Bit := nil; ArrASCII := nil;
// if no UDH, ignore the octet info of UDH length,
// not part of SMS message.
if PanjangUDH > 0 then
StrASCII := Copy(StrASCII,PanjangUDH + 3,Length(StrASCII));
Result := StrASCII;
end; { TSMSCODEC.PDU7BIT2ASCII }
{
--------------------------------------------------------------------------------
function PDU8BIT2ASCII
-> Convert string 8-bit PDU to ASCII
-> parameter:
strPDU: string; -> the 8 bit PDU string
PanjangUDH: integer; -> length of the UDH
PanjangData: integer; -> Length of the data
-> return value: string (ASCII)
--------------------------------------------------------------------------------
}
function TSMSCODEC.PDU8BIT2ASCII(StrPDU : string; PanjangUDH, PanjangData: integer) : string;
var StrASCII : string;
i, x, PjPDUPesan : integer;
begin
if PanjangUDH <> 0 then
begin
x := (PanjangUDH * 2) + 2; // PDU info UDH
StrPDU := RightStr(StrPDU,(PanjangData * 2) - x); // PDU Pesan
end;
PjPDUPesan := Length(StrPDU);
i := 1;
while i <= PjPDUPesan do
begin
if i mod 2 = 0 then
begin
StrASCII := StrASCII + Chr(EQ7BIT2ASCII[StrToInt('$' + LeftStr(StrPDU,2))]);
StrPDU := RightStr(StrPDU,Length(StrPDU)-2);
end;
Inc(i);
end;
result := StrASCII;
end; { TSMSCODEC.PDU8BIT2ASCII }
{
--------------------------------------------------------------------------------
function PDU16BIT2ASCII
-> COnvert StrPDU (string 16-bit PDU) to ASCII
-> parameter:
strPDU: string; -> the 8 bit PDU string
PanjangUDH: integer; -> length of the UDH
PanjangData: integer; -> Length of the data
-> Return value: string (ASCII)
--------------------------------------------------------------------------------
}
function TSMSCODEC.PDU16BIT2ASCII(StrPDU : string; PanjangUDH, PanjangData: integer) : string;
var StrASCII : string;
i, x, PjPDUPesan : integer;
begin
if PanjangUDH <> 0 then
begin
x := (PanjangUDH * 2) + 2;
StrPDU := RightStr(StrPDU,(PanjangData * 2) - x);
end;
PjPDUPesan := Length(StrPDU);
i := 1;
while i <= PjPDUPesan do
begin
if i mod 4 = 0 then
begin
StrASCII := StrASCII + Chr(EQ7BIT2ASCII[StrToInt('$' + LeftStr(StrPDU,4))]);
StrPDU := RightStr(StrPDU,Length(StrPDU)-4);
end;
Inc(i);
end;
result := StrASCII;
end; { TSMSCODEC.PDU16BIT2ASCII }
{
--------------------------------------------------------------------------------
function ASCII2PDU7BIT
-> Convert par StrASCII (string ASCII) to 7-bit PDU
-> parameter:
strASCII: string; -> the ASCII string
-> Return value: string (7-bit PDU)
--------------------------------------------------------------------------------
}
function TSMSCODEC.ASCII2PDU7BIT(StrASCII : string) : string;
var i,x,z : integer;
ArrPDU : array of string;
Arr7Bit : array of string;
StrPDU : string;
begin
for i := 1 to Length(StrASCII) do
begin
SetLength(Arr7BIT,Length(Arr7BIT)+1);
case Ord(StrASCII[i]) of
12 : begin
SetLength(Arr7BIT,Length(Arr7BIT)+1);
Arr7BIT[High(Arr7BIT)-1] := RightStr(Binerkan(27),7);
Arr7BIT[High(Arr7BIT)] := RightStr(Binerkan(10),7);
end;
94 : begin
SetLength(Arr7BIT,Length(Arr7BIT)+1);
Arr7BIT[High(Arr7BIT)-1] := RightStr(Binerkan(27),7);
Arr7BIT[High(Arr7BIT)] := RightStr(Binerkan(20),7);
end;
123 : begin
SetLength(Arr7BIT,Length(Arr7BIT)+1);
Arr7BIT[High(Arr7BIT)-1] := RightStr(Binerkan(27),7);
Arr7BIT[High(Arr7BIT)] := RightStr(Binerkan(40),7);
end;
125 : begin
SetLength(Arr7BIT,Length(Arr7BIT)+1);
Arr7BIT[High(Arr7BIT)-1] := RightStr(Binerkan(27),7);
Arr7BIT[High(Arr7BIT)] := RightStr(Binerkan(41),7);
end;
92 : begin
SetLength(Arr7BIT,Length(Arr7BIT)+1);
Arr7BIT[High(Arr7BIT)-1] := RightStr(Binerkan(27),7);
Arr7BIT[High(Arr7BIT)] := RightStr(Binerkan(47),7);
end;
91 : begin
SetLength(Arr7BIT,Length(Arr7BIT)+1);
Arr7BIT[High(Arr7BIT)-1] := RightStr(Binerkan(27),7);
Arr7BIT[High(Arr7BIT)] := RightStr(Binerkan(60),7);
end;
126 : begin
SetLength(Arr7BIT,Length(Arr7BIT)+1);
Arr7BIT[High(Arr7BIT)-1] := RightStr(Binerkan(27),7);
Arr7BIT[High(Arr7BIT)] := RightStr(Binerkan(61),7);
end;
93 : begin
SetLength(Arr7BIT,Length(Arr7BIT)+1);
Arr7BIT[High(Arr7BIT)-1] := RightStr(Binerkan(27),7);
Arr7BIT[High(Arr7BIT)] := RightStr(Binerkan(62),7);
end;
124 : begin
SetLength(Arr7BIT,Length(Arr7BIT)+1);
Arr7BIT[High(Arr7BIT)-1] := RightStr(Binerkan(27),7);
Arr7BIT[High(Arr7BIT)] := RightStr(Binerkan(64),7);
end;
else
begin
Arr7BIT[High(Arr7BIT)] := RightStr(Binerkan(EQASCII27BIT[Ord(StrASCII[i])]),7);
end;
end; { case }
end; { for }
i := 1;
x := 1;
z := 0;
SetLength(ArrPDU,z+1);
while i <= High(Arr7BIT) do
begin
if (i > 1) and ((i) mod 8 = 0) then
begin
x := 1;
inc(i);
end
else
begin
ArrPDU[z] := RightStr(Arr7BIT[i],x) + Arr7BIT[i-1];
SetLength(Arr7BIT[i],Length(Arr7BIT[i])-x);
inc(z);
SetLength(ArrPDU,z+1);
Inc(x);
Inc(i);
end;
end;
ArrPDU[High(ArrPDU)] := Arr7BIT[High(Arr7BIT)];
for i := 0 to High(ArrPDU) do
StrPDU := StrPDU + IntToHex(Bin2Long(ArrPDU[i]),2);
ArrPDU := nil;
Arr7Bit := nil;
{if RightStr(StrPDU,2) = '00' then
StrPDU := LeftStr(StrPDU,Length(StrPDU)-2);}
Result := StrPDU;
end; { TSMSCODEC.ASCII2PDU7BIT }
{
--------------------------------------------------------------------------------
procedure DecodeSMS
-> Decode the SMSPDU then parse the element (such as the sender and the message) to be returned.
-> Return value: stringlist (length: 2, index 0: sender number, index 1: the message
--------------------------------------------------------------------------------
}
function TSMSCODEC.DecodeSMS(StrSMS : string) : TStringList;
var i, j, x, y, z, PanjangUDH, PanjangData : integer;
StrBiner, StrPDU, StrPengirim, s : string;
PakeUDH, Alpha : boolean; Elemen : TStringList;
zDCS : integer; zDCSBinIdx23 : string;
begin
Elemen := TStringList.Create;
i := 1;
PakeUDH := False;
Alpha := False;
x := StrToInt('$' + Copy(StrSMS,i,2));
i := i + 2;
i := i + (x * 2);
x := StrToInt('$' + Copy(StrSMS,i,2));
StrBiner := Binerkan(x);
if StrBiner[2] = '1' then
begin
PakeUDH := True;
end;
i := i + 2;
x := StrToInt('$' + Copy(StrSMS,i,2));
y := x;
i := i + 2;
z := StrToInt('$' + Copy(StrSMS,i,2));
StrBiner := Binerkan(z);
if Copy(StrBiner,2,3) = '101' then
Alpha := True;
i := i + 2;
if x mod 2 <> 0 then
x := x + 1;
s := Copy(StrSMS,i,x);
for j := 1 to Length(s) do
begin
if j mod 2 = 0 then
begin
StrPengirim := StrPengirim + s[j] + s[j-1];
end;
end;
if Alpha = True then
begin
StrPengirim := PDU7BIT2ASCII(s,0,Length(s));
StrPengirim := StringReplace(StrPengirim,'@','',[rfReplaceAll]);
end
else
StrPengirim := Copy(StrPengirim,1,y);
Elemen.Add(StrPengirim);
i := i + x + 2;
// Data Coding Schemes (DCS)
z := StrToInt('$' + Copy(StrSMS,i,2));
StrBiner := Binerkan(z);
zDCSBinIdx23 := Copy(StrBiner,5,2);
zDCS := 0;
if zDCSBinIdx23 = '00' then
zDCS := 0; // 7-bit
if zDCSBinIdx23 = '01' then
zDCS := 1; // 8-bit
if zDCSBinIdx23 = '10' then
zDCS := 2; // UCS2
if zDCSBinIdx23 = '11' then
zDCS := 0; // reserved (pake 7-bit)
i := i + 2;
x := 14; // abaikan SCTS
i := i + x;
PanjangData := StrToInt('$' + Copy(StrSMS,i,2));
i := i + 2;
StrPDU := Copy(StrSMS,i,Length(StrSMS));
if PakeUDH then
PanjangUDH := StrToInt('$' + Copy(StrSMS,i,2))
else
PanjangUDH := 0;
case zDCS of
0 : Elemen.Add(PDU7BIT2ASCII(StrPDU, PanjangUDH, PanjangData));
1 : Elemen.Add(PDU8BIT2ASCII(StrPDU, PanjangUDH, PanjangData));
2 : Elemen.Add(PDU16BIT2ASCII(StrPDU, PanjangUDH, PanjangData));
else
Elemen.Add(PDU7BIT2ASCII(StrPDU, PanjangUDH, PanjangData));
end;
Result := Elemen;
end; { TSMSCODEC.DecodeSMS }
{
--------------------------------------------------------------------------------
procedure EncodeSMS
-> Compose an SMS message
-> Paramaters:
strTujuan = string; -> The destination number
strPesan = string; -> Your message to be sent
-> Return value: SMS-PDU
--------------------------------------------------------------------------------
}
function TSMSCODEC.EncodeSMS(StrTujuan, StrPesan: string): string;
var zTujuan, zPesan, Gabung: string;
i, pjTujuan : integer;
begin
pjTujuan := Length(StrTujuan);
// If length of StrTujuan is odd, add "F" to the end
if Length(StrTujuan) mod 2 = 1 then
StrTujuan := StrTujuan + 'F';
// The reversing process of destination number (swapped nibble)
for i := 1 to Length(StrTujuan) do
begin
if i mod 2 = 0 then
begin
zTujuan := zTujuan + StrTujuan[i] + StrTujuan[i-1];
end;
end;
// We use internasional format here, add 91 in front of the destination number
zTujuan := IntToHex(pjTujuan,2) + '91' + zTujuan;
if Length(StrPesan) > 0 then
zPesan := ASCII2PDU7BIT(StrPesan); // We use 7Bit encoding here
// Add the hexadecimal reperesentation of the PDU length (1/2 of the number of chars) in front of the PDU
zPesan := IntToHex(Length(StrPesan),2) + zPesan;
// We use the SMSC number in the cellphone/gsmmodem, no status report, no UDH
Gabung := '000100' + zTujuan + '0000' + zPesan;
result := Gabung;
end; { TSMSCODEC.EncodeSMS }
end.
4使用代码示例
procedure TForm1.Button1Click(Sender: TObject);
begin
Edit2.Text := SMSCODEC1.ASCII2PDU7BIT(Edit1.Text);
Edit6.Text := Edit2.Text;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Edit3.Text := SMSCODEC1.PDU7BIT2ASCII(Edit6.Text,0,Length(Edit2.Text));
end;
procedure TForm1.Label1Click(Sender: TObject);
begin
ShellExecute(Handle,'open','http://www.averagecoder.com','','',SW_SHOWNORMAL);
end;
procedure TForm1.Label1MouseEnter(Sender: TObject);
begin
Label1.Font.color := clBlue;
end;
procedure TForm1.Label1MouseLeave(Sender: TObject);
begin
Label1.Font.color := clBlack;
end;
procedure TForm1.Button3Click(Sender: TObject);
var ResultList: TStringList;
begin
ResultList := SMSCODEC1.DecodeSMS(Edit4.Text);
Edit5.Text := ResultList.Strings[0]; // sender number
Memo1.Text := ResultList.Strings[1]; // The message
end;
procedure TForm1.Button4Click(Sender: TObject);
begin
Edit8.Text := SMSCODEC1.EncodeSMS(Edit7.Text,Memo2.Text)
end;
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"的缩写。
根据维基百科全书(http://zh.wikipedia.org/wiki/)的记载:历史上存在两个试图独立设计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资料,加上我这些应该很容易就可以实现发短信了。