前情提要
四层网络模型各司其职,消息(SDU)在进入每一层时都会多加一个报头(PCI),这个PCI记录着该SDU的一些关键统计信息
。SDU+PCI合并起来就组成一个完整的消息,简称为PDU
- 链路层:帧(Frame)头部作用
源 MAC 地址 和 目的 MAC 地址,用于在局域网内通信唯一标识设备,实现数据在物理链路上的传输 - 网络层:数据包(Packet)头部作用
包含 源IP 地址和 目的IP 地址,用户在互联网中实现端到端通信 - 传输层:段(Segment)/数据报(Datagram)头部作用
包含 源端口号 和 目的端口号,用于标识同一主机上的不同应用程序 - 应用层:消息(Message)头部作用
应用层协议根据具体需求定义头部格式,用于实现特定的业务逻辑
详见https://www.cnblogs.com/lmy5215006/p/18838393
数据切片
把互联网比作一条水管,那么这条水管是有一定的粗细的,而水管的粗细决定了流量的大小。因此,当我们发送"Hello World"时,当水管粗时,可以一次性发送完毕,当水管细时,就需要拆解成'He','llo','Wo','rld'。
决定"水管粗细"的由底层的数据链路层决定,为一个MTU(通常为1500Byte),当网络层(IP)数据包<=1500byte时,一个数据包即可完成发送,如果>1500byte就需要拆分为两个数据包。
MTU(Maximum Transmission Unit,最大传输单元)
举个例子:MTU为1500byte,IP头为20Byte,IP层传输了一个3000Byte的数据包
- 第一个数据包
IP Header:20byte
Payload:1480byte
Total:1500byte - 第二个数据包
IP Header:20byte
Payload:3000byte-1480byte=1520byte (超过MTU,需要再次分片,实际1480)
Total:1500byte - 第三个数据包
IP Header:20byte
Payload:40byte
Total:60byte
由此可以看到,一次传输被拆分成了三次,每个分配重复携带IP Header,增加了额外的传输冗余
,且分配需要重组,增加了延迟与丢包风险
。
MTU与MSS
为了缓解上述的问题,传输层的TCP协议通过MSS(Maximum Segment Size,最大段大小)来避免IP分片。
- 三次握手协商
三次握手时,双方通过SYN报文交换MSS值,确保Segment 不超过MSS。
MSS=MTU-IP Header-TCP Header,比如MTU=1500,那么MSS=1500-20-20=1460byte。 - 路径MTU发现
IP协议通过ICMP协议探测传输路径中的最小MTU,动态调整数据包大小以减少分片。
通过动态协商MTU,使得IP数据包使用不会超过MTU的值,从而避免了分片。不会出现MTU=1500,IP数据包3000Byte的现象。
粘包
人生就像打电话,不是你挂就是我挂。
切片也是,你链路层倒是爽了,不用拆包了,但传输层就遭殃了,因为总要有人负重前行。
TCP是一种面向连接,可靠的,基于字节流
的传输层通信协议,因为基于字节流的特点,数据由01组成,所以当我们在互联网中传输"hello World"时,是以0101010101这种格式的字节流发送。
这些二进制数据,对于接收端来说,不知道要接收多少才能组成一个消息,因此其本质是应用层消息边界在TCP流中消失
。
粘包发生的原因如下:
- 数据包过小
当Segment特别小,没有达到MSS的标准,TCP的Nagle算法
会原地等待200ms,等下一个包一起发送。 - 数据包过大
如果下一个包来之后,超过了MSS的标准,则会拆包
。 - 接收端读取数据不及时
接收端处理不及时,导致在Buff中好几个Segment的先后粘在一起,导致接收端无法区分。
说白了,粘包不是TCP的设计缺陷,而是一种取舍。
如何解决粘包
既然原理知道了,那么解决它也很简单.
- 消息定长
每个数据包的长度固定,那么接收端只要固定读取特定长度的二进制流即可区分。 - 分割符标记
通过特殊标记作为头/尾,比如EOF,回车,0xffffff等。当接收端处理到特殊标记时,就知道消息读取完了。 - 头部包含长度字段
一般会配合上面的分割符标记来加强,在Header中加入消息长度,然后就如同消息定长一样读取即可。
如果某个数据里正好有EOF怎么办?
还有标志位作为兜底,发送端在发送时加入16校验和(对完整数据进行CRC),以供接收端校验。
如同文件的MD5一样,下载完成后校验MD5,避免错误。
FAQ
UDP会有粘包问题吗?
不会,原因如下:
- UDP 不存在合并数据的机制
Datagram是独立的数据单元,是最小单位,包含完整的Head和Payload。接收方要设置合理的缓冲区来接收,否则数据会丢失。
当Datagram,网络层(IP层)会对其分片,但传输层本身不处理分片。 - Datagram边界明确
UDP 协议保证接收方可以通过数据报的长度字段(头部中 Length 字段)准确区分每个数据包的起始和结束。
简单来说,UDP自己都不保证消息消息完整,就算发生粘包又怎么样呢?
网络层会有粘包问题吗?
虽然我们在传输层明确了MSS会小于MTU(1500byte),避免了IP层的大包分片,但还会有漏网之鱼。比如在动态路径MTU发现中,发现某个路由器MTU只有500byte,那么IP层也需要对数据包进行切片。
再此之后会重新协商,传输层会调整MSS为500byte。
回到正题,那么网络层会有粘包问题吗?
答案是不会,再次强调一遍粘包本质是应用层消息边界在TCP流中消失
。网络层只负责数据切片以及数据重组,它不关心里面的内容是什么
,只是数据的搬运工,因此不会发生粘包。
关闭Nagle算法会减少粘包吗?
关闭Nagle算法会减少粘包,因为小的数据包会立即发送,而不是等200ms。
但治标不治本,接收方处理速度慢也是粘包的一个原因。