短信猫软件的实现(C#)<十三>超长短信

超长短信:长度超过一条,而分多条发送的短信,通过用户数据头标识在接收端进行组合的短信(接收的短信在手机或其他终端上看到的是一条)。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。

长短信消息头规律:第一个字节:消息头剩余长度;第二字节:消息类型;第三字节:剩余消息头长度;后面一个或两个字节根据标识位数作为这批短信的唯一标识,是否唯一不重要,但同批短信标志位必须相同,否则将被解析成多条短信。后面两个字节分别是总数量和序号。

  • 编码实现:

此次编码是在之前编码基础上添加长短信编解码部分而实现的,添加时不对原来程序做过多修改;这次添加长短信深感这个类库的可扩展性太差,以致程序有点乱,添加长短信费了一番功夫,而且功能实现不尽合理;由于这段时间比较忙,暂时不对程序做大的改动,仅仅添加长短信编码部分。

  1. 对编解码类的更改:

属性更改:

长短信发送时需将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 ProtocolDataUnitType
 6:  {
 7:      set
 8:      {
 9:          protocolDataUnitType = value;
10:      }
11:      get
12:      {
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:              else
 35:              {
 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:                  else
 47:                  {
 48:                      result = result.TrimEnd(',');
 49:                  }
 50:              }
 51:          }
 52:   
 53:          return result;
 54:      }
 55:   
 56:      //不是长短信
 57:      UserData = Text;
 58:      return serviceCenterAddress + protocolDataUnitType
 59:          + 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:              else
 99:              {
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:                  else
115:                  {
116:                      result = result.TrimEnd(',');
117:                  }
118:              }
119:          }
120:   
121:          return result;
122:      }
123:   
124:      UserData = Text;
125:   
126:      return serviceCenterAddress + protocolDataUnitType
127:          + 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赋值,区分解码7bit
23:  
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:          else
41:          {
42:              userData = strPDU.Substring(lenSCA + lenOA + 22 + 6 * 2 + 1 * 2);   //消息头六字节,第一字节特殊译码 >>7
43:  
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:          try
21:          {
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:          finally
39:          {
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:      else
64:      {
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:              try
78:              {
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);接收解码时只需右移一位即可。

附件:工程项目文件

posted @ 2011-03-11 10:26  给我一杯酒  阅读(9975)  评论(31编辑  收藏  举报