内容简介:本文介绍了CMPP2.0协议SP端.net实现需要注意的问题,并提供解决方案和参考意见,对CMPP协议做一个解读参考。 关键字:CMPP 2.0 SMS ISMG Socket 线程 线程同步 .Net事件模型 一、CMPP协议简介 中国移动通信互联网短信网关接口协议(China Mobile Peer to Peer CMPP),是中国移动梦网内部各SMS参与节点相互交换SMS的官方协议。作为梦网的参与方,移动梦网的增值服务商(Service Provider SP )要按照此协议规范实现SP的部分,才可以将自己的短信通过移动的GSM网络的数据通道传输到最终手机用户上。 实际上,协议规范了3个方面的内容: 。SP与移动的互联网短信网关(Internet Short Message Gateway,ISMG)之间的接口协议 。ISMG之间的接口协议(譬如移动各省、市之间的短信息交换通过ISMG之间进行) 。ISMG与汇接网关(Gateway Name Server GNS,类似互联网上的DNS服务器)之间的接口协议,譬如跨省之类的短信需要GNS的帮助指出当前ISMG该如何传递短信。 其中,后二方面属于移动短信息系统内部实现,对于SP来讲大概可以“透明”来看待,只要实现了SP同ISMG的正确交互,就可以实现接入移动梦网短信系统。我们关心的只是SP端的开发细节。 二、CMPP交互模式 从手机用户角度讲,按短信的发起/接收路径来讲,有两个叫法: MT(Short Message Mobile Terminated, SMMT),短信接收,短信从SP发送到手机用户。 MO (Short Message Mobile Originate,SMMO),短信发送,短信从手机用户端发送到目标SP。 这两类短信交互,从SP端来看,都是属于Socket传输应用,CMPP的协议是以TCP/IP协议作为底层承载协议的,属于TCP/IP协议栈之上的应用。 SP同ISMG的交互连接分长连接和短连接。 所谓短连接,就是一次连接,传输一个消息,然后等待回复后拆除连接,显然,效率很低,所以,基本上不被考虑(实际应用移动也不允许SP采用短连接,只是不明白移动为什么还要写入文档? ISMG间会需要?) 所谓长连接,就是SP建立同ISMG连接,然后不断将数据包(一个个CMPP消息)发送到ISMG,此处发送不必等待某条消息的ISMG回应消息返回,就接着发送下一个消息。同时,等待ISMG返回信息或者等待ISMG发送给SP的消息。发送同接收消息不是一定要同步的,实际采用异步(同时也时双工)模式。从效率上,显然,必须全双工的异步模式才能够满足实际应用需求。 如下图(摘自CMPP2.0官方文档)所示,演示了长连接模式数据传输过程: 三、SP端开发 1. 消息分类 首先,图中的CMPP消息有很多种,SP同ISMG之间交流这些消息。大体上这些消息发出后,对方往往需要回复一个应答(RESP)类消息。注意,这些消息大多具有方向性,也就是说只能够从一端到另一端,而不可反方向进行,有些(少数)则可两端都能够发出。以下信息主要来源于移动的文档,但针对大家易混淆或源文档解释不够详细做了明确和补充。具体见下表:
2. 交互阶段 整个CMPP协议交互分为验证、事务两个阶段。验证阶段,发送CMPP_CONNECTION消息进行验证,通过验证后(必须要通过才)进入CMPP事务阶段,可以发送短信数据了。上表中的CMPP_CONNECTION以下的消息都属于事务阶段的消息。 3. 消息数据结构 每一个消息包含 消息头 和 消息体两个部分,头固定长度为12字节,其他消息长度各异,但是同一类型消息的长度是固定的。所有消息的各个字段基本上仅有3种类型:Unsigned Integer (无符号整型) 、Integer(整型)、Octet String(字符串),每种类型具体长度不定,网络字节顺序。 1、 消息头(3个Unsigned Integer字段组成): 4字节的Total_Length (Unsigned Integer),包含了此消息的总计(包括了头部分)长度。 4字节的Command_Id(Unsigned Integer),指明了此消息到底是什么消息,就是上表中消息的枚举值。应用程序根据此值确定本数据包到底是什么消息,从而可以按照确定的消息类型,解析余下的消息体。 4字节的Sequence_Id(Unsigned Integer),指明了此数据包在发送此消息端的唯一编号。这个唯一编号,实际上可以看作流水操作编号。因为分析到交互模式我们看到,SP发送数据到ISMG,不是每发送一个就停下来等待ISMG的回复,而是“一下子”发送多个数据包过去,然后等待ISMG的回应。然而,怎么知道回应的消息是到底对应之前发送过去的消息中的那一条呢?本字段就是解决此难题。SP按照编号发送消息过去,等待ISMG的回应—一般情形下回应消息数据结构都有表明本消息回应的是SP发出的哪一条消息,这个对应就是依靠Sequence_Id。它并不要求一定要严格唯一,但是在给定的一段时间内,必须唯一(基本上只要SP发送过去的消息中没有重复就行了)。如果是需要SP回答的消息,SP也必须将ISMG发送过来的消息的Sequence_Id填入相应字段,表明这是某个消息的回应。SP端和ISMG端Sequence_ID都没有确定具体的算法。SP可以(但不推荐)采用数据库的唯一Id作为此值。 2、消息体。消息体长度根据消息不同,长度不一。其他的参考移动的文档《中国移动通信互联网短信网关接口协议(China Mobile Peer to Peer, CMPP)(V2.0)》,这里着重讲讲2个重要消息的消息体数据结构: CMPP_SUBMIT的消息体:
CMPP_SUBMIT消息长度是可变的,将SP端的消息发送给ISMG,ISMG将返回一个MSGID给SP标示此消息,之后(48小时以内,但一般最多几分钟内就可),ISMG返回关于此消息的递送报告。递送报告同MO短消息是通过另外一个重要消息CMPP_DELIVER来提交给SP的: CMPP_DELIVER的各个字段:
如果是报告,那么Msg_Content将按照状态报告结构来解释:
关于State字段,如下解释:
其他消息结构,具体说明见中移动的CMPP协议。 4. 安全验证 CMPP协议在CMPP_CONNECT中传递验证消息。验证消息为9字节的0+移动给出的密码+当前时间戳字节数组的MD5算法后的字节。时间戳为 月日时分秒,10位。代码算法如下: private byte[] getMd5Code() { byte[] buf=new byte[6+9+_Password.Length+10] ; byte[] s_a=Encoding.ASCII.GetBytes(_SystemID); //就是企业代码 byte[] s_0={0,0,0,0,0,0,0,0,0}; //9字节的0,此处当作右补0 byte[] s_p=Encoding.ASCII.GetBytes(_Password); //密码 this._timestamp =getTimestamp(); //取得认证码时赋值字符串 byte[] s_t=Encoding.ASCII.GetBytes(_timestamp); //10位字符串字节数组 s_a.CopyTo(buf,0); s_0.CopyTo(buf,6); s_p.CopyTo(buf,6+9); s_t.CopyTo(buf,6+9+_Password.Length); MD5 md5= new MD5CryptoServiceProvider(); //创建MD5类别 return(md5.ComputeHash(buf,0,buf.Length)); } 其中getTimestamp函数为返回例如“0710125959”(7月10号12点59分59秒)这样的字符串,详细代码略过,有兴趣请查看本文的附件代码。 5. 厂商API问题 笔者公司所处广东,广东移动提供了华为的以C 形式的API(SMEIDLL.dll),来帮助大家初期熟悉CMPP协议。但是,经过开发测试,发现华为的API至少存在几个问题: 1、 封装成几个API函数,但是由于CMPP自身的复杂性,导致这些函数丑陋无比,参数多,而且难以明晰含义。华为的API,内部将CMPP的验证、事务阶段分成几个函数实现,其中将发送SMS到ISMG功能以函数提供,竟然出现SubmitAExExEx之类的函数说明。 2、 CMPP的交互是异步的,需要多线程实现一边发送,一边接收反馈信息。此API应当是内部维护一个线程进行CMPP_SUBMIT消息发送,但是华为API却通过空循环之类的操作等待ISMG返回CMPP_SUBMIT_RESP得到相应的MSGID再返回(从而实现消息同步返回)。经过测试,大约需要200毫秒,这个在实际SP的高性能需求场合根本无法满足系统要求。 3、 接收短信必须依靠程序主动先发出函数HasDeliverMessage调用 ,得到有消息才可通过GetDeliverSMEx函数获取消息,显然,这种方式是低效率的,而且容易产生消息数据包丢失,表现为有些MO消息,SP接收不到。而且,令人疑惑的是,你还不能够新开一个线程专门来做判断并接收MO的动作,实际开发中一旦采用线程来做就回发生内存保护错误(大概属于同API自身的线程有冲突)。 4、 返回错误码,往往又是华为自己定的一套错误码(大概华为设计此API为了适应SMGP CMPP等多个协议),而且经常变动,很是伤脑筋。 基于以上理由,我认为自己按照CMPP协议开发一个SP端程序,比较能够满足一般SP的需求。 四、C#实现 1、CMPP协议实现类CMPPClient 通过研究,笔者用C#写了一组类实现自己的CMPP SP端程序(CMPPClient)。为了实现相关类,还需要编写一些辅助类,并且首先要解决CMPP协议的数据结构同C#的数据之间的转换问题。 CMPP的Octet String 实际上相当于C#中的byte[],所有CMPP消息的Octet String字段出了CMPP_SUBMIT和CMPP_DELIVER的msg_content字段外,其他的都可以认为是ASCII编码,所以全部可以采用System.Text.Encoding.ASCII进行编码和解码;对于Msg_Content字段,由于一般情况下存在汉字信息传输.,所以默认的编/解码应该为Encoding.Default,实际是什么编码还要考察MSG_Fmt字段指示正文到底是什么编码。 对于Unsigned Integer 和Interger字段,需要按照网络字节顺序和x86机器的字节编码顺序对照关系进行转换,具体我设计了一个工具类提一些转换方法使用: public class BIConvert //字节 整形 转换类 网络格式转换为内存格式 { public static byte[] Int2Bytes(uint i) //转换整形数据的网络次序字节数组 { byte[] t=BitConverter.GetBytes(i) ; byte b=t[0]; t[0]=t[3]; t[3]=b; b=t[1]; t[1]=t[2]; t[2]=b; return(t); }
public static uint Bytes2UInt(byte[] bs,int startIndex) //返回字节数组代表的整数数字,4个字节长度的数组 { byte[] t=new byte[4]; for(int i=0;i<4 && i< bs.Length-startIndex ;i++) { t[i]=bs[startIndex+i]; } byte b=t[0]; t[0]=t[3]; t[3]=b; b=t[1]; t[1]=t[2]; t[2]=b; return(BitConverter.ToUInt32(t,0)); }
public static uint Bytes2UInt(byte[] bs) //没有指定起始索引 { return( Bytes2UInt(bs,0)); } } 其次,为了实现收发数据的“全双工”,需要设计至少两个线程处理socket的读取和数据包写入。另外,为了自动实现对数据链路的保持,以及自动实现数据包重发机值,我还增加了一个值守线程,自动处理以上问题。详细见后代码。另外,消息中有很多同时间有关的字段,但是这些时间相关字段并非按照统一规格编码的,这个需要仔细研究协议或者实现代码。 其三,为了解析/编码数据包方便,我将SP端涉及到的消息以类的形式实现,根据具体的消息类型,将数据包字节解析还原为特定的消息;另一方面,当需要发送一些消息时,将消息的各个字段,根据类型和编码类型“组装”成字节数组,以便Socket能够发送出去。 其四,为了达到及时处理短消息的收发,我大量采用了C#事件触发来达到消息通知目的,而且,也设计一组事件参数,供事件的具体监听者可以掌握需要的信息。 其五,需要注意多线程间的同步问题。 其六,填写CMPP_SUBMIT消息需要注意内容编码、计费字段正确填写 2、事件模型 大体上实现了十多个事件,这些事件具体为: 当CMPP_DELIVER消息送来的是短消息送达报告时,发生消息送达报告事件: public delegate void ReportEventHandler(object sender, ReportEventArgs e); public delegate void SMSEventHandler(object sender, SMSEventArgs e); public delegate void TerminateEventHandler(object sender,TerminateEventArgs e); public delegate void TerminateRespEventHandler(object sender,TerminateRespEventArgs e); 以下两个事件针对链路保持消息CMPP_ACTIVE_TEST及CMPP_ACTIVE_TEST_RESP发生: public delegate void TestEventHandler(object sender,TestEventArgs e); public delegate void TestRespEventHandler(object sender,TestRespEventArgs e); public delegate void ConnectRespEventHandler(object sender,ConnectRespEventArgs e); SP取消某条端消息,发出CMPP_CANCEL后,ISMG响应此消息返回CMPP_CANCEL_RESP消息时,激活事件: public delegate void CancelRespEventHandler(object sender,CancelRespEventArgs e); public delegate void SubmitRespEventHandler(object sender,SubmitRespEventArgs e); 查询ISMG返回消息后,发生: public delegate void QueryRespEventHandler(object sender,QueryRespEventArgs e); public delegate void LogonSuccEventHandler(object sender,EventArgs e); //当成功登录系统 以下事件,不是基于CMPP消息,而是根据SP同ISMG消息队列扫描后判断触发事件: public delegate void SocketClosedEventHandler(object sender,EventArgs e); //当套接字被检测到关闭 public delegate void FailedItemDeletedEventHandler(object sender,WaitingQueueItemEventArgs e); //当一条存在于等待队列的消息超过60秒没有回应 以上这些事件缺省实现保证了SP端CMPP客户对于ISMG的响应自动化,提供触发事件保证调用此客户端类的系统可以通过事件发生准确的控制SP端的内部状态,获取交互信息。 另一方面,由于这些大多数事件发生于数据包达到后的处理,实际上需要处理事件程序一定要“迅速”解决,或者干脆将消息转换为可以暂存的消息形式,由其他程序进一步处理。CMPP的SP端要满足大量短信息应用需求,就必须严格控制消息交互处理逻辑不要太复杂,特别不要设计大量I/O处理;如果实在要处理,最好采用异步线程的方式来处理。 3、为了提高效率,开了3个线程: RecvISMGMsgThread 用于处理接收ISMG发送过来的消息,并根据消息、消息解析后的字段内容触发相应的事件。 SendSPMsgThread 用于处理向ISMG发送数据包。注意,有些消息(譬如CMPP_ACTIVE_TEST)是系统自己产生的。另外,有些消息是收到ISMG的消息后需要立即回应给ISMG的,那么这些消息,全部进入内部维护的消息队列(_outSeqQueue)。该队列会自动排序消息,所有需要发送的消息,进入此队列,本线程不断从队列取出需要发送的消息,转换成数据包,通过Socket发送到ISMG. DeamonThread 用于监测数据连接socket是否可用,是否需要发出维持数据连接的测试数据包;有些消息发送过去了,过了协议规定的时间仍然没有收到RESP消息,那么需要将消息从已经发送的队列中提取,重新加入到发送队列中,排队后等待送出。 可以仔细分析提供的代码,研究其中的具体实现。 五、问题小结 根据自己的经验,觉得以下几点对于整个系统开发较为重要: 1、一定要正确理解协议。 很多网友交流时候,总抱怨协议滥,搞不定,其实很多原因属于自己没有清楚理解协议。从我的接触的移动ISMG来看,应该说实现协议还是很严格遵守CMPP的描述。倒是,一些网友自己开发的模拟器不是很规范(不是批评,郑重声明),需要自己在开发时候引起注意。 2、多线程互斥问题。 多个线程之间涉及一些队列的操作,需要进行同步锁定,否则容易引起问题。出现异常也需要及时捕获,并纪录作为错误信息参考,便于排除bug。 3、自己控制数据包流向和处理时间。 由于设计目标是高性能,所以在处理socket数据读写时候要注意对于一些事件处理不要过多消耗系统资源,避免引起数据包来不及处理而导致数据丢失。特别在数据繁忙时刻往往会使ISMG的吞吐性能下降,需要考虑对这种情况下的流量控制。有时,你不能够指望ISMG如你所愿及时回应你,更为常见的是ISMG根本不返回RESP类型的数据包。另外,本协议处理数据收发采用阻塞Socket,有网友建议我采用异步非阻塞Socket,我想可能异步非阻塞Socket会更好。 4、服务监控问题。 由于一些意外,往往会导致数据连接被中断,这是,需要建立超时重建连接的机制。我给出的例子并未很好解决,希望其他方家指正。 5、字节顺序问题。 这个问题,对于初接触socket编程的人士往往造成很大麻烦。不过,CMPP协议设计的基本数据类型很简单,仅需要按照本例子参考即可解决。 6、具体协议应用问题。 本例仅仅是一个按照协议要求实现CMPP协议的类,完整的SP端方案需要结合自己公司的实际要求,改造或者重用本例。限于篇幅,文中仅仅能够列出重点片断,详细细节清参考供下载的代码(附注释)。本文仅仅是针对CMPP2.0协议进行讨论开发,协议的详情请从移动梦网(http://www./moneditor/cs/SP/cmcc/)下载。其他技术参考可以到如下处获取,也可察看网友的贴字获取进一步详细说明: 天堂鸟交流论坛(http://www./bbs/index.asp) CSDN社区 移动平台 SP论坛(http://www./jishu/Index.asp) 如果你发现本人的实例存在问题(我想那简直是一定的了),请不吝赐教myjobsdk@yahoo.com.cn。 后记:现在CMPP3。0也就是移动的MISC平台版本的需要订购关系确定才可以计费。不过不是本文讨论的重点。 |
|