TCP是一个数据流协议,所以TCP发送的数据包大小是不可控制的,这时候就会出现粘包和半包的现象,下面这张图是我从网上找的,描述很形象
1. 情况1,Data1和Data2都分开发送到了Server端,没有产生粘包和拆包的情况。
2. 情况2,Data1和Data2数据粘在了一起,打成了一个大的包发送到Server端,这个情况就是粘包。
3. 情况3,Data2被分离成Data2_1和Data2_2,并且Data2_1在Data1之前到达了服务端,这种情况就产生了拆包。
由于网络的复杂性,可能数据会被分离成N多个复杂的拆包/粘包的情况,所以在做TCP服务器的时候就需要首先解决拆包/粘包的问题。
1. 应用程序写入数据的字节大小大于套接字发送缓冲区的大小
2. 进行MSS大小的TCP分段。MSS是最大报文段长度的缩写。MSS是TCP报文段中的数据字段的最大长度。数据字段加上TCP首部才等于整个的TCP报文段。所以MSS并不是TCP报文段的最大长度,而是:MSS=TCP报文段长度-TCP首部长度
3. 以太网的payload大于MTU进行IP分片。MTU指:一种通信协议的某一层上面所能通过的最大数据包大小。如果IP层有一个数据包要传,而且数据的长度比链路层的MTU大,那么IP层就会进行分片,把数据包分成托干片,让每一片都不超过MTU。注意,IP分片可以发生在原始发送端主机上,也可以发生在中间路由器上。
1. 消息定长。例如100字节。
2. 在包尾部增加回车或者空格符等特殊字符进行分割,典型的如FTP协议
3. 将消息分为消息头和消息尾。
我使用方法3解决数据包粘包和半包的情况
数据包分为
包头+包体
包头
/// 网络数据包包头
struct NetPacketHeader
{
unsigned short wDataSize; ///< 数据包大小,包含封包头和封包数据大小
unsigned short wOpcode; ///< 操作码
};
包体
struct NetPacket
{
NetPacketHeader Header; ///< 包头
unsigned char Data[NET_PACKET_DATA_SIZE]; ///< 数据
};
/// 网络操作码
enum eNetOpcode
{
NET_TEST1 = 1,
};
/// 测试1的网络数据包定义
struct NetPacket_Test1
{
int nIndex;
char name[20];
char sex[20];
int age;
char arrMessage[512];
};
封包方法
bool TCPServer::SendData( unsigned short nOpcode, const char* pDataBuffer, const unsigned int& nDataSize )
{
NetPacketHeader* pHead = (NetPacketHeader*) m_cbSendBuf;
pHead->wOpcode = nOpcode;//操作码
// 数据封包
if ( (nDataSize > 0) && (pDataBuffer != 0) )
{
CopyMemory(pHead+1, pDataBuffer, nDataSize);
}
// 发送消息
const unsigned short nSendSize = nDataSize + sizeof(NetPacketHeader);//包的大小事发送数据的大小加上包头大小
pHead->wDataSize = nSendSize;//包大小
int ret = ::send(mAcceptSocket, m_cbSendBuf, nSendSize, 0);
return (ret > 0) ? true : false;
}
拆包策略
//TCP会存在有时候发送半包的情况,所以事先要检测接受的数据是否大于包头的长度,如果大于的话就接受并解析包头,包头的大小是固定的
int nRecvSize = ::recv(mServerSocket,
m_cbRecvBuf+m_nRecvSize,
sizeof(m_cbRecvBuf)-m_nRecvSize, 0);
// 保存已经接收数据的大小
m_nRecvSize += nRecvSize;
// 接收到的数据够不够一个包头的长度
while (m_nRecvSize >= sizeof(NetPacketHeader))//已经收到一个完整的包,如果没用收到一个完整的包,此处循环不执行,继续下一轮循环
// 读取包头
NetPacketHeader* pHead = (NetPacketHeader*) (m_cbRecvBuf);
const unsigned short nPacketSize = pHead->wDataSize;
// 判断是否已接收到足够一个完整包的数据
if (m_nRecvSize < nPacketSize)
{
// 还不够拼凑出一个完整包
break;
}
// 拷贝到数据缓存
CopyMemory(m_cbDataBuf, m_cbRecvBuf, nPacketSize);
// 从接收缓存移除
MoveMemory(m_cbRecvBuf, m_cbRecvBuf+nPacketSize, m_nRecvSize);
m_nRecvSize -= nPacketSize;
// 解密数据,以下省略一万字
// ...
// 分派数据包,让应用层进行逻辑处理
pHead = (NetPacketHeader*) (m_cbDataBuf);
const unsigned short nDataSize = nPacketSize - (unsigned short)sizeof(NetPacketHeader);
OnNetMessage(pHead->wOpcode, m_cbDataBuf+sizeof(NetPacketHeader), nDataSize);
以上就是解决简单的解决数据包粘包和半包的方法,结尾应该加上一个结尾符。