查看: 7282|回复: 0
打印 上一主题 下一主题

短信收发设备c#实现超长短信

[复制链接]
跳转到指定楼层
楼主
发表于 2014-3-14 12:36:27 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
超长短信原理:长度超过一条,而分多条发送的短信,通过用户数据头标识在接收端进行组合的短信(接收的短信在手机或其他终端上看到的是一条)。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 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.              byte byt = Convert.ToByte(strPDU.Substring(lenSCA + lenOA + 22 + 6 * 2, 2), 16);
      45.               char first = (char)(byt >> 1);
      46.   
      47.              return strPDU.Substring(lenSCA + lenOA + 22 + 4 * 2, 2 * 2)
      48.                   + strPDU.Substring(lenSCA + lenOA + 22 + 3 * 2, 2) + "," + ServiceCenterAddress + ","
      49.                   + OriginatorAddress + "," + ServiceCenterTimeStamp + "," + first + UserData;
      50.           }
      51.       }
      52.    
      53.       userData = strPDU.Substring(lenSCA + lenOA + 22);
      54.       return "010100," + ServiceCenterAddress + "," + OriginatorAddress + "," + ServiceCenterTimeStamp + "," + UserData;
      55.   }
      复制代码

      这样,程序返回值字符串中有长短信的有关消息,前两个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串即可:
      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);接收解码时只需右移一位即可。

附件: GSMMODEM(修改3).rar (301.49 KB, 下载次数: 2515)
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏 分享分享
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|小黑屋|未来时代科技 ( 粤ICP备12044031号-1

GMT+8, 2024-4-29 04:39 , Processed in 0.073191 second(s), 33 queries .

Powered by WLSD X3.1

© 2013-2014 WLSD Inc.

快速回复 返回顶部 返回列表
 
【电话】(15118131494)
【QQ】 未来时代科技01 售前咨询
【QQ】 未来时代科技02 售后技术
【旺旺】 请问有什么可以帮到您?不在线可留言.
【邮箱】
inextera@sina.com
【地址】 (深圳市龙岗坂田扬马小区)