正文

自定义应用层通信协议(2)2009-07-11 22:33:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/xman/45132.html

分享到:

4.数据包TLV的设计 从应用层HTTP协议,到超文本置标语言HTML(HyperText Mark-up Language),再到可扩展置标语言XML(Extensible Markup Language),它们提供了数据的格式化存储、传输和格式化显示的规范,是网络通信的基石。然而HTTP协议以及HTML/XML置标语言的本质就是定义一堆标签(Tag)对数据进行串行化序列化,然后接收方再根据标签解析、还原数据。 自定义通信协议的关键是对数据包的合理构造(construct)和正确解析(parse),即制定编解码规则。 抽象语法标记ASN(Abstract Syntax Notation) BER的长度确定的编码方式,由3部分组成Identifier octets、Length octets和Contents octets,实际上这就是一中TLV(Type-Length-Value)模型:类型字段(Type或Tag)是关于标签和编码格式的信息;长度字段(Length)定义数值的长度; 内容字段(Value)表示实际的数值。 因此,一个编码值又称TLV三元组。编码可以是基本型或结构型,如果它表示一个简单类型的、完整的显式值,那么编码就是基本型(primitive);如果它表示的值具有嵌套结构,那么编码就是结构型 (constructed)。 TLV编码就是指对Type(Tag)、Length和Value进行编码,形成比特流数据包;解码是编码的逆过程,是从比特流缓冲区中解析还原出原始数据。 采用C++编程语言设计TLV协议类,其类视图如图5所示。 图5 CTLV类视图 目前只提供设置整形值(int型)的setValue_Int和设置字符串值(C_String型)的SetValue_Cstring两个接口。 TLV将数据封装成包的格式如表1所示。 表1 TLV包格式 TLV包 头部 包实体 m_dwTag m_nLen m_pValue TLV的接口说明: (1)值类型标签m_vtTag是内部辅助枚举变量,它根据构造TLV时传递的服务类型标签m_dwTag来确定。 (2)TLV::m_nLen在为TLV设置具体值时确定。 (3)TLV包的封装: 1)使用Tag参数创建一个TLV对象后,调用TLV::setValue_*方法为TLV填充具体值; 2)调用TLV::toBuffer方法打包到缓冲区streamBuffer。 (4)TLV包的解析:创建一个TLV对象后,调用TLV::fromBuffer方法从缓冲区streamBuffer解析出TLV。 (5)封装和解析涉及到本机字节顺序和网络字节顺序的转换问题。 (6)调用TLV::setValue_*方法填充TLV时,统一字节边界数为4。 5.数据报Package的设计 不同于底层的数据包/数据报只是对数据层次的封装解析,实际应用程序是以事件驱动的,因此必须注册不同的信令(事件类型标签),然后填充到数据报中。接收端根据信令做出相应的事件处理。 例如在C/S通信系统中,客户端往往要先登录,通过服务器端的校验才能进行后续通信。因此客户端运行后,需要构造并向服务器端发送含有LOGIN信令的包含用户名字符串strUserName和密码字符串strPassWord的数据报;服务器端解析LOGIN信令后做校验处理,然后发送含有LOGIN_RESPONSE信令和校验结果的回执数据报给客户端。 采用C++编程语言设计Package类,其类视图如图6所示。 图6 CPackage类视图 Package类将TLV封装成包的格式如表2所示。 表2 Package包格式 Package包 头部 序列号 包实体 m_nCmdLen m_dwCmdID m_dwCmdState m_nSeqNo Count*Tlv Package的接口说明: (1)Package::m_nCmdLen是整个Package包的长度,将其作为首个字段的好处在于当传送大数据包时,接收方可以根据数据长度来控制读状态,从而将一个大数据包分批接收。 (2)Package::m_nCmdLen在构造函数中初始化为16,在调用Package::addTLV方法填充包实体时增长。 (3)Package包的封装: 1)创建Package对象后,调用Package::setHeader方法填充头部信令; 2)创建TLV对象并填充数据,再调用Package::addTLV方法填充包实体; 3)调用Package::toBuffer方法将Package打包到缓冲区streamBuffer。 (4)Package包的解析: 1)先创建一个Package对象,调用Package::fromBuffer方法从缓冲区streamBuffer先解析出Package的头部和序列号,再从剩余缓冲区中解析出TLV并将其串行化到链表。 2)调用Package::getTLV方法根据Tag从链表中查找具体TLV包,再调用TLV::getValue方法取得具体值。 (5)Package::toBuffer方法和Package::fromBuffer方法主要遍历Package::m_TLV_List列表,然后调用TLV::toBuffer方法和TLV::fromBuffer方法解析出TLV数据单元。 TLV数据包的功能测试(主要是本地测试) 鉴于实际通信数据最后都要转换成比特流,故只测试发送字符串类型的变量,仅测试协议能否正确打包、解析。其他类型的普通数据都可以转换成字符串传输,最后,接收方根据m_dwTag确定值类型m_vtTag,解析出具体值。 对TLV::setValue_C_String方法填充TLV的测试,需要考虑字节对齐问题。对于长度为4字节倍数的C状态字符串,打包时省去末尾的‘\0’结束标志符。需要测试长度非4倍数的字符串和长度为4倍数的字符串。 经本地测试,调用TLV::setValue_Int方法和TLV::setValue_C_String方法构造整形和字符串时,能够正确封装、正确解析。 Package数据报的功能测试,主要是将TLV组合成包,然后添加信令,完成特定的通信。对登陆LOGIN和发送消息SUBMIT_SM的测试表明Package协议能正确封装、正确解析。 在实际项目中使用Package通信协议,对于稍大一点的数据块需要控制好读的步骤,以便能接收整包完整的信息。

阅读(8956) | 评论(0)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

暂无评论
您需要登录后才能评论,请 登录 或者 注册