一 现象:
粘包:
A机器发出2包数据,B机器把2包数据作为一次收到,此时2包数据粘在一起。
分包:
A机器发送1包数据,B机器分为两次收到这包数据,此时,这1报数据分为2次被B机器收到。
二 产生原因:
当服务端和客户端用到TCP通信时,可能会有以下场景(1)网络有延迟、(2)客户端频繁发送的数据包给服务端,(3)TCP自身机制(需要等自己缓冲区满后,才发送一包数据),由于这些原因会导致粘包,服务端一次收到的数据中包含多个数据包,此时就得分包处理数据。
三 处理粘包分包代码:
(1)协议:
(2)代码:
#include "stdafx.h" #include"windows.h" #include<iostream> #define CHONG_QING_SIX_LOCK_FRAME_MAX_LENGTH 4096 #define CHONG_QING_SIX_LOCK_DATA_HEADER_LENGTH 6 int RecvData() { DWORD recv_len = 0; int dataLength = 0; int sumDataLength = 0; int nRemainSize = 0; int lastPos = 0; BYTE recvbuf[CHONG_QING_SIX_LOCK_FRAME_MAX_LENGTH], databuf[CHONG_QING_SIX_LOCK_FRAME_MAX_LENGTH]; char oneFrameData[1024]; memset(recvbuf, 0, sizeof(recvbuf)); memset(databuf, 0, sizeof(databuf)); //收到服务端消息 //接受数据,处理粘包,拆分包 recv_len = (int)recv(m_Socket, (char *)recvbuf, CHONG_QING_SIX_LOCK_FRAME_MAX_LENGTH, 0); if (recv_len > 0) { memcpy(databuf + lastPos, recvbuf, recv_len); lastPos += recv_len; //判断消息缓冲区的数据长度大于消息头 while (lastPos >= CHONG_QING_SIX_LOCK_DATA_HEADER_LENGTH) { //包头做判断,如果包头错误,收到的数据全部清空 if (databuf[0] == 0xEF && databuf[1] == 0xEF && databuf[2] == 0xEF && databuf[3] == 0xEF) { dataLength = MAKEWORD(databuf[4], databuf[5]); sumDataLength = CHONG_QING_SIX_LOCK_DATA_HEADER_LENGTH + dataLength + 6; //判断消息缓冲区的数据长度大于消息体 if (lastPos >= sumDataLength) { //CRC校验 if (CheckSum((byte *)databuf, dataLength + CHONG_QING_SIX_LOCK_DATA_HEADER_LENGTH + 2)) { memcpy(oneFrameData, databuf, sumDataLength); //处理数据 DealData(oneFrameData); //剩余未处理消息缓冲区数据的长度 nRemainSize = lastPos - sumDataLength; //将未处理的数据前移 if (nRemainSize > 0) { memcpy(databuf, databuf + (dataLength + CHONG_QING_SIX_LOCK_DATA_HEADER_LENGTH + 6), nRemainSize); lastPos = nRemainSize; } } else { if (nRemainSize > 0) { memcpy(databuf, databuf + sumDataLength, nRemainSize); } lastPos = nRemainSize; } } else { break; } } else //寻找下一个包头 { BOOL isFind = FALSE; int nFindStart = 0; for (int k = 1; k < lastPos; k++) { if (databuf[k] == 0xEF && databuf[k + 1] == 0xEF && databuf[k + 2] == 0xEF && databuf[k + 3] == 0xEF) { nFindStart = k; isFind = TRUE; break; } } if (isFind == TRUE) { memcpy(databuf, databuf + nFindStart, lastPos - nFindStart); lastPos = lastPos - nFindStart; } else { memset(databuf, 0, sizeof(databuf)); lastPos = 0; break; } } } } return 0; } int _tmain(int argc, _TCHAR* argv[]) { system("pause"); return 0; }
四 对以上代码处理流程解释:
1)可以看到以上代码分为2个缓冲区,第一个只负责接收数据,第二个负责处理数据。
2)当服务端收到一包数据时,2种情况时,将处理数据的缓冲区的数据全部清空,(1)当出现包头验证错误,(2)CRC校验失败。
3)当接受到的数据长度大于包头,但是不大于整个消息体的长度时,保存前面接收的数据后,跳出while循环,继续接受数据,
4)当接收到的数据是多包数据时,它会一直在while循环里面处理数据,直到把每一包数据都分开并处理完后跳出。