app开发者平台在数字化时代的重要性与发展趋势解析
730
2022-09-17
从一次Windows网络编程排错经历中得出的一个可靠拆包算法
作者:朱金灿前段时间,维护一个网络程序。客户反映我们的系统有时接收不到来自任务管理系统的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,表示出现了错误,应根据具体情况来做出响应的处理。相关源码为:
BOOL GlocalReceiveMsg(CMIClientSocket* pClientSocket,BYTE** pResultBuf,int& iResultLen){
// 待接收的数据包的长度
int nLeftDataLen = 0; // 数据包体长度
int nPackBodyLen = 0; // 外部接收缓冲区
BYTE* pRstbuffer = NULL;// 接收函数一次实际收到的数据长度
int nRevOnceLen = 0;// 包头缓冲区
BYTE PackLenBuf[STDATABAND_SIZE];// STDATABAND_SIZE为网络数据包头,具体数值为12
int nRecPackLen = STDATABAND_SIZE;// 初始化实际接收的长度为0 iResultLen = 0;// 接收包头数据
do {
// Receive函数第一个参数为缓冲区指针,第二个参数为缓冲区长度,返回值为 // 实际接收的字节数。注意这里有缓冲区指针的移动和缓冲区长度的变化,变化规律就是 // 首先设定缓冲区长度为STDATABAND_SIZE(即12),若实际接收字节数为4,那么第二 // 次设定缓冲区指针移动4个字节,长度为12-4=8,照此方法循环接收 nRevOnceLen = pClientSocket->Receive(PackLenBuf+STDATABAND_SIZE-nRecPackLen,nRecPackLen); if(result
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~