一.粘包分包现象(来自Unity 3D网络实战一书)
粘包:
由于TCP协议本身的机制,客户端与服务器会维持一个连接发送数据,如果发送的网络数据包太小,TCP会等待,然后合并较小的数据包在发送,接收端便无法区分哪些数据是发送端自己分开的,因此便会产生粘包现象,或者接收端把数据放到tcp接受缓冲区中,如果数据没有及时从缓冲区取走,下次取数据时可能出现一次取出多个数据包的情况,如例,客户端发送两次数据,服务器一次接受:
客户端 Send: h e l l o
客户端 Send: u n i t y
服务器 Recv : h e l l o u n i t y
分包:
如果发送的数据包太大,TCP可能会把它拆分成多个包发送,接收端的一次Receive可能只收到一部分数据,如下例,如果发送的字符串较短,可能出现的概率很小,或者接收端的tcp接受缓冲区比较小时,每次读取,只能读取一部分.
客户端 Send:h e l l o u n i t y
服务器 Recv:h e l
服务器 Recv: l o u n i t y
二.解决方案
处理粘包分包的一种方法是在每个数据包前面加上长度字节(数据包长度,每次接收到数据后放到应用层定义的缓冲区,然后先读取长度字节,如果缓冲区的数据长度大于要提取的字节数,就取出相应字节,否则等待下一次数据接受,如下,客户端要发送”hellounity“ 和”love“两个字符串,它在每个包前面加上一个代表字符串长度的字符,按照TCP机制,接收端收到的字节顺序一定和发送顺序一致(顺序不一致的话tcp在传输控制层会做处理,不会传给上层应用使用,在计算机网络自顶向下一书有说)。
1:假设第一次接收到的是"10hel",那么服务器程序将接受到的数据存入缓冲区,然后读取第一个字节”10“,此时缓冲区长度只有4,服务器不处理,等待下一次接受。
2.假设第二次接收到的是"lounity4l",此时缓冲区便有13个字节,超出第一个包所需要的11个字节(10个数据字节加一个长度字节。于是程序读取缓冲区前11个字节的数据并进行处理,之后缓冲区便只剩下”4l“两个字节。
3.假设第三次接收到的是"ove"三个字节,这时缓冲区便有了 ”4love“ 个5字节,程序读取缓冲区这5个字节并进行处理。
客户端 Send ①:10 h e l l o u n i t y
客户端 Send ②:4 l o v e
服务器 Buffer①: 10 h e l
服务器 Buffer②: 10 h e l l o u n i t y 4 l
服务器 Buffer③: 4 l o v e
注意:上面都是假设所有字符按照1个字节来的,实际上包长度应该是一个int类型(4字节),数据按照协议的字符编码所占字节来计算的。
三.实际项目使用(如果有需求再详细查看文档以及和项目中demo)
1.一般都是定义通信协议,分为包头和包体,包头是固定长度
例如:包头: magicCode: 1byte 魔法值或版本号
pkeSize: 4byte 包体总长度
cmdId: 1byte 业务类型
包体: 用户业务自定义数据,数据长度为pkeSize
2. 那么java 程序员在写socket通信的话,该怎么办呢?
bio,nio这种原生的socket api,只能自己写逻辑,判断包长度够不够,不够就存到内存(或者缓存)中,等到下一次包到了,将上次的包拼到一起,如果是一个完整的包,继续走处理逻辑,所以其实解决粘包,分包其实很简单,但是切记
解决粘包:根据数据长度去读取
解决分包:数据包长度够,就用,不够就缓存起来,等着下次用
现在java大多都是使用netty框架实现socket通信,netty实现了很多解决粘包分包问题的解决方案(本质上就是我说的这种),降低用户在实际实现中的复杂度,详细的可以看书
udp没有粘包分包,因为底层使用的是链表结构(每次read都是一个完整的包),一次发送对应一次接受,tcp有,是因为tcp是流,没有边界的(可以理解为数组),所以需要协议去定义边界。
websocket没有粘包分包问题是因为websocket协议里解决了(数据包的第一个bit 表示是否是最后一个帧,然后还有数据长度等)
netty中websocket解决粘包,分包如下:
https://www.jianshu.com/p/30c26a755a87
https://blog.csdn.net/mafei6827/article/details/79886593
下面这篇文章关于tcp有一段写的很好
https://blog.csdn.net/weixin_32821913/article/details/113317948?spm=1001.2014.3001.5506
写本篇文章主要是给小白看的,因为很多人没接触过socket编程,对于粘包分包也没有概念,如果直接使用netty等框架,可能不是很好理解它里面封装的各种解码器。
看到文章末尾的,可以看下的我的程序人生这篇文章,主要是讲我在编程这条路上的经历,祝愿对你有用,感谢!