一、前言
关于TCP网络传输粘包,网上很多人写了原理。总结起来就一句话(这里拿Server和Client长连接,Server和Client之间通过信令传输做说明)
Server发送的时候,虽然按照一条条信令发送,经过网络传输,到达客户端操作系统网络层,首先进入缓冲池,然后TCP协议从池子中获取数据,传输给Client App。但是,我们知道TCP的传输有多个方案,比如,滑动窗口、1比特方案。所以应用层 Client App收到的数据包,基本不可能是一个完整的Server发送的信令包了。
个人理解TCP粘包的概念,即它描述了一个场景:“信令是一个个紧挨着的,好像是被粘在一起了,不知道从哪儿将信令拆分”。在把信令拆开之前我们要做一个必须的任务:规整数据。规整了socket数据,那么原始信令就一条条规整出来了。
二、分析
刚才已经讲过,Client从Socket收到的数据是不确定的,可能是1Byte,可能1000Byte。而即便相同的信令,内容不一样长度也是不同的。将信令一个个摘出来,需要一个关键的属性:信令长度。通常信令的前两个字节是长度(如下图中橙色块),其表明了该条信令的长度。Client需要一个Buffer,用于规整信令。
通常Client接收Socket数据,存在以下几种场景:
(图1)
上图1展示了:信令前两个字节(橙色标注)是200B,然而Client的Buffer只是接收到了100B,那么客户端什么也不做,等待后续的socket数据
(图2)
上图2展示了:信令前两个字节(橙色标注)是50B,然而Client的Buffer已经超过50B了,那么Client可以截取50B,当成完整的信令,给后续逻辑处理了。
(图3)
上图3跟,图2相似。
总之,逻辑上只有这三种情况:预期 > 实际 ; 预期 < 实际;预期 = 实际
三、逻辑实现
需要注意的是:在获取前两个字节的时候,需要判断系统是大端,还是小端
/**
* @brief 收到消息
*
* @param data 数据指针
* @param length 数据长度
*/
void onReceiveData(NSData * data)
{
if (data == NULL)
{
return;
}
[m_data appendData:data];
const Byte *packageData = (Byte *)[m_data bytes];
NSUInteger packageSize = [m_data length];
// parse packet
unsigned short messageSize = 0;
NSUInteger pos = 0;
while (pos < packageSize)
{
if (pos + 2 < packageSize)
{ // can read message packet-size
// read message packet-size
messageSize = *((unsigned short *)(packageData + pos));
// 现为小头
//Byte tmp = (messageSize & 0xFF00) >> 8;
//messageSize <<= 8;
//messageSize |= tmp;
if(messageSize <= packageSize - pos)
{ // there is a complate message
if (m_callback)
{
m_callback->onDeliverMsg((packageData + pos), messageSize);
}
pos += messageSize;
continue;
}
}
break;
}
// deal with last bytes.
if (pos < packageSize)
{
[m_data replaceBytesInRange:NSMakeRange(0, packageSize - pos) withBytes:(packageData + pos)];
[m_data setLength:packageSize - pos];
}
else
{
[m_data setLength:0];
}
}
(完)