短信猫软件的实现(C#)<十三>超长短信
2011-03-11 10:26
375 查看
超长短信:长度超过一条,而分多条发送的短信,通过用户数据头标识在接收端进行组合的短信(接收的短信在手机或其他终端上看到的是一条)。GSM_03.40规范中是ConcatenatedShortMessages:Thisfacilityallowsshortmessagestobeconcatenatedtoformalongermessage.
此种短信理论上最长可以将255条短信合成一条,名副其实的超长短信。
有关超长短信可以参考GSM_03.40规范和CMPP有关超长短信的内容:GSM_03.40规范中的9.2.3.23TP-User-Data-Header-Indicator(TP-UDHI)和9.2.3.24TP-UserData(TP-UD)
本文的程序是在原来基础上添加的,详细请参考:
PDU字符串中与超长短信有关的只有TP-UDHI位(在PDU字串中的PDUType的D6位),有关PDU编码请参考:
消息头是UserData的开头部分,有两种格式:6位格式和7位格式。6位:050003XXMMNN;7位格式:060804XXXXMMNN。
各字节含义:
byte1:剩余协议头长度。
byte2:00/08这个字节在GSM03.40规范9.2.3.24中规定,00:代表长短信,8位参考标识;08:代表长短信,16位参考标识;还规定了其他数值,与长短信无关,详细参考GSM03.40规范9.2.3.24。
byte3:代表剩下短信标识的长度:03,三个字节;04,四个字节。
byte4:XX这批短信的唯一标志,事实上,SME(手机或者SP)把消息合并完之后,就重新记录,所以这个标志是否唯一并不是很重要。7位格式的和byte5一起作为16位标志。
byte5:MM这批短信的数量,超长短信分成几条,值即是几。7位XX和byte4共同作为16位标识。
byte6:NN本条短信在超长短信中是第几条。7位格式MM同6位格式的MM。
byte7:NN7位格式中,同6位格式中的NN。
长短信消息头规律:第一个字节:消息头剩余长度;第二字节:消息类型;第三字节:剩余消息头长度;后面一个或两个字节根据标识位数作为这批短信的唯一标识,是否唯一不重要,但同批短信标志位必须相同,否则将被解析成多条短信。后面两个字节分别是总数量和序号。
编码实现:
此次编码是在之前编码基础上添加长短信编解码部分而实现的,添加时不对原来程序做过多修改;这次添加长短信深感这个类库的可扩展性太差,以致程序有点乱,添加长短信费了一番功夫,而且功能实现不尽合理;由于这段时间比较忙,暂时不对程序做大的改动,仅仅添加长短信编码部分。
对编解码类的更改:
属性更改:
长短信发送时需将TP-UDHI位置为1,而这位位于PDU-type这个8位组,普通短信这个八位组发送时值为“11”接收时为“24”,长短信分别为:“51”、“64”。之前程序对应的属性只能读到“11”,字段值也为“11”没有更改。为使其支持长短信编解码将其中属性、字段更改为:
privatestringprotocolDataUnitType="11";
///<summary>
///协议数据单元类型(1个8位组)
///</summary>
publicstringProtocolDataUnitType
{
set
{
protocolDataUnitType=value;
}
get
{
returnprotocolDataUnitType;
}
}
这样编解码时只需正确设置属性值,即可完成长短信的编解码。
方法更改:
编码:(USC2/7位):
只需把原来程序字符数超过最大字符数时抛出异常改为对应长短信编码即可;为了改动的地方比较少,返回值:长短信返回逗号分隔的PDU串。7bit编码须做一定处理,规范中要求添加填充位,让后面userData符合7bit的格式;6byte消息头共占48bit填充一位补成49bit,相当于后面第一个ASCII符做一定特殊处理,后面直接调用之前的编码函数即可,通过验证发现第一个只需左移一位,即完成这一位编码,放入PDU传即可。
///<summary>
///PDU编码器,完成PDU编码(USC2编码,超过70个字时分多条发送,PDU各个串之间逗号分隔)
///</summary>
///<paramname="phone">目的手机号码</param>
///<paramname="Text">短信内容</param>
///<returns>编码后的PDU字符串长短信时逗号分隔</returns>
publicstringPDUUSC2Encoder(stringphone,stringText)
{
DestinationAddress=phone;
if(Text.Length>70)
{
//长短信设TP-UDHI位为1PDU-type=“51”
ProtocolDataUnitType="51";
//计算长短信条数
intcount=Text.Length/67+1;
//长短信格式字符串,格式每条之间逗号分隔
stringresult=string.Empty;
for(inti=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(',');
}
}
}
returnresult;
}
//不是长短信
UserData=Text;
returnserviceCenterAddress+protocolDataUnitType
+messageReference+destinationAddress+protocolIdentifer
+dataCodingScheme+validityPeriod+userDataLenghth+userData;
}
///<summary>
///7bit编码(超过160个字时分多条发送,PDU各个串之间逗号分隔)
///</summary>
///<paramname="phone">手机号码</param>
///<paramname="Text">短信内容</param>
///<returns>编码后的字符串长短信时逗号分隔</returns>
publicstringPDU7BitEncoder(stringphone,stringText)
{
dataCodingScheme="00";
DestinationAddress=phone;
if(Text.Length>160)
{
//长短信设TP-UDHI位为1PDU-type=“51”
ProtocolDataUnitType="51";
//计算长短信条数
intcount=Text.Length/153+1;
//长短信格式字符串,格式每条之间逗号分隔
stringresult=string.Empty;
for(inti=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)(newASCIIEncoding().GetBytes(Text.Substring(i*153,1))[0]<<1)).ToString("X2")+userData+",";
}
else
{
UserData=Text.Substring(i*153+1);
intlen=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)(newASCIIEncoding().GetBytes(Text.Substring(i*153,1))[0]<<1)).ToString("X2")
+userData;
}
else
{
result=result.TrimEnd(',');
}
}
}
returnresult;
}
UserData=Text;
returnserviceCenterAddress+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>
///<paramname="strPDU">短信PDU字符串</param>
///<returns>信息字符串(MMNN,中心号码,手机号码,发送时间,短信内容MM这批短信总条数NN本条所在序号)</returns>
publicstringPDUDecoder(stringstrPDU)
{
intlenSCA=Convert.ToInt32(strPDU.Substring(0,2),16)*2+2;//短消息中心占长度
serviceCenterAddress=strPDU.Substring(0,lenSCA);
//PDU-type位组
protocolDataUnitType=strPDU.Substring(lenSCA,2);
intlenOA=Convert.ToInt32(strPDU.Substring(lenSCA+2,2),16);//OA占用长度
if(lenOA%2==1)//奇数则加1F位
{
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);
intlenUD=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);
returnstrPDU.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
//首字节译码
bytebyt=Convert.ToByte(strPDU.Substring(lenSCA+lenOA+22+6*2,2),16);
charfirst=(char)(byt>>1);
returnstrPDU.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位组和这批短信的唯一标识判断处理即可解码出长短信,拼接长短信。
注释掉:publicvoidPDUDecoder(stringstrPDU,outstringmsgCenter,outstringphone,outstringmsg,outstringtime)方法。对应对其的调用都改为对刚修改的函数的调用。
2.GSMMODEM类更改:
接收短信,读取短信先读出刚才信息字符串(MMNN,中心号码,手机号码,发送时间,短信内容MM这批短信总条数NN本条所在序号)然后在处理(现在程序对此不做处理,需要的话自己先改动)
发送短信:只需添加长短信的编码的发送,用foreach语句遍历发送各条PDU串即可:
///<summary>
///发送短信
///发送失败将引发异常
///</summary>
///<paramname="phone">手机号码</param>
///<paramname="msg">短信内容</param>
publicvoidSendMsg(stringphone,stringmsg)
{
PDUEncodingpe=newPDUEncoding();
pe.ServiceCenterAddress=msgCenter;//短信中心号码服务中心地址
stringtemp=pe.PDUUSC2Encoder(phone,msg);
stringtmp=string.Empty;
foreach(stringstrintemp.Split(','))
{
intlen=(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));//26Ctrl+Zascii码
}
catch(Exception)
{
thrownewException("短信发送失败");
}
finally
{
}
if(tmp.Substring(tmp.Length-4,3).Trim()=="OK")
{
continue;
}
thrownewException("短信发送失败");
}
}
///<summary>
///发送短信(重载)
///</summary>
///<paramname="phone">手机号码</param>
///<paramname="msg">短信内容</param>
///<paramname="msgType">短信类型</param>
publicvoidSendMsg(stringphone,stringmsg,MsgTypemsgType)
{
if(msgType==MsgType.AUSC2)
{
SendMsg(phone,msg);
}
else
{
PDUEncodingpe=newPDUEncoding();
pe.ServiceCenterAddress=msgCenter;//短信中心号码服务中心地址
stringtemp=pe.PDU7BitEncoder(phone,msg);
stringtmp=string.Empty;
foreach(stringstrintemp.Split(','))
{
intlen=(str.Length-Convert.ToInt32(str.Substring(0,2),16)*2-2)/2;//计算长度
try
{
tmp=SendAT("AT+CMGS="+len.ToString()+"\r"+str+(char)(26));//26Ctrl+Zascii码
}
catch(Exception)
{
thrownewException("短信发送失败");
}
if(tmp.Substring(tmp.Length-4,3).Trim()=="OK")
{
continue;
}
thrownewException("短信发送失败");
}
}
}
直接调用函数即可发送,长度超过最大字符数,自动以长短信发送;调用方式和以前完全一样。
读取短信的函数直接调用解码函数,返回格式同解码函数,调用时需要根据字符串自己组合短信,函数没有太大变化,这里不再给出具体函数了,详细参考附件源程序。
总结:长短信的发送就是把超过协议最大长度的短信分成多条发送,在接收终端(如手机)端看到的是一条短信。置TP-udhi位为1,添加消息头;USC2的编码只需添加消息头,剩下的134个字节可以发送67个字符,7位短信需要加上填充位6byte消息头占48位,需添加一位填充(0或1)填充位置在本字节的最低位,我的程序把字节左移一位(相当于填充0);接收解码时只需右移一位即可。
附件: