什么是粘包
TCP有粘包现象,而UDP不会出现粘包。
- TCP(Transport Control Protocol,传输控制协议)是面向连接的,面向流的。TCP的收发两端都要有成对的Socket,因此,发送端为了将更多有效的包发送出去,采用了合并优化算法(Nagle算法),将多次、间隔时间短、数据量小的数据合并为一个大的数据块,进行封包处理。这样的包对于接收端来说,就没办法分辨,所以需要一些特殊的拆包机制。
- UDP(User Datagram Protocol,用户数据报协议)是无连接的,面向消息的提供高效率服务。不会使用合并优化算法。UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。
举个例子
我们连续发送三个数据包,大小分别是1k,2k ,4k,这三个数据包,都已经到达了接收端的网络堆栈中,如果使用UDP协议,不管我们使用多大的接收缓冲区去接收数据,我们必须有三次接收动作,才能够把所有的数据包接收完.而使用TCP协议,我们只要把接收的缓冲区大小设置在7k以上,我们就能够一次把所有的数据包接收下来,只需要有一次接收动作。
如何处理粘包
-
提前通知接收端要传送的包的长度
粘包问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。
不建议使用,因为程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这样会放大网络延迟带来的性能损耗
-
加分割标识符
{数据段01}+标识符+{数据段02}+标识符
发送端和接收端约定好一个标识符来区分不同的数据包,如果接收到了这么一个分隔符,就表示一个完整的包接收完毕。
也不建议使用,因为要发送的数据很多,数据的内容格式也有很多,可能会出现标识符不唯一的情况
-
自定义包头(建议使用)
image.png
在开始传输数据时,在包头拼上自定义的一些信息,比如前4个字节表示包的长度,5-8个字节表示传输的类型(Type:做一些业务区分),后面为实际的数据包。
- 发送数据
以传输字符串 ”hello“ 为例:
//要传输的数据
NSData * data = [@"hello" dataUsingEncoding:NSUTF8StringEncoding];
//实际传输的数据
NSMutableData * mData = [NSMutableData data];
//计算数据总长度
unsigned int totalLength = 4 + 4 + (int)data.length;
//拼前4位
NSData * lengthData = [NSData dataWithBytes:&totalLength length:4];
[mData appendData:lengthData];
//拼5-8位
int type = 1;
NSData * typeData = [NSData dataWithBytes:&type length:4];
[mData appendData:typeData];
//拼接最后的data
[mData appendData:data];
//发送mData
。。。
- 接收数据
@property (nonatomic, strong) NSMutableData *dataM; //接收的完整的一个数据包的data
@property (nonatomic, assign) unsigned int totalSize; //一个完整的数据包大小
BOOL isNewPackage = self.dataM.length == 0;
if (isNewPackage) {
//接收一个新的数据包
//获取总大小
NSData * totalSizeData = [data subdataWithRange:NSMakeRange(0, 4)];
unsigned int totalSize = 0;
[totalSizeData getBytes:&totalSize length:4];
self.totalSize = totalSize;
NSLog(@"接收总数据的大小 %u",totalSize);
//获取type
NSData * typeData = [data subdataWithRange:NSMakeRange(4, 4)];
unsigned int type = 0;
[typeData getBytes:&type length:4];
NSLog(@"接收总数据的类型 %u",type);
//获取数据段
NSData * realData = [data subdataWithRange:NSMakeRange(8, data.length - 8)];
[self.dataM appendData:realData];
}
else {
//不是一个新的数据包 直接追加进去
[self.dataM appendData:data];
}
//判断是否接收完成
if (self.dataM.length == self.totalSize - 8) {
//已经接收完整
//处理data
NSString * string = [[NSString alloc] initWithData:self.dataM encoding:NSUTF8StringEncoding];
NSLog(@"接收到的数据为:%@",string);
//dataM重置
self.dataM = [NSMutableData data];
}
网友评论