超长短信:长度超过一条,而分多条发送的短信,通过用户数据头标识在接收端进行组合的短信(接收的短信在手机或其他终端上看到的是一条)。GSM_03.40规范中是Concatenated Short Messages :This facility allows short messages to be concatenated to form a longer message.
此种短信理论上最长可以将255条短信合成一条,名副其实的超长短信。
有关超长短信可以参考GSM_03.40规范和CMPP有关超长短信的内容:GSM_03.40规范中的 9.2.3.23 TP-User-Data-Header-Indicator (TP-UDHI) 和9.2.3.24 TP-User Data (TP-UD)
本文的程序是在原来基础上添加的,详细请参考:短信猫软件的实现(C#)系列博客索引PDU字符串中与超长短信有关的只有TP-UDHI位(在PDU字串中的PDUType的D6位),有关PDU编码请参考:短信猫软件的实现(C#)<三>PDU格式短信解析。TP-UDHI位为1,则在User Data中含有消息头,用来表示各种不同的其他形式短信,其中包括长短信。
消息头是User Data的开头部分,有两种格式:6位格式和 7位格式。6位:05 00 03 XX MM NN;7位格式:06 08 04 XX XX MM NN。
各字节含义:
byte 1:剩余协议头长度。
byte 2:00/08 这个字节在GSM 03.40规范9.2.3.24中规定,00:代表长短信,8位参考标识;08:代表长短信,16位参考标识;还规定了其他数值,与长短信无关,详细参考GSM 03.40规范9.2.3.24。
byte 3:代表剩下短信标识的长度:03,三个字节;04,四个字节。
byte 4:XX 这批短信的唯一标志,事实上,SME(手机或者SP)把消息合并完之后,就重新记录,所以这个标志是否唯一并不是很 重要。7位格式的 和byte 5一起作为16位标志。
byte 5:MM 这批短信的数量,超长短信分成几条,值即是几。7位 XX和byte 4共同作为16位标识。
byte 6:NN 本条短信在超长短信中是第几条。7位格式 MM 同6位格式的 MM。
byte 7:NN 7位格式中,同6位格式中的NN。长短信消息头规律:第一个字节:消息头剩余长度;第二字节:消息类型;第三字节:剩余消息头长度;后面一个或两个字节根据标识位数作为这批短信的唯一标识,是否唯一不重要,但同批短信标志位必须相同,否则将被解析成多条短信。后面两个字节分别是总数量和序号。
- 编码实现:
此次编码是在之前编码基础上添加长短信编解码部分而实现的,添加时不对原来程序做过多修改;这次添加长短信深感这个类库的可扩展性太差,以致程序有点乱,添加长短信费了一番功夫,而且功能实现不尽合理;由于这段时间比较忙,暂时不对程序做大的改动,仅仅添加长短信编码部分。
对编解码类的更改:
属性更改:
长短信发送时需将TP-UDHI位置为1,而这位位于PDU-type 这个8位组,普通短信这个八位组发送时值为“11” 接收时为“24”,长短信 分别为: “51”、“64”。之前程序对应的属性只能读到“11”,字段值也为“11”没有更改。为使其支持长短信编解码将其中属性、字段更改为:
1: private string protocolDataUnitType = "11";2: /// <summary>3: /// 协议数据单元类型(1个8位组)4: /// </summary>5: public string ProtocolDataUnitType6: {
7: set8: {9: protocolDataUnitType = value;10: }
11: get12: {13: return protocolDataUnitType;14: }
15: }
这样编解码时只需正确设置属性值,即可完成长短信的编解码。
方法更改:
编码:(USC2/7位):
只需把原来程序 字符数超过最大字符数时 抛出异常改为 对应长短信编码即可;为了改动的地方比较少,返回值:长短信返回逗号分隔的PDU串。7bit编码须做一定处理,规范中要求添加填充位,让后面userData符合7bit的格式;6byte消息头共占48bit 填充一位补成49bit,相当于后面第一个ASCII符做一定特殊处理,后面直接调用之前的编码函数即可,通过验证发现 第一个只需左移一位,即完成这一位编码,放入PDU传即可。
1: /// <summary>2: /// PDU编码器,完成PDU编码(USC2编码,超过70个字时 分多条发送,PDU各个串之间逗号分隔)3: /// </summary>4: /// <param name="phone">目的手机号码</param>5: /// <param name="Text">短信内容</param>6: /// <returns>编码后的PDU字符串 长短信时 逗号分隔</returns>7: public string PDUUSC2Encoder(string phone, string Text)8: {
9: DestinationAddress = phone;
10:
11: if (Text.Length > 70)12: {
13: //长短信设TP-UDHI位为1 PDU-type = “51”14: ProtocolDataUnitType = "51";15:
16: //计算长短信条数17: int count = Text.Length / 67 + 1;18:
19: //长短信格式字符串,格式 每条之间 逗号分隔20: string result = string.Empty;21:
22: for (int i = 0; i < count; i++)23: {
24: //如果不是最后一条25: if (i != count - 1)26: {
27: UserData = Text.Substring(i * 67, 67);
28:
29: result += serviceCenterAddress + protocolDataUnitType
30: + messageReference + destinationAddress + protocolIdentifer
31: + dataCodingScheme + validityPeriod + (userData.Length / 2 + 6).ToString("X2")32: + "05000339" + count.ToString("X2") + (i + 1).ToString("X2") + userData + ",";33: }
34: else35: {36: UserData = Text.Substring(i * 67);
37:
38: if (userData != null || userData.Length != 0)39: {
40:
41: result += serviceCenterAddress + protocolDataUnitType
42: + messageReference + destinationAddress + protocolIdentifer
43: + dataCodingScheme + validityPeriod + (userData.Length / 2 + 6).ToString("X2")44: + "05000339" + count.ToString("X2") + (i + 1).ToString("X2") + userData;45: }
46: else47: {48: result = result.TrimEnd(',');49: }
50: }
51: }
52:
53: return result;54: }
55:
56: //不是长短信57: UserData = Text;58: return serviceCenterAddress + protocolDataUnitType59: + messageReference + destinationAddress + protocolIdentifer
60: + dataCodingScheme + validityPeriod + userDataLenghth + userData;
61: }
62:
63: /// <summary>64: /// 7bit 编码(超过160个字时 分多条发送,PDU各个串之间逗号分隔)65: /// </summary>66: /// <param name="phone">手机号码</param>67: /// <param name="Text">短信内容</param>68: /// <returns>编码后的字符串 长短信时 逗号分隔</returns>69: public string PDU7BitEncoder(string phone, string Text)70: {
71: dataCodingScheme = "00";72: DestinationAddress = phone;
73:
74: if (Text.Length > 160)75: {
76: //长短信设TP-UDHI位为1 PDU-type = “51”77: ProtocolDataUnitType = "51";78:
79: //计算长短信条数80: int count = Text.Length / 153 + 1;81:
82: //长短信格式字符串,格式 每条之间 逗号分隔83: string result = string.Empty;84:
85: for (int i = 0; i < count; i++)86: {
87: //如果不是最后一条88: if (i != count - 1)89: {
90: UserData = Text.Substring(i * 153 + 1, 152);
91:
92: result += serviceCenterAddress + protocolDataUnitType
93: + messageReference + destinationAddress + protocolIdentifer
94: + dataCodingScheme + validityPeriod + (160).ToString("X2")95: + "05000339" + count.ToString("X2") + (i + 1).ToString("X2")96: +((int)(new ASCIIEncoding().GetBytes(Text.Substring(i*153,1))[0]<<1)).ToString("X2") + userData + ",";97: }
98: else99: {100: UserData = Text.Substring(i * 153+1);
101:
102: int len = Text.Substring(i * 153).Length;103:
104: if (userData != null || userData.Length != 0)105: {
106:
107: result += serviceCenterAddress + protocolDataUnitType
108: + messageReference + destinationAddress + protocolIdentifer
109: + dataCodingScheme + validityPeriod + (len + 7).ToString("X2")110: + "05000339" + count.ToString("X2") + (i + 1).ToString("X2")111: + ((int)(new ASCIIEncoding().GetBytes(Text.Substring(i * 153, 1))[0] << 1)).ToString("X2")112: + userData;
113: }
114: else115: {116: result = result.TrimEnd(',');117: }
118: }
119: }
120:
121: return result;122: }
123:
124: UserData = Text;
125:
126: return serviceCenterAddress + protocolDataUnitType127: + messageReference + destinationAddress + protocolIdentifer
128: + dataCodingScheme + validityPeriod + userDataLenghth + userData;
129: }
这样,调用时 非长短信调用方式不变 长短信 返回值为逗号分隔的各PDU串、很方便调用方更改。
解码(USC2/7位):
解码函数只需添加对TP-UDHI位判断,为1则根据消息头解出本条短信在本批次短信中的位置 及本批次短信的总条数。
PDUDecoder完成PDU解码,PDU7bitDecoder仅完成7位PDU的用户数据编码,供UserData属性调用,不需改动;只需更改PDUDecoder即完成长短信编码:根据TP-udhi位取出消息头,消息体赋给userData即可正常解码。方法返回格式改为:MMNNXX,中心号码,手机号码,发送时间,短信内容 MM这批短信总条数 NN本条所在序号 XX为这条短信的唯一标识;
1: /// <summary>2: /// 重载 解码,返回信息字符串 格式3: /// </summary>4: /// <param name="strPDU">短信PDU字符串</param>5: /// <returns>信息字符串(MMNN,中心号码,手机号码,发送时间,短信内容 MM这批短信总条数 NN本条所在序号)</returns>6: public string PDUDecoder(string strPDU)7: {
8: int lenSCA = Convert.ToInt32(strPDU.Substring(0, 2), 16) * 2 + 2; //短消息中心占长度9: serviceCenterAddress = strPDU.Substring(0, lenSCA);10:
11: //PDU-type位组12: protocolDataUnitType = strPDU.Substring(lenSCA, 2);13:
14: int lenOA = Convert.ToInt32(strPDU.Substring(lenSCA + 2, 2), 16); //OA占用长度15: if (lenOA % 2 == 1) //奇数则加1 F位16: {17: lenOA++;
18: }
19: lenOA += 4; //加号码编码的头部长度20: originatorAddress = strPDU.Substring(lenSCA + 2, lenOA);21:
22: dataCodingScheme = strPDU.Substring(lenSCA + lenOA + 4, 2); //DCS赋值,区分解码7bit23:
24: serviceCenterTimeStamp = strPDU.Substring(lenSCA + lenOA + 6, 14);25:
26: userDataLenghth = strPDU.Substring(lenSCA + lenOA + 20, 2);
27: int lenUD = Convert.ToInt32(userDataLenghth, 16) * 2;28:
29: if (protocolDataUnitType != "24")30: {
31: if (dataCodingScheme == "08" || dataCodingScheme == "18") //USC2 长短信 去掉消息头32: {33: userDataLenghth = (Convert.ToInt16(strPDU.Substring(lenSCA + lenOA + 20, 2), 16) - 6).ToString("X2");34: userData = strPDU.Substring(lenSCA + lenOA + 22 + 6 * 2);
35:
36: return strPDU.Substring(lenSCA + lenOA + 22 + 4 * 2, 2 * 2)37: + strPDU.Substring(lenSCA + lenOA + 22 + 3 * 2, 2) + "," + ServiceCenterAddress + ","38: + OriginatorAddress + "," + ServiceCenterTimeStamp + "," + UserData;39: }
40: else41: {42: userData = strPDU.Substring(lenSCA + lenOA + 22 + 6 * 2 + 1 * 2); //消息头六字节,第一字节特殊译码 >>743:
44: //首字节译码45: byte byt = Convert.ToByte(strPDU.Substring(lenSCA + lenOA + 22 + 6 * 2, 2), 16);46: char first = (char)(byt >> 1);47:
48: return strPDU.Substring(lenSCA + lenOA + 22 + 4 * 2, 2 * 2)49: + strPDU.Substring(lenSCA + lenOA + 22 + 3 * 2, 2) + "," + ServiceCenterAddress + ","50: + OriginatorAddress + "," + ServiceCenterTimeStamp + "," + first + UserData;51: }
52: }
53:
54: userData = strPDU.Substring(lenSCA + lenOA + 22);
55: return "010100," + ServiceCenterAddress + "," + OriginatorAddress + "," + ServiceCenterTimeStamp + "," + UserData;56: }
这样,程序返回值字符串中有长短信的有关消息,前两个8位组 分别指示这批短信的总条数和这条所在的序号,调用方只需对这两个8位组和这批短信的唯一标识判断处理即可解码出长短信,拼接长短信。
注释掉:public void PDUDecoder(string strPDU, out string msgCenter, out string phone, out string msg, out string time)方法。对应对其的调用都改为对刚修改的函数的调用。
2 . GSMMODEM类更改:
接收短信,读取短信先读出刚才信息字符串(MMNN,中心号码,手机号码,发送时间,短信内容 MM这批短信总条数 NN本条所在序号)然后在处理(现在程序对此不做处理,需要的话 自己先改动)
发送短信:只需添加长短信的编码的发送,用foreach语句遍历发送各条PDU串即可:
1: /// <summary>2: /// 发送短信3: /// 发送失败将引发异常4: /// </summary>5: /// <param name="phone">手机号码</param>6: /// <param name="msg">短信内容</param>7: public void SendMsg(string phone, string msg)8: {
9: PDUEncoding pe = new PDUEncoding();10: pe.ServiceCenterAddress = msgCenter; //短信中心号码 服务中心地址11:
12: string temp = pe.PDUUSC2Encoder(phone, msg);13:
14: string tmp = string.Empty;15:
16: foreach (string str in temp.Split(','))17: {
18: int len = (str.Length - Convert.ToInt32(str.Substring(0, 2), 16) * 2 - 2) / 2; //计算长度19:
20: try21: {22: //注销事件关联,为发送做准备23: sp.DataReceived -= sp_DataReceived;24:
25: sp.Write("AT+CMGS=" + len.ToString() + "\r");26: sp.ReadTo(">");27: sp.DiscardInBuffer();
28:
29: //事件重新绑定 正常监视串口数据30: sp.DataReceived += sp_DataReceived;31:
32: tmp = SendAT(str + (char)(26)); //26 Ctrl+Z ascii码33: }34: catch (Exception)35: {
36: throw new Exception("短信发送失败");37: }
38: finally39: {40: }
41:
42: if (tmp.Substring(tmp.Length - 4, 3).Trim() == "OK")43: {
44: continue;45: }
46:
47: throw new Exception("短信发送失败");48: }
49: }
50:
51: /// <summary>52: /// 发送短信 (重载)53: /// </summary>54: /// <param name="phone">手机号码</param>55: /// <param name="msg">短信内容</param>56: /// <param name="msgType">短信类型</param>57: public void SendMsg(string phone, string msg, MsgType msgType)58: {
59: if (msgType == MsgType.AUSC2)60: {
61: SendMsg(phone, msg);
62: }
63: else64: {65:
66: PDUEncoding pe = new PDUEncoding();67: pe.ServiceCenterAddress = msgCenter; //短信中心号码 服务中心地址68:
69: string temp = pe.PDU7BitEncoder(phone, msg);70:
71: string tmp = string.Empty;72:
73: foreach (string str in temp.Split(','))74: {
75:
76: int len = (str.Length - Convert.ToInt32(str.Substring(0, 2), 16) * 2 - 2) / 2; //计算长度77: try78: {79: tmp = SendAT("AT+CMGS=" + len.ToString() + "\r" + str + (char)(26)); //26 Ctrl+Z ascii码80: }81: catch (Exception)82: {
83: throw new Exception("短信发送失败");84: }
85:
86: if (tmp.Substring(tmp.Length - 4, 3).Trim() == "OK")87: {
88: continue;89: }
90:
91: throw new Exception("短信发送失败");92: }
93: }
94: }
直接调用函数即可发送,长度超过最大字符数,自动以长短信发送;调用方式和以前完全一样。
读取短信的函数直接调用解码函数,返回格式同解码函数,调用时需要根据字符串自己组合短信,函数没有太大变化,这里不再给出具体函数了,详细参考附件源程序。
- 总结:长短信的发送就是把超过协议最大长度的短信分成多条发送,在接收终端(如手机)端看到的是一条短信。置TP-udhi位为1,添加消息头;USC2的编码只需添加消息头,剩下的134个字节可以发送67个字符,7位短信需要加上填充位 6byte消息头占48位,需添加一位填充(0或1)填充位置在本字节的最低位,我的程序把字节左移一位(相当于填充0);接收解码时只需右移一位即可。
附件:工程项目文件