c#实现GSM短信设备超长短信

分享

超长短信原理:长度超过一条,而分多条发送的短信,通过用户数据头标识在接收端进行组合的短信(接收的短信在手机或其他终端上看到的是一条)。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”没有更改。为使其支持长短信编解码将其中属性、字段更改为:

    •    private string protocolDataUnitType = "11";
         /// <summary>
         /// 协议数据单元类型(1个8位组)
         /// </summary>
         public string ProtocolDataUnitType
         {
            set
             {
                 protocolDataUnitType = value;
            }
            get
            {
                return protocolDataUnitType;
            }
        }

      这样编解码时只需正确设置属性值,即可完成长短信的编解码。

      方法更改:

      编码:(USC2/7位):


      只需把原来程序 字符数超过最大字符数时 抛出异常改为 对应长短信编码即可;为了改动的地方比较少,返回值:长短信返回逗号分隔的PDU串。7bit编码须做一定处理,规范中要求添加填充位,让后面userData符合7bit的格式;6byte消息头共占48bit 填充一位补成49bit,相当于后面第一个ASCII符做一定特殊处理,后面直接调用之前的编码函数即可,通过验证发现 第一个只需左移一位,即完成这一位编码,放入PDU传即可。


    •   /// <summary>
          /// PDU编码器,完成PDU编码(USC2编码,超过70个字时 分多条发送,PDU各个串之间逗号分隔)
          /// </summary>
          /// <param name="phone">目的手机号码</param>
         /// <param name="Text">短信内容</param>
          /// <returns>编码后的PDU字符串 长短信时 逗号分隔</returns>
          public string PDUUSC2Encoder(string phone, string Text)
          {
              DestinationAddress = phone;
          
             if (Text.Length > 70)
             {
                 //长短信设TP-UDHI位为1 PDU-type = “51”
                 ProtocolDataUnitType = "51";
          
                 //计算长短信条数
                 int count = Text.Length / 67 + 1;
          
                 //长短信格式字符串,格式 每条之间 逗号分隔
                 string result = string.Empty;       
          
                 for (int i = 0; i < count; i++)
                 {
                     //如果不是最后一条
                     if (i != count - 1)
                     {
                         UserData = Text.Substring(i * 67, 67);
          
                         result += serviceCenterAddress + protocolDataUnitType
                             + messageReference + destinationAddress + protocolIdentifer
                              + dataCodingScheme + validityPeriod + (userData.Length / 2 + 6).ToString("X2")
                              + "05000339" + count.ToString("X2") + (i + 1).ToString("X2") + userData + ",";
                     }
                     else
                     {
                         UserData = Text.Substring(i * 67);
          
                         if (userData != null || userData.Length != 0)
                         {
          
                             result += serviceCenterAddress + protocolDataUnitType
                                 + messageReference + destinationAddress + protocolIdentifer
                                  + dataCodingScheme + validityPeriod + (userData.Length / 2 + 6).ToString("X2")
                                  + "05000339" + count.ToString("X2") + (i + 1).ToString("X2") + userData;
                         }
                         else
                         {
                             result = result.TrimEnd(',');
                         }
                     }
                 }
          
                 return result;
             }
          
             //不是长短信
             UserData = Text;
             return serviceCenterAddress + protocolDataUnitType
                 + messageReference + destinationAddress + protocolIdentifer
                 + dataCodingScheme + validityPeriod + userDataLenghth + userData;
         }
         
         /// <summary>
         /// 7bit 编码(超过160个字时 分多条发送,PDU各个串之间逗号分隔)
         /// </summary>
         /// <param name="phone">手机号码</param>
         /// <param name="Text">短信内容</param>
         /// <returns>编码后的字符串 长短信时 逗号分隔</returns>
         public string PDU7BitEncoder(string phone, string Text)
         {
             dataCodingScheme = "00";
             DestinationAddress = phone;
          
             if (Text.Length > 160)
            {
                 //长短信设TP-UDHI位为1 PDU-type = “51”
                 ProtocolDataUnitType = "51";
          
                 //计算长短信条数
                 int count = Text.Length / 153 + 1;
          
                 //长短信格式字符串,格式 每条之间 逗号分隔
                 string result = string.Empty;       
          
                 for (int i = 0; i < count; i++)
                 {
                     //如果不是最后一条
                     if (i != count - 1)
                     {
                         UserData = Text.Substring(i * 153 + 1, 152);
          
                         result += serviceCenterAddress + protocolDataUnitType
                             + messageReference + destinationAddress + protocolIdentifer
                              + dataCodingScheme + validityPeriod + (160).ToString("X2")
                              + "05000339" + count.ToString("X2") + (i + 1).ToString("X2") 
                              +((int)(new ASCIIEncoding().GetBytes(Text.Substring(i*153,1))[0]<<1)).ToString("X2") + userData + ",";
                     }
                     else
                     {
                       UserData = Text.Substring(i * 153+1);
         
                        int len = Text.Substring(i * 153).Length;
         
                        if (userData != null || userData.Length != 0)
                        {
         
                            result += serviceCenterAddress + protocolDataUnitType
                                + messageReference + destinationAddress + protocolIdentifer
                                + dataCodingScheme + validityPeriod + (len + 7).ToString("X2")
                                 + "05000339" + count.ToString("X2") + (i + 1).ToString("X2")
                                 + ((int)(new ASCIIEncoding().GetBytes(Text.Substring(i * 153, 1))[0] << 1)).ToString("X2")
                                 + userData;
                         }
                        else
                        {
                            result = result.TrimEnd(',');
                        }
                    }
                }
         
                return result;
            }
         
           UserData = Text;
         
            return serviceCenterAddress + protocolDataUnitType
               + messageReference + destinationAddress + protocolIdentifer
                + dataCodingScheme + validityPeriod + userDataLenghth + userData;
        }

      这样,调用时 非长短信调用方式不变 长短信 返回值为逗号分隔的各PDU串、很方便调用方更改。

      解码(USC2/7位):

      解码函数只需添加对TP-UDHI位判断,为1则根据消息头解出本条短信在本批次短信中的位置 及本批次短信的总条数。

      PDUDecoder完成PDU解码,PDU7bitDecoder仅完成7位PDU的用户数据编码,供UserData属性调用,不需改动;只需更改PDUDecoder即完成长短信编码:根据TP-udhi位取出消息头,消息体赋给userData即可正常解码。方法返回格式改为:MMNNXX,中心号码,手机号码,发送时间,短信内容 MM这批短信总条数 NN本条所在序号 XX为这条短信的唯一标识;


    •    /// <summary>
         /// 重载 解码,返回信息字符串 格式 
         /// </summary>
         /// <param name="strPDU">短信PDU字符串</param>
         /// <returns>信息字符串(MMNN,中心号码,手机号码,发送时间,短信内容 MM这批短信总条数 NN本条所在序号)</returns>
         public string PDUDecoder(string strPDU)
         {
             int lenSCA = Convert.ToInt32(strPDU.Substring(0, 2), 16) * 2 + 2;       //短消息中心占长度
             serviceCenterAddress = strPDU.Substring(0, lenSCA);
         
            //PDU-type位组
            protocolDataUnitType = strPDU.Substring(lenSCA, 2);
         
            int lenOA = Convert.ToInt32(strPDU.Substring(lenSCA + 2, 2), 16);           //OA占用长度
            if (lenOA % 2 == 1)                                                     //奇数则加1 F位
            {
                lenOA++;
            }
            lenOA += 4;                 //加号码编码的头部长度
            originatorAddress = strPDU.Substring(lenSCA + 2, lenOA);
         
           dataCodingScheme = strPDU.Substring(lenSCA + lenOA + 4, 2);             //DCS赋值,区分解码7bit
        
            serviceCenterTimeStamp = strPDU.Substring(lenSCA + lenOA + 6, 14);
         
            userDataLenghth = strPDU.Substring(lenSCA + lenOA + 20, 2);
            int lenUD = Convert.ToInt32(userDataLenghth, 16) * 2;
         
           if (protocolDataUnitType != "24")
            {
                if (dataCodingScheme == "08" || dataCodingScheme == "18")           //USC2 长短信 去掉消息头
                {
                    userDataLenghth = (Convert.ToInt16(strPDU.Substring(lenSCA + lenOA + 20, 2), 16) - 6).ToString("X2");
                    userData = strPDU.Substring(lenSCA + lenOA + 22 + 6 * 2);
        
                    return strPDU.Substring(lenSCA + lenOA + 22 + 4 * 2, 2 * 2)
                       + strPDU.Substring(lenSCA + lenOA + 22 + 3 * 2, 2) + "," + ServiceCenterAddress + ","
                        + OriginatorAddress + "," + ServiceCenterTimeStamp + "," + UserData;
                }
                else
                {
                    userData = strPDU.Substring(lenSCA + lenOA + 22 + 6 * 2 + 1 * 2);   //消息头六字节,第一字节特殊译码 >>7
      
                    //首字节译码 
                   byte byt = Convert.ToByte(strPDU.Substring(lenSCA + lenOA + 22 + 6 * 2, 2), 16);
                    char first = (char)(byt >> 1);
        
                   return strPDU.Substring(lenSCA + lenOA + 22 + 4 * 2, 2 * 2)
                        + strPDU.Substring(lenSCA + lenOA + 22 + 3 * 2, 2) + "," + ServiceCenterAddress + ","
                        + OriginatorAddress + "," + ServiceCenterTimeStamp + "," + first + UserData;
                }
            }
         
            userData = strPDU.Substring(lenSCA + lenOA + 22);
            return "010100," + ServiceCenterAddress + "," + OriginatorAddress + "," + ServiceCenterTimeStamp + "," + UserData;
        }

      这样,程序返回值字符串中有长短信的有关消息,前两个8位组 分别指示这批短信的总条数和这条所在的序号,调用方只需对这两个8位组和这批短信的唯一标识判断处理即可解码出长短信,拼接长短信。

      注释掉:public void PDUDecoder(string strPDU, out string msgCenter, out string phone, out string msg, out string time)方法。对应对其的调用都改为对刚修改的函数的调用。

    • GSMMODEM类更改:

      接收短信,读取短信先读出刚才信息字符串(MMNN,中心号码,手机号码,发送时间,短信内容 MM这批短信总条数 NN本条所在序号)然后在处理(现在程序对此不做处理,需要的话 自己先改动)

      发送短信:只需添加长短信的编码的发送,用foreach语句遍历发送各条PDU串即可:


    •    /// <summary>
         /// 发送短信
         /// 发送失败将引发异常
         /// </summary>
         /// <param name="phone">手机号码</param>
         /// <param name="msg">短信内容</param>
         public void SendMsg(string phone, string msg)
         {
             PDUEncoding pe = new PDUEncoding();
            pe.ServiceCenterAddress = msgCenter;                    //短信中心号码 服务中心地址
        
            string temp = pe.PDUUSC2Encoder(phone, msg);
         
            string tmp = string.Empty;
         
            foreach (string str in temp.Split(','))
            {
                int len = (str.Length - Convert.ToInt32(str.Substring(0, 2), 16) * 2 - 2) / 2;  //计算长度
        
               try
                {
                    //注销事件关联,为发送做准备
                    sp.DataReceived -= sp_DataReceived;
         
                    sp.Write("AT+CMGS=" + len.ToString() + "\r");
                    sp.ReadTo(">");
                    sp.DiscardInBuffer();
         
                   //事件重新绑定 正常监视串口数据
                   sp.DataReceived += sp_DataReceived;
         
                    tmp = SendAT(str + (char)(26));  //26 Ctrl+Z ascii码
                }
                catch (Exception)
                {
                    throw new Exception("短信发送失败");
                }
                finally
                {
                }
         
                if (tmp.Substring(tmp.Length - 4, 3).Trim() == "OK")
                {
                    continue;
                }
         
                throw new Exception("短信发送失败");
            }
        }
         
        /// <summary>
        /// 发送短信 (重载)
        /// </summary>
        /// <param name="phone">手机号码</param>
        /// <param name="msg">短信内容</param>
        /// <param name="msgType">短信类型</param>
        public void SendMsg(string phone, string msg, MsgType msgType)
        {
            if (msgType == MsgType.AUSC2)
            {
                SendMsg(phone, msg);
            }
            else
            {
         
                PDUEncoding pe = new PDUEncoding();
                pe.ServiceCenterAddress = msgCenter;                    //短信中心号码 服务中心地址
        
                string temp = pe.PDU7BitEncoder(phone, msg);
        
               string tmp = string.Empty;
         
                foreach (string str in temp.Split(','))
               {
         
                   int len = (str.Length - Convert.ToInt32(str.Substring(0, 2), 16) * 2 - 2) / 2;  //计算长度
                    try
                    {
                       tmp = SendAT("AT+CMGS=" + len.ToString() + "\r" + str + (char)(26));  //26 Ctrl+Z ascii码
                    }
                    catch (Exception)
                    {
                       throw new Exception("短信发送失败");
                    }
        
                    if (tmp.Substring(tmp.Length - 4, 3).Trim() == "OK")
                    {
                       continue;
                    }
         
                   throw new Exception("短信发送失败");
                }
            }
        }

      直接调用函数即可发送,长度超过最大字符数,自动以长短信发送;调用方式和以前完全一样。

      读取短信的函数直接调用解码函数,返回格式同解码函数,调用时需要根据字符串自己组合短信,函数没有太大变化,这里不再给出具体函数了,详细参考附件源程序。

    • 总结:长短信的发送就是把超过协议最大长度的短信分成多条发送,在接收终端(如手机)端看到的是一条短信。置TP-udhi位为1,添加消息头;USC2的编码只需添加消息头,剩下的134个字节可以发送67个字符,7位短信需要加上填充位 6byte消息头占48位,需添加一位填充(0或1)填充位置在本字节的最低位,我的程序把字节左移一位(相当于填充0);接收解码时只需右移一位即可。

    附件:

    GSMMODEM(修改3).rar


短信设备二次开发 2022-04-05 0 0

admin

  • 注册时间 : 2022-03-30 04:08:47
  • 邮箱地址 : admin@tenghengkeji.com
  • 此页面用于展示用户的基本上资料信息

回帖 ( 0 )