type
status
date
slug
summary
tags
category
icon
password
一、粘包与半包
粘包和半包在网络通信中是一种常见的现象。粘包主要在数据传输时,服务端在一条信息中读取到另外一条信息的数据。半包指的是服务端只接收到部分数据,而非完整的数据。主要是由于TCP是面向连接、以“流”的形式传输数据的协议,而
“流”数据是没有明确的开始和结尾边界
。1.1、粘包
客户端发送两次消息分别为abc、def,而服务端只接收到一次消息为abcdef。
原因:
- 应用层:接收方 ByteBuf 设置太大(Netty默认1024)
- 滑动窗口:假设发送方
256bytes
表示一个完整报文,但由于接收方处理不及时且窗口大小足够大,这256bytes
字节就会缓冲在接收方的滑动窗口中,当滑动窗口缓冲了多个报文就会粘包。
- Nagle算法:会造成粘包。
1.2、半包(拆包)
客户端发送了一次消息abcdef,而服务端却接收到两次消息abc、def,将原本的完整消息拆分了。
原因:
- 应用层:接收发 ByteBuf 小于实际发送数据量
- 滑动窗口:假设接收方的窗口只剩了
128bytes
,发送方的报文大小是256bytes
,这是放不下,只能先发送128bytes
,等待ack
后才能发送剩余部分,这就造成了半包。
- MSS限制:当发送的数据超过MSS限制后,会将数据切分发送,就会造成了半包。
本质是TCP是面向连接、以流的形式传输数据的协议,消息无边界。
1.3、滑动窗口
TCP以一个段(segment)为单位,每发送一个段就需要进行一次确认应答(ack)处理,但如果这么做,缺点是包的往返时间越长性能就越差
![notion image](https://oss.zhulinz.top/newImage/202302022132118.png?t=0f1d3f82-e623-4863-9f5d-42b18914e8a9)
为了解决性能差的问题,就引入了窗口概念,窗口大小即决定了无需等待应答而可以继续发送的数据最大值。
![notion image](https://oss.zhulinz.top/newImage/202302022134577.png?t=b4a4b3cb-921c-489e-850a-771ae5857d40)
窗口实际就起到一个缓冲区的作用,同时也能起到流量控制的作用
- 图中深色的部分即要发送的数据,高亮的部分即窗口
- 窗口内的数据才允许被发送,当应答未到达前,窗口必须停止滑动
- 如果 1001~2000 这个段的数据 ack 回来了,窗口就可以向前滑动
- 接收方也会维护一个窗口,只有落在窗口内的数据才能允许接收
1.4、MSS限制
- 链路层对一次能够发送的最大数据有限制,这个限制称之为 MTU(maximum transmission unit),不同的链路设备的 MTU 值也有所不同,例如
- 以太网的 MTU 是 1500
- FDDI(光纤分布式数据接口)的 MTU 是 4352
- 本地回环地址的 MTU 是 65535 - 本地测试不走网卡
- MSS 是最大段长度(maximum segment size),它是 MTU 刨去 tcp 头和 ip 头后剩余能够作为数据传输的字节数
- ipv4 tcp 头占用 20 bytes,ip 头占用 20 bytes,因此以太网 MSS 的值为 1500 - 40 = 1460
- TCP 在传递大量数据时,会按照 MSS 大小将数据进行分割发送
- MSS 的值在三次握手时通知对方自己 MSS 的值,然后在两者之间选择一个小值作为 MSS
![notion image](https://oss.zhulinz.top/newImage/202302022135785.png?t=678f3893-0d74-4ea2-a17c-b72a875e02f5)
1.5、Nagle算法
- 即使发送一个字节,也需要加入 tcp 头和 ip 头,也就是总字节数会使用 41 bytes,非常不经济。因此为了提高网络利用率,tcp 希望尽可能发送足够大的数据,这就是 Nagle 算法产生的缘由
- 该算法是指发送端即使还有应该发送的数据,但如果这部分数据很少的话,则进行延迟发送
- 如果 SO_SNDBUF 的数据达到 MSS,则需要发送
- 如果 SO_SNDBUF 中含有 FIN(表示需要连接关闭)这时将剩余数据发送,再关闭
- 如果 TCP_NODELAY = true,则需要发送
- 已发送的数据都收到 ack 时,则需要发送
- 上述条件不满足,但发生超时(一般为 200ms)则需要发送
- 除上述情况,延迟发送
1.6、解决方案
- 短链接,发一个包建立一次连接,这一连接建立到连接断开之间就是消息的边界,但是效率太低,需要重复的连接与断开,十分影响性能。
- 固定长度,每一条消息采用一定的长度,缺点浪费空间。
- 采用分隔符,每一条消息采用分隔符,例如\n结尾,缺点是需要转义。
- 预设长度,每一条消息分为head和body,head中包含body的长度。
1、短链接
将消息发送完就关闭连接通道channel
2、固定长度
让所有数据包长度固定(假设长度为8字节),服务端
childHandler
中加入客户端中
缺点:数据包的长度大小不好把握
- 长度定的太大,浪费资源。
- 长度定的太小,可能不满足某些数据包的长度,会造成半包现象。
3、固定分隔符
服务端加入,默认以\n或\r\n作为分隔符,如果超出指定长度仍未出现分隔符,则抛出异常
客户端在每条消息最后加入\n分隔符
缺点
- 处理字符数据比较合适,但如果内容本身包含了分隔符(字节数据常常会有此情况),那么就会解析错误
4、预设长度
利用LengthFieldBasedFrameDecoder(),
二、协议设计与解析
- Author:拾荒😂
- URL:https://blog.zhulinz.top//article/netty03
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!