作者:朱金灿
来源:http://blog.csdn.net/clever101
前段时间,维护一个网络程序。客户反映我们的系统有时接收不到来自任务管理系统的socket字符串,存在丢失数据的问题。我看了一下代码(代码是别人写的),发现系统的代码写得有问题。原来系统的代码是开辟一个大的接收缓冲区,试图一下把整个数据包接收过来。实际上接收网络数据包面临一个拆包的问题,可靠的拆包的方式应是进行分段接收,然后把数据拼接起来。如何分段接收,这里大有讲究,且容我结合我的排错经历细细道来。
开始我考虑的一种算法是:
开始循环接收数据,设定每次接收100个字节(我能确保数据包都大于100个字节),对下面各种情况进行判断:
a.如果实际接收字节数为0,直接退出.
b.如果实际接收字节数小于100,就可能有两种情况:
(1)如果是第一次接收,就退出
(2)如果是最后一次接收(比如包的大小为320,最后接收的20个字节),就把数据接收过来,并退出
c.如果实际接收字节数等于100,首先判断是否是第一次接收,若是解析出包的大小(判断包头的第八个字节),然后进行接收,若不是就直接接收.
3.循环进行进行第二步,直接接收完整个包。
我这样设计是基于我对TCP协议的理解。我以为TCP协议能保证我每次都能接收到100个字节。我这样修改系统之后用户依然反映存在丢失数据的问题。于是我判断最有可能的情况就是第一次就接收不到100个字节从而导致程序退出。为证实我这个判断,我采用把接收到字符串都写进日志文件的办法。通过分析日志文件证实了我的判断,在丢失数据的一种情况就是第一次只接收了63个字节。这促使我更深刻地理解了TCP协议的特点。我认为TCP协议具有以下特点(大家认为不正确欢迎指出):
1. 可靠性。就是甲给乙传300个字节,TCP协议能保证这300个字节一个不少地传给乙。
2. 流特性。你可以想象TCP协议下的网络转输就是一条河流。这条“河流”的“河水”不是平坦的,而是起伏不平的,具体就是说你在客户端开辟一个100个字节的接收缓冲区,每次你实际接收的字节数有可能是0到100。
3. 顺序性。TCP协议保证后面服务器端发给客户端的数据不会跑到前面发的数据去,所有接收数据的顺序都按照发送顺序来。
理解了TCP协议的特点,我重新设计了数据包拆包算法。一般数据包对应程序逻辑中的一个结构体,该结构体一般要定义一个数据包长度的成员变量(为保证数据包接收的完整性),而且要将该变量尽可能排在数据包前面。我把这个算法分为两步:
第一步解析数据包的长度
第二步根据数据包的长度接收数据
具体算法是:
第一步解析数据包的长度
在接收的时候,应该先指定接收8字节(假设包长度的变量所占的字节数为8),并判断返回值,如果实际接收到的数据不足8字节,应调整缓冲区指针,继续接收后面的数据,例如只收到2字节,应该将缓冲区指针加4,在执行接收,指定接收6字节,然后再判断返回值,重复上面步骤,直到收齐8字节为止。收齐8字节立即解析并保存起来。
第二步根据数据包的长度接收数据
1. 设定接收缓冲区为整个包的长度.
2.开始循环接收数据,
a.如果实际接收字节数为0,直接退出.
b.如果实际接收字节数不为0,就直接接收,同时将待接收数据长度(初始化为整个包的长度)减去本次实际接收的字节数。
3.循环进行进行第二步,直接接收完整个包(以待接收数据长度为0进行判断).
值得注意的是,在各次接收过程中,如果接收函数返回0,表示连接已断开,如果返回值为-1,表示出现了错误,应根据具体情况来做出响应的处理。
相关源码为:
在此非常感谢CSDN的cnzdgs大侠给我的指点!