回答
TCP 拆包/粘包问题主要是因为 TCP 是一个面向流的协议,它在传输数据时没有固定边界的概念。这就意味着,当应用程序使用 TCP 协议进行通信时,TCP 层会根据需要将发送的数据拆分为若干段(拆包),或者将多个消息合并成一段(粘包),这就导致接收端在读取这些数据的时候可能无法一次性完整地解析一个完整的消息。
Netty 对拆包/粘包的解决方案是提供编解码器,它提供了多种解决拆包/粘包问题的编解码器:
FixedLengthFrameDecoder
:固定长度的解码器。过设置固定长度,每次都解码固定大小的数据包。LineBasedFrameDecoder
:行分隔符解码器。使用换行符(如\n
或\r\n
)作为消息的分隔符来解码消息。DelimiterBasedFrameDecoder
:分隔符解码器。自定义消息的分隔符来进行消息的分割。LengthFieldBasedFrameDecoder
:长度字段解码器。通过在消息头中加入长度字段来标识消息的总长度,然后根据这个长度来解码消息。
详情
拆包/粘包原因
客户端消息明明是一条一条地发,为什么会有拆包/粘包情况呢?
TCP 是传输层协议,它并不了解我们应用层业务数据的含义,它会根据实际情况对数据包进行划分。所以在业务上我们认为是一个完整的数据包,可能会被 TCP 拆分为多个数据库报进行发送,也有可能会将多个数据包合并成一个数据包发送,这就会出现拆包/粘包的问题。
在网络通信的过程中,影响可以发送的数据包大小受很多因素限制,比如 MTU 传输单元大小、MSS 最大报文长度、滑动窗口。同时 TCP 也采用了 Nagle 算法对网络数据包进行了优化,所以要了解 TCP 为什么会有拆包/粘包问题,就需要了解这些概念。
MTU 最大传输单元
MTU(Maximum Transmission Unit),最大传输单元,是指网络能够传输的最大数据包大小,它决定了发送端一次能够发送报文的最大字节数。它是链路层协议,其最大值默认为 1500 byte。
MTU 是数据链路层对网络层的限制,最小为 46 byte,最大为 1500 byte,意思就是说网络层必须将发给网卡 API 的数据包大小控制在 1500 byte 以下,否则失败。
那为什么要有一个这样的限制呢?我们都知道网络中通常是以数据包为单位进行信息传输的,那么一次传输多大的数据包就决定了整个传输过程中的效率了,理论上数据包的大小设置为尽可能大,因为着有效的数据量就越大,传输的效率也就越高,但是传输一个数据包的延迟就会很大,而且数据包中 bite 位发送错误的概率也就越大,如果这个数据包丢失了,那么重传的代价就会很大。但是如果我们将数据包大小设置较小,那么我们传输的有效数据就会很小,传输效率就会比较低。所以我们就需要 MTU 来控制网络上传输数据包的大小,如果数据包大,我们就将其拆分,如果小,我们就把几个数据包进行合并,从而提供传输效率。
MSS 最大报文长度
MSS(Maximum Segment Size),最大报文长度,它表示 TCP payload 的最大值,它是 TCP 用来限制应用层发送的最大字节数。
我们知道了 MTU 限定了网络层往数据链路层发送数据包的大小,如果网络层发现一个数据包大于 MTU,那么它需要将其进行分片,切割成小于 MTU 的数据包,再将其发送到数据链路层。
一台主机上的所有应用都将数据包发往网络层,如果这些数据包太大了,则需要对其进行分片,但是这么多数据包都交给网络层来分片,是不是降低了效率?作为网络层,它的理想状态是,让 TCP 来的每一个数据包,大小都小于 MTU,这样它就不需要分片了。
MSS 是 TCP 协议定义的一个选项,是 TCP 用来限定应用层最大的发送字节数。它是在 TCP 连接建立时,双方进行约定的。当一个 TCP 连接建立时,连接的双方都需要通告各自的 MSS,以避免分片。
TCP 建立连接时,双方都需要根据 MTU 来计算各自的 MSS,计算规则如下:
MTU = IP Header(20) + TCP Header(20) + Data,MTU 默认最大值为 1500,所以 TCP 的有效数据 Data 的最大值为 1500 - 20 - 20 = 1460 ,这个值就是 MSS 的值。
MSS 的值是通过三次握手的方式告知对方的,互相确认对方的 MSS 值大小,取较小的那个作为 MSS。
- 客户端在发送的 SYN 报文中携带自己的 MSS(1300)。
- 服务端接收该报文后,取客户端的 MSS(1300) 和自己本地的 MSS (1200)中较小的那个值作为自己的 MSS(1200)。在回复的 SYN-ACK 中也携带自己的 MSS(1200)。
- 客户端收到该 SYN-ACK 后,取服务端的 MSS(1200)和自己本地的 MSS(1200)中较小的那个值作为客户端的 MSS(1200)。
Nagle 算法
Nagle 算法于 1984 年被福特航空和通信公司定义为 TCP/IP 拥塞控制方法。它主要用于解决频繁发送小数据包而带来的网络拥塞问题。
为了尽可能地利用网络带宽,TCP 总是希望能够发送足够大的数据包,由于有 MSS 的控制,所以它总是希望每次都能够以 MSS 的尺寸来发送数据。但是我们需要发送的数据并不会每次都有那么多字节,怎么办?攒着。Nagle 算法会在数据为得到确认之前会先将其写入到缓冲区中,等待数据确认或者缓冲区积攒到一定大小再把数据包发送出去。
Nagle 算法就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。