长度、分隔符加校验的TCP分包设计(C#)

 

我们做TCP程序的时候最经常遇到的问题就是分包,这也是激动人心的程序开发(可能你不这么认为)中最不激动人心的一部分,换句话说,确实挺枯燥的。

这里简要说一下分包问题的来源吧,毕竟有人并没做过TCP。TCP或者说流式套接字都会面临一个同样的问题,就是逻辑上我们会引入报文或包的概念,通过报文传播消息,但是基于流的套接字不会在套接字层区分这个概念。传递来传递去的东西永远是一串串的char(抑或byte),不会有人告诉你从一个报文是从哪里开始到哪里结束,而且一个报文可能被拆成两部分传递。所以,我们要分包。

分包其实是个挺常见的问题,从Socket到串口,只要做流式套接字,都要在底层实现一个分包机制,然后给上层一个接口,实现对底层的透明。大体有如下两种常见的方式

  1. 特殊字符做分界符:比如,之间的认为是一个完整报文。
  2. 长度前缀:在报文前加一个长度信息,指明报文的长度。

两种方式的利弊如下表:

  1. 特殊字符

如果有错位或丢失,不影响下一个报文 报文内容里不能出现分界符,否则要做相应的置换

  1. 长度前缀

报文内容无关,可以出现任何字符 假如有错位或丢失,以后的报文都会找不到长度信息位置

这里稍作说明,关于分界符的置换,大概的做法是:假如分界符是#,正文里出现的#都置换成##,然后按照单一的#为分界符进行分界,最后把##置换回#。相当繁琐的一个过程。

所以,有没有一种方法,能即不受丢失错位影响,又可以兼容所有字符呢?答案是肯定的。(这句型是如此的NC,以至于所有的电视购物都少不了)

本文我们探讨一种特殊字符 长度前缀的定界方式,并且加入校验机制而保证报文的正确性。具体报文设计是这样的

开始符号
长度信息
报文正文
[校验信息]
结束符号

其中校验信息是可选的,本文的实现里是使用C#的序列化反序列化机制[1]进行校验,能反序列化则证明报文无误,否则有误。如果选择校验,大可用MD5等算法进行校验。

算法伪代码不再给出,源码已经相当通俗易懂了(至少我是这么觉得)。定界过程需要处理的问题确实很多,尤其需要考虑拼包,一个包拆成几部分而只有第一部分携带了长度信息,这就是很纠结的问题。(话说不画图说明真的是很没有职业道德的行为,但是我真没找到比较方便的可以画这种图的Latex代码,或者工具)

主程序里Debug函数是专门测试各种情况的,有兴趣可以自己测一下,如果有我没考虑到的情况请火速通知我,非常感谢。无聊贴一下程序截图吧

![未命名][1]

本文源码下载: [源码下载][2]

参考文献:

[1] 可扩展多线程异步Socket服务器框架EMTASS 2.0,

[2] 关于C#序列化结果的长度获取,