什么是TCP粘包
TCP粘包就是通过TCP传输数据的时候,在接收方接收到的一个数据包中包含的不是我们定义的N个完整的数据结构,也就是说N个数据结构头尾相接,有时最后一个或开头一个数据结构被截断。
TCP粘包的原因
产生粘包的原因有两种:一是发送方造成的,二是接收方造成的。
发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。
接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据。
解决方法
既然粘包的原因有两种,那么我们就必须在发送方和接收方想办法。
1. 对于发送方引起的粘包现象,用户可通过编程设置来避免,TCP提供了强制数据立即传送的操作指令push,TCP软件收到该操作指令后,就立即将本段数据发送出去,而不必等待发送缓冲区满。
但这样做就等于关闭了TCP精心设计的优化算法。push越多发送越频繁,势必影响发送端效率。
2. 对于接收方引起的粘包,则可通过优化程序设计、精简接收进程工作量、提高接收进程优先级等措施,使其及时接收数据,从而尽量避免出现粘包现象。
但这样做只能减少出现粘包的可能性,但并不能完全避免粘包,当发送频率较高时,或由于网络突发可能使某个时间段数据包到达接收方较快,接收方还是有可能来不及接收,从而导致粘包。
因此,我推荐大家在收到数据之后在自己的程序中做一些处理,将收到的粘包拆开,虽然这样做会对接收端的效率产生一点影响,但相较于前面两种方法,这样做简单方便且解决粘包的成功率是百分之百。适用于大多数对实时性能要求不是非常高的程序。
算法分析
由于是在做iOS项目时遇到的这个问题,所以下面就贴出ObjectiveC的代码。
if (dataSegment == nil) {
dataSegment = [[NSMutableData alloc] initWithCapacity:512]; //设置一个缓冲区存放收到的数据,不完整的数据将被存储在这里等待剩余的数据到来拼接成完整的数据,再交给上层处理
}
[dataSegment appendData:data]; //将收到的data存入缓冲区
if (dataSegment.length <= kMinPacketLength) //如果缓冲区的数据还不够我们定义的最小数据包长度,那么不做处理继续等待
{
return;
}
NSMutableData *dataCopy = [[NSMutableData alloc] initWithCapacity:1024];
[dataCopy appendData:dataSegment];
dataSegment.length = 0; //清空数据缓冲区
int32_t m = dataCopy.length;
int32_t n = [self packetSize:dataCopy]; //根据数据包头部信息计算包长
NSRange range = NSMakeRange(0, n);
while (m > 0)
{
if (m > n)
{
//Multiple data
NSData *receivedData = [[NSData alloc] initWithData:[dataCopy subdataWithRange:range]];
[self didReceiveData: receivedData];
range.location += range.length;
int32_t nextPackSize = [self packetSize:[dataCopy subdataWithRange:NSMakeRange(range.location, kMinPacketLength)]];
range.length = nextPackSize;
m = m - n;
n = nextPackSize;
}
else if (m < n)
{
//Incomplete data
NSData *incompleteData = [dataCopy subdataWithRange:NSMakeRange(range.location, m)];
[dataSegment appendData:incompleteData]; //将不完整的数据存入数据缓冲区等待剩余部分到来
m = m - n;
}
else
{
//One complete data
NSData *receivedData = [[NSData alloc] initWithData:[dataCopy subdataWithRange:range]];
[self didReceiveData: receivedData];
m = m - n;
}
}
- (uint32_t) packetSize:(NSData*)data
{
NSData *packSizeData = [data subdataWithRange:NSMakeRange(4, 4)];
int32_t *p = (int32_t*)packSizeData.bytes;
int32_t packSize = CFSwapInt32LittleToHost(*p);
return packSize;
}
参考文档:http://www.ciw.com.cn/ 解决TCP网络传输“粘包”问题 作者:杨小平 王胜开