我们做TCP程序的时候最经常遇到的问题就是分包,这也是激动人心的程序开发(可能你不这么认为)中最不激动人心的一部分,换句话说,确实挺枯燥的。
这里简要说一下分包问题的来源吧,毕竟有人并没做过TCP。TCP或者说流式套接字都会面临一个同样的问题,就是逻辑上我们会引入报文或包的概念,通过报文传播消息,但是基于流的套接字不会在套接字层区分这个概念。传递来传递去的东西永远是一串串的char(抑或byte),不会有人告诉你从一个报文是从哪里开始到哪里结束,而且一个报文可能被拆成两部分传递。所以,我们要分包。
分包其实是个挺常见的问题,从Socket到串口,只要做流式套接字,都要在底层实现一个分包机制,然后给上层一个接口,实现对底层的透明。大体有如下两种常见的方式
- 特殊字符做分界符:比如,之间的认为是一个完整报文。
- 长度前缀:在报文前加一个长度信息,指明报文的长度。
两种方式的利弊如下表:
- 特殊字符
如果有错位或丢失,不影响下一个报文 报文内容里不能出现分界符,否则要做相应的置换
- 长度前缀
报文内容无关,可以出现任何字符 假如有错位或丢失,以后的报文都会找不到长度信息位置
这里稍作说明,关于分界符的置换,大概的做法是:假如分界符是#,正文里出现的#都置换成##,然后按照单一的#为分界符进行分界,最后把##置换回#。相当繁琐的一个过程。
所以,有没有一种方法,能即不受丢失错位影响,又可以兼容所有字符呢?答案是肯定的。(这句型是如此的NC,以至于所有的电视购物都少不了)
本文我们探讨一种特殊字符 长度前缀的定界方式,并且加入校验机制而保证报文的正确性。具体报文设计是这样的
开始符号
长度信息
报文正文
[校验信息]
结束符号
其中校验信息是可选的,本文的实现里是使用C#的序列化反序列化机制[1]进行校验,能反序列化则证明报文无误,否则有误。如果选择校验,大可用MD5等算法进行校验。
算法伪代码不再给出,源码已经相当通俗易懂了(至少我是这么觉得)。定界过程需要处理的问题确实很多,尤其需要考虑拼包,一个包拆成几部分而只有第一部分携带了长度信息,这就是很纠结的问题。(话说不画图说明真的是很没有职业道德的行为,但是我真没找到比较方便的可以画这种图的Latex代码,或者工具)
主程序里Debug函数是专门测试各种情况的,有兴趣可以自己测一下,如果有我没考虑到的情况请火速通知我,非常感谢。无聊贴一下程序截图吧
![未命名][1]
本文源码下载: [源码下载][2]
参考文献:
[1] 可扩展多线程异步Socket服务器框架EMTASS 2.0,
[2] 关于C#序列化结果的长度获取,