本文总结 TCP 协议的原理,包括:
本文笔记目的,内容较多。 TCP 协议非常复杂, 读完本文需要许多耐心 。
网络是不可靠的 ¶
计算机网络是不可靠的,存在 丢包、乱序、延时 。
这是众多 TCP 协议机制的设计出发点,万恶之源,将贯穿全文。
TCP 概念和特点 ¶
TCP 协议全称 传输控制协议, 是一种 面向连接的、可靠的、面向字节流的 传输层通信协议。
TCP 在 TCP/IP 协议模型 中位于传输层。
TCP/IP 模型中各层数据包结构的关系如下图。
TCP 协议的主要特点:
- 面向连接,一对一通信。
- 可靠交付:保序、不重复、不丢失。
- 全双工通信:双方均可收发。
- 和上层应用进程的交互方式是 面向字节流的。
TCP 和上层应用进程的交互方式:
- 发送方可以发送不同大小的数据块到发送缓存。
- 先会对发送缓存内的数据分段,打包成 TCP 数据包再发送。
- 在接收一侧,数据包会先进入接收缓存区。
- 当一定数量的数据包全部到达,重排、组装,以数据流方式吐给接收方。
虽然 TCP 是面向字节流的,但是 TCP 所处理的数据单元却是面向报文段的, 也就是本文所说的数据包。
将看到,短短 可靠交付 四字背后并不简单。
可靠传输的基础机制 ¶
若不止考虑 TCP 协议本身的实现,如何设计可靠的网络通信?
首先,由于 丢包的可能性,要实现可靠通信:
- 发送方要知道对方接收成功,因此需要接收方回复确认 即 ACK。
- 如果丢包发生,发送方需要重传。
一种触发重传的方式是,超时重传 (也有其他触发方法,见后续 TCP 重传机制 )。
此外,无论往返中哪个包丢失或延迟, 发送方认为对方没有收到,就会重传 。
网络延时发生时,重传可能会导致重复:
- 接收方会丢弃收到的重复数据包,但是仍然回复确认。
- 发送方会丢弃收到的重复确认包。
其次,对于如何发送确认包和重传包,有两种方式。
一问一答的方式
也叫做 停止并等待 ARQ 协议, 是指 发送方等到接收方的确认包后,再发下一个数据包 。
类似乒乓方式,具体来说:
- 如果时限内收到对方确认,才发送下一个数据包。
- 否则,重传当前数据包。
可以看到这种方式下,发送方大部分时间在等待,效率非常低。
流水线传输的方式
叫做 连续 ARQ 协议 , 是指 发送方会连续发送一组数据包,同时等待这些数据包的确认 。
具体来说:
- 发送方发送一批数据包。
- 同步地接收对方的确认包。
简单来说, 发送方不闲着,一边发送,一边等回复 。
可以看到这种方式相对一问一答的方式,效率要高。
如果发生丢包或延时,需要重传,有两种方式:
回退 N 重传
发送方每发送一个数据包,都会发起一个定时器。
一旦一个某个定时器触发,就会重传。
发送指针回退到未拿到确认的数据包处,以实现重传。
可以看到,此方法下, 会重传后面所有的数据包。
选择重传
同样,每发送一个数据包,都会发起一个定时器。
不同点仅在于, 只重传未拿到确认的数据包,不回退发送指针。
综合以上,得出的结论是,不考虑 TCP 协议的具体实现的话, 要实现可靠的网络通信,需要依赖确认和重传机制, 并且一个好的办法是采用流水线传输的方式。
而流水线传输方式,正是下一个部分 TCP 协议中的 滑动窗口机制 的引子。
TCP 滑动窗口机制 ¶
滑动窗口机制是 TCP 协议的精髓所在,它是 TCP 协议设计的基本框架 。
滑动窗口机制就是 流水线传输方式 在 TCP 协议中的细化设计, 发送方一边连续地发送数据包,一边等待接收方的确认。
由于 TCP 是全双工的, 所以通信的每一端都会同时维护两种窗口 。
数据包序号
在 可靠传输的基础机制 中, 接收的数据包可能是重复的、乱序的,因此 TCP 会对每一个数据包进行唯一标号, 叫做数据包的序号。
数据包的序号是 TCP 协议头 中的一个 32
比特大小的整数字段。
每次发送一个包,这个序号就会增加一。
TCP 是全双工的,两个通信端各自维护自己的序号。
因为 网络延时不可控, 如果两次连接建立时差很短、或者连接重建后老连接的数据包延迟到达, 会造成序号冲突。
所以,序号并非由固定数字初始化。可以综合时间、随机数来生成等。
确认号和累计确认
在 TCP 中,一个用以确认的回复包,会有确认号。
如果一个数据包同时也是一个确认包,那么它也会有确认号。
一个序号为 SEQ
的数据包,其确认包的确认号会是 SEQ+1
。
同样,确认号也是 TCP 协议头 中的一个 32
比特大小的整数字段。
可以理解为,接收方已收到序号为 SEQ
的数据包,期待发送方下一次给 SEQ+1
的包。
更广义的理解是, 确认号是接收方期望对方发送的下一个包的序号 。
下图中,发送方连续发送一组包,如果中间有丢包, 接收方则期待序号最小的丢失的包。当重传成功后, 接收方仍然期待下一个未拿到的数据包:
接收方所期待的是序号最小的没拿到的数据包 。
这种确认号的机制,即可实现累计确认机制:
接收方确认了标号为 SEQ
的数据包, 即代表确认了所有小于 SEQ
的数据包, 此时接收方给的确认号是 SEQ+1
。
累计确认其实是一种批量确认的机制,以减少确认包的数量。
此外,如果接收方恰好需要发送数据,确认号可以直接标在数据包上,即捎带确认。
一个问题是,如何控制累计确认的时机?
TCP 协议中的 Nagle 算法 给出的办法是 延迟确认。
其大概的原理是,未确认的包达到一定量、或者达到一个时间阈值,才回复一次确认。
所以说, 在默认的 TCP 协议中,确认不是立即回复的,而是延迟的 。
不过,接收方的延迟确认不应该过分延迟,否则会造成发送方的重传,浪费网络资源。
可以设置 TCP_NODELAY 选项 来禁用 Nagle 算法。
TCP 的累计确认机制,是累计确认和延迟确认两个策略的综合。
此外,TCP 协议还有另外一种确认机制,叫做 选择确认机制 ,将会后面讲到。
发送机制
已经讲过,TCP 协议默认的 Nagle 算法 采用了延迟确认的方法。
对应的,发送的时机如何确定?
办法是类似的,大概是,未发送的包达到一定量、或者达到一个时间阈值,才发送一次。
TCP 的发送机制,是累计发送和延迟发送两个策略的综合。
同样可以设置 TCP_NODELAY 选项 来关闭延迟发送的行为。
发送窗口
发送窗口的示意图如下,当收到对方的累计确认后,则向右滑动。
注意的是 窗口大小 是有限的(稍后将讨论它的受限情况), 发送方只能发送窗口内的数据包 。
接收窗口
接收窗口的示意图如下,当回复对方确认后,则向右滑动。
仍需注意 窗口大小 是有限的(稍后将讨论它的受限情况), 接收方只能接收窗口内的数据包 。
由于 网络数据包是乱序的 , 所以接收后的数据包会按照序号重新排序,才可以交付给应用程序。
窗口大小
窗口的大小是受限的,也是动态的 。
窗口大小是 TCP 协议头 中的一个 16
比特大小的整数字段。
首先,显然 接收窗口受限于接收缓存区的大小, 发送窗口受限于发送缓存区的大小 。
其次指出, 发送窗口大小也受限于接收窗口大小 。
这其实比较容易理解,发送方是生产者,接收方是消费者, 如果生产速度快而消费速度慢,则会导致接收方来不及接收。
所以,发送方只能发送窗口内的数据包 。
其实,这就是 流量控制机制 : 由接收方控制的、调节发送方生产速度的机制 , 其具体的实现方式,就是在回复时设置 TCP 协议头 中的窗口大小字段。
窗口的大小也是动态变化的, 因为两端接收和发送能力是动态变化的 :
- 接收能力的变化导致窗口大小的变化,即后面所讲的 TCP 流量控制机制。
- 发送能力的变化导致窗口大小的变化,即后面所讲的 TCP 拥塞控制机制。
可以看到,滑动窗口的大小是一个贯穿式的重要概念。
但是,如果窗口要缩小,窗口的前沿是不可以向前收缩的。
窗口收缩的方法则是,慢慢地,随着已发送的数据包得到确认, 保持窗口前沿不动,前移窗口后沿。
因此, 发送窗口也并不总和接收窗口一样大 。
窗口大小的初始化,是在 连接建立 过程中两端协商确定的, 在后续的传输阶段,它会因主动或被动的原因而动态变化。
选择确认机制
和前面所讲的 累计确认机制 一样, 都是 TCP 协议中用来控制如何回复确认包的机制。
因为网络丢包、延时、乱序的不确定性,序号大的包可能先到达。
如果仅采用累计确认机制,发送方并不知道大号的包传输成功了, 它只会执行 回退 N 重传 , 将后面的所有包重传。
下图中,如果接收方的回复包丢失或延迟、 或发送方的速率较快,就会导致短时间内多次无效重传。 接收方则不得不丢弃重复数据。 造成信道资源浪费。
选择确认机制,简记作 SACK,是指 接收方在回复确认包 ACK 的同时, 告诉对方已收到数据包的序号区间 。
比如,在回复对方 ACK=10
的时候,同时回复 SACK=20,39
, 这表示接收方已经收到 20~39
序号的数据包。 发送方只需要重传 10~19
就可以了。
下图中同样的场景,可以看到开启选择确认机制, 可以一定程度上优化无意义的重传。
如果接收方采用了选择确认机制, 那么发送方就可以采用前面所讲的 选择重传 。
所以,选择确认机制是搭配选择重传机制一块使用的。
这个机制是可选的,需要两端都支持,在 TCP 连接建立 阶段, 两端协商确定是否采用此机制。
TCP 重传机制 ¶
重传机制是 TCP 协议中比较复杂的部分。
其具体实施方式又有两种,前面所讲的 回退 N 重传 和 选择重传 。
超时重传和超时计算
前面 可靠传输的基础机制 有讲到超时重传, TCP 协议每发送一个数据包,就对这个数据包设置一次计时器 ,超时即触发重传。
超时重传中的一个问题是:超时的时间是多少呢?
这是 TCP 协议中又一个复杂的问题。
- 如果超时重传时间设置太短,会引起不必要的重传。
- 如果设置太长,会使得网络空闲时间增大,降低传输效率。
TCP 采用一种自适应算法,具体地,是一种加权平均的方法。
首先,记录每个数据包发出去后到收到确认包的时间差,叫做往返时间 RTT 。
具体的,假设加权平均后的数据叫做 $T$ ,其计算方式:
\[{T}_{n} = (1 - \alpha) \times {T}_{n-1} + \alpha \times {RTT}_{n}\]其中 $ 0 \leq \alpha < 0$ 。
这个计算式的意思是,一部分权重考虑实时数据,另一部分权重考虑历史沉淀。
可以知道,$\alpha$ 越大,新数据的贡献越大,$T$ 跟随 $RTT$ 的就越快。 反之,$\alpha$ 越小,新数据的贡献越小,$T$ 跟随 $RTT$ 的就越慢。
按这个计算式迭代下去,历史数据的权重会越来越小,$T$ 会跟随实时数据的趋势。
同时,由于综合了历史数据,又可以消除毛刺。
快速重传机制
TCP 的另一种重传机制,是快速重传机制,它不再以超时作为触发标准, 而是观察确认包的情况。
具体来说, 如果收到同一个数据包的多次确认,立即发起重传 。
一般取 3
次作为阈值,常叫做 3ACK 方法。
快速重传机制的作用在于,将有可能在超时触发之前,提前发起重传。
细节地, 因为每发送一个数据包,就会对这个包设置一次定时器, 所以,快速重传触发后不会造成超时重传的重新触发。
TCP 流量控制机制 ¶
前面 窗口大小 部分已经提及, 如果数据发送过快,接收方就会来不及接收。
流量控制,就是接收方调控对方的发送速度不要太快的机制 。
发送窗口的大小受限于接收窗口 , 接收方在回复时通过设置 TCP 协议头 中的窗口大小字段, 来限制发送方的发送窗口大小。
需要注意的是, 流量控制过程是动态的 。
其原因在于,两端的发送和接收数据的能力是动态变化的 。
以接收方为例, 哪怕我们假设缓存区的大小未发生变化,上层应用程序读取数据数据的能力也是动态变化的。
比如,接收方应用程序因为负载升高,无法及时读取缓存区数据, 导致已回复数据包大量积压,从而挤压接收窗口的大小。
零窗口死锁问题
一个特殊的场景是,如果接收方的窗口变为 0
,发送方的窗口也会被限制到 0
。
此时,发送方窗口是 0
,无法发送新数据包。而接收方在等待数据包到来, 也不会回复任何确认包,即使接收方的窗口已经可以变大,也没办法告知对方。
这样,两端都在等待,造成零窗口死锁问题。
在 TCP 协议中,打破死锁的方法,是采用 持续计时器机制。
具体来说,发送方收到零窗口通知时,启动计时器, 当计时器超时,就发送一个 仅携带一个字节数据的探测数据包 。
如此,接收方就可以回复确认包,如果此时接收方的窗口可以恢复, 双方就回到正常轨道。
一个细节是,在发送探测数据包时,持续定时器会重新开始计时。 也就是说, 发送方会周期性地进行窗口探测,这样可以应对探测数据包丢失的情况 。
如果接收方拿到探测数据包后, 仍坚持零窗口, 那么发送方的计时器就重新计时。 如此往复一定次数后仍无法恢复,则关闭连接。
糊涂窗口综合征
糊涂窗口综合征 的通俗理解是, 双方处理速度不一致,会导致通信演化为小包通信的问题 。
如果接收方的处理速度跟不上发送方的生产速度, 最终接收方的数据积压在缓存区来不及拿走,接收窗口宣告为 0
, 此时发送方停止发送。
一旦接收方的应用进程处理了一个字节,接收窗口变为 1
, 发送方就可以发送一个字节。
如果发送方总是足够快地填充接收方的窗口, 这个过程会不断循环下去,导致每次通信的数据包都只有一个字节, 显然降低了通信效率。
此问题的解决措施有多种,其中包括 Nagle 算法的 累计与延迟确认 和 累计与延迟发送 。
- 接收方 过一定时间 或 累计一定数量的包 后再确认。
- 发送方 过一定时间 或 累计一定数量的包 后再发送。
此外,还有一种关键的措施,就是 延迟宣告窗口 。
大概意思是,接收方的窗口稍稍变大时,不要急于告知对方, 而是达到一定阈值才告知对方,如此发送方就会等接收方窗口足够大时将数据包一并发送, 从而解决小包传输问题。
或者是,接收方的窗口变的足够小的时候,直接宣告窗口关闭,阻止发送方再发数据。 直到接收窗口可以变的足够大,再恢复大包传输。
下图是延迟宣告窗口的一种推演过程。
可以看出, 窗口探测实际是探测一个足够大的窗口,而不是完全非零的窗口 。
TCP 拥塞控制机制 ¶
网络拥塞是指,对网络中某种资源的总需求量大于总可用量的情况。
直接的理解就是, 要传输的数据量超过网络负荷 。
在网络拥塞的情况下,TCP 协议的重传机制反而会加剧拥塞情况。
虽然网络拥塞是一个全局的、宏观的问题, 但是 TCP 协议对网络拥塞的缓解措施,是从个体角度出发的。
简而言之,TCP 协议的拥塞控制的办法是, 发送方主动减少发送量 。
拥塞控制和 流量控制 是不同的事情:
- 拥塞控制是发送方主动减少数据传输,解决的宏观网络的超负荷问题的机制。
- 流量控制是接收方调控发送方数据传输量,以平衡生产消费速度的机制。
网络拥塞的判断
拥塞判断的方法有许多种,这里主要说明两种。
这两种就是 TCP 协议中重传机制的两种触发方式 。
超时标准
前面 超时重传和超时计算 部分有讲, TCP 协议每发送一个数据包,就会对它设置一次计时器。
当超时发生时,意味着数据包丢失、或延迟,此时网络可能发生拥塞。
3ACK 标准
前面 快速重传机制 部分有讲, 如果收到同一个数据包的多次确认,意味着数据包丢失、或延迟, 此时网络可能发生拥塞。
拥塞窗口算法
TCP 协议仍然基于滑动窗口的机制,进行拥塞控制。
发送方会维护一个拥塞窗口 CWND ,此窗口范围内的数据才允许被发送。
因此实际的发送窗口的大小是:Min(对方接收窗口大小,自身拥塞窗口大小)
。
拥塞窗口算法就是如何调整拥塞窗口大小的办法。
它主要有四个策略:慢开始、拥塞避免、快重传、快恢复。
虽然有四个策略,但是它们其实是 一套算法。
首先,「快重传」就是前面所讲的 快速重传 , 也就是说, 如果遭遇 3ACK ,就立即发起重传 。
慢开始
发送方每收到一个对新数据包(就是不包括重传包)的确认, 就让拥塞窗口增一。
虽然叫做慢开始,但是其实并不慢, 拥塞窗口是倍增的 。
简而言之,如果没有遭遇超时 或 3ACK, 拥塞窗口就扩大一倍。
拥塞避免
慢开始的增长速度是非常快的, 发送量越大,就离拥塞越近。
所以有一个门限值 ssthresh 的概念。
拥塞窗口倍增到门限值后,就改为线性增长,比如每次增大一 。
其后,线性增大拥塞窗口的过程,就是拥塞避免阶段。
慢开始和拥塞避免,都是在探测拥塞边缘 : 一开始疯狂试探,到达门限值后,慢慢试探。
拥塞控制的惩罚
如果出现 拥塞的征兆,拥塞窗口的大小会受到惩罚,让它减小。
超时条件
超时未确认,意味着发生丢包或者较大延迟,所以处罚更为严厉。
拥塞窗口打回初始值
1
, 门限值改为当前拥塞窗口大小的一半。总而言之,从新进入慢启动 。
3ACK 条件
收到 3 个相同的确认包,意味着发生少许延迟,所以处罚相对柔和。
同样,慢启动的门限值减半。
不同的是,此时不必要从新进入慢启动, 而是减少一些拥塞窗口的值,跳过慢启动,直接重新进入拥塞避免阶段。
减少到多少呢?减少到最新门限值大小,也就是从新进入拥塞避免。
这种直接跳过慢启动,直接进入拥塞避免的方式,就叫做 快恢复 。
拥塞控制算法总览
总结如下:
- 慢开始阶段,倍增到门限值,激进探测拥塞边缘。
- 到达门限值,进入拥塞避免,线性增加,小心探测拥塞边缘。
- 遭遇超时,惩罚严厉,从新进入慢开始。
遭遇 3ACK,惩罚柔和,快恢复,门限减半,直接进入拥塞避免。
同时,3ACK 时,立即重传,即 快重传。
TCP 连接建立和释放 ¶
TCP 的三次握手和四次握手,已经是老生常谈了。
连接建立 - 三次握手
首先,TCP 连接建立过程要解决的问题:
下面是 TCP 协议三次握手建立连接的过程示意图,其中
SYN
(synchronous) 是同步标志位 ,表示开始传输数据的意思。ACK
(acknowledgement) 是确认标志位,表示是否启用确认机制。ack
(小写)是前面所说的 确认号。seq
是前面所说的 序号 。
具体过程的描述如下:
客户端发送一个 TCP 数据包,主动发起连接。
设置
SYN=1
标志位,表示发起连接,想要传输数据。客户端初始化自己的数据包序号
seq=x
,一同发送给对方。服务端收到对方的连接请求,回复对方确认包。
设置回复标志位
ACK=1
,表示确认。根据 确认号的规则, 同时设
ack=x+1
表示期待对方下一次发过来的数据包序号为x+1
。因为 TCP 是全双工的,服务端也会设置
SYN=1
, 并初始化自己的数据包序号seq=y
,捎带回复给客户端。此时,服务端可以确定: 客户端可以发送 TCP 数据包,自身可以接收 TCP 数据包 。
客户端收到对方的确认包,并回复对方确认。
同样,设置回复标志位
ACK=1
,表示确认。对方的序号是
y
, 回复对方ack=y+1
表示期待对方下一次发来的数据包序号为y+1
。同时,自身的数据包序号需要自增
seq=x+1
。自己发送上一个数据包,对方收到了,并且收到了对方回复。
所以客户端可以确定: 服务端可以正常收发 TCP 数据包, 自身也可以正常收发 TCP 数据包 。
服务端收到客户端的确认,三次握手结束。
此时服务端可以进一步确定:
客户端端可以正常接收 TCP 数据包, 自身也可以正常发送 TCP 数据包 。
三次握手结束后,双方都可以确定对方是可以正常收发 TCP 数据的。
三次握手的必要性
一个经常出现的问题是: 两次握手可以吗?
当然是不可以的,可以分几个方面解释:
三次握手是为了确定双方的收发能力
如果缺少最后一次
ACK
的话,服务端就无法知道客户端是否可以接收 TCP 数据, 也无法知道自己的发送是否成功,就无法做到可靠传输。序号同步的确定性
如果缺少最后一次
ACK
的话,服务端就无法确定对方有无收到自己的初始序号。前面讲到,数据包的序号是 TCP 滑动窗口机制的基本字段,它如果是不可靠的, 后续的确认机制、重传机制等都无从谈起。
网络是不可靠的,存在丢包,所以连接建立时, 双方都必须确定初始序号交到了对方手中。
三次握手过程,也是可靠地交换初始序号的过程 。
历史失效连接请求的乱序问题
网络中,数据传输可能存在延迟、乱序。
如果一个老的失效的连接请求,延迟到达服务端,倘若缺少第三次握手, 服务端会建立一条新连接,直接进入
ESTABLISHED
状态。客户端收到服务端的回复包时,可以根据序号是否过期判断是否是失效连接。
在三次握手的情况下,客户端就进一步可以发送
RST
标志,终止连接。如果不是历史失效连接,客户端则发送
ACK
标志,正常建立连接。
三次握手中丢包的情况
另一个常见的问题是: 如果第三次 ACK 丢包呢?
无论哪一次通信发生丢包或延迟,都出发 TCP 的 重传机制 。
简单说,再发一次,如果达到一定次数,则放弃连接。
对于第三次 ACK
丢包的情况,重试一定次数后,服务端会关闭连接,回收资源。
不过,服务端单方面关闭连接,客户端并不知晓,它认为连接已经建立。
如果客户端向服务端发送数据,会被服务端打回 RST
报文, 借此客户端可知道连接失效。
数据传输的开始时机
上面的 三次握手图示 中,一个细节是:
- 客户端在两次握手之后就进入了
ESTABLISHED
状态。 - 服务端则是在三次握手之后才进入
ESTABLISHED
状态。
其原因在于, 在 第二次握手 后, 客户端已经可以确定双方都可以正常收发数据包 。
所以,客户端可以提前进入 ESTABLISHED
状态,分配端口,开始通信。
因此, 第三次 ACK
是可以捎带客户端的数据一并发送的 。
服务端直到 第三次握手 成功之后,才可以确定双方都正常, 所以它会稍晚进入 ESTABLISHED
。
连接关闭 - 四次挥手
TCP 的连接释放过程,需要注意两个点:
- TCP 是全双工的。
- 双方都可以主动释放连接。
释放连接的过程有一个重要的目的:
双方都要确定地知道对方不再发送数据了,连接才正式关闭 。
假设客户端主动关闭,下面是 TCP 协议四次挥手释放连接的过程示意图。
其中 FIN
(finish)是结束数据传输的标志位。
具体过程的描述如下:
客户端发送
FIN
数据包,不再发送数据。设置
FIN=1
标志位,表示想要关闭连接,不再发送数据。但是,此时服务端还可以继续发送数据 。
服务端收到
FIN
数据包,回复确认 。服务端对于
FIN
包也会进行确认。此时服务端明确知道,客户端不再发送数据了 。
服务端传输完剩余数据 。
由于 TCP 是全双工的, 连接关闭由客户端主动发起,并不意味着服务端的数据已传输完。
服务端需要把剩余数据传输完毕。
此时服务端知道,双方都不再发送数据了。 。
服务端发送
FIN
数据包,不再发送数据包 。同样,设置
FIN=1
标志位,表示服务端不再发送数据。客户端收到对方
FIN
包后,回复确认 。任何一方结束数据发送,都要确保对方是知道的,就需要对方确认。
此时客户端知道,双方都不再发送数据了。
客户端等待
2MSL
时间等待后,四次挥手结束,释放所有资源
再次明确, FIN
标志位的意思是,不再发送数据 。
挥手四次的必要性
双方都必须对 FIN
包做确认, 这样对方才可以确定地知道自己不再发送数据。
TCP 连接的建立需要三次握手,是因为, 第二次握手过程服务端将设置 SYN
和 ACK
合并为一个数据包发送,所以是三次 。
而对于释放连接的情况, 无法将第二次和第三次合并, 因为中间还要传输数据, 所以是四次。
如果任何一个确认包发生丢包或延迟,主动设置 FIN
的一方会触发重传。
等待 2MSL 时间的原因
首先,MSL
是指报文在网络中最大生存时间。
2MSL
就是两倍的 MSL
,也被叫做 TIME_WAIT
时间, 在 linux 中常被设置为 2*60s
。
2MSL
的值一定需要大于重传超时阈值 。
为何要等待这么长时间呢?
如果最后一次
ACK
丢包呢?前面有说过,任何一方的
FIN
包丢失后,如果超时未收到对方确认, 就会触发重传。如果客户端在一定时间内未收到服务端的
FIN
包重传,说明对方已经收到ACK
。否则,如果收到服务端的
FIN
包重传,自然回复ACK
。一次
ACK
包的发送时间,再算上重传FIN
的时间,所以叫做2MSL
。不过,无论如何,
2MSL
时间至少要比重传超时阈值长。防止新老连接数据包混乱
如果立即释放端口资源,此端口可能被一个新连接立即使用。
如此的话,假设网络中存在老数据包的延迟,那么老数据包会被新连接接收,造成混乱。
所以, 等待一定时间,使得网络中潜在的、可能延迟传输的数据包悉数殆尽, 才不会影响到后面的连接。
综合来看,还是因为网络是不可靠的。
TCP 连接的有限状态机
综合连接建立和连接关闭的过程,整个 TCP 连接的状态机图示如下。
两个常见的问题:
TIME_WAIT
非常多的情况已经知道,
TIME_WAIT
是主动关闭连接一方,发送完最后一次ACK
数据包后进入的状态。并且
TIME_WAIT
的时间默认是120s
。TIME_WAIT
非常多,说明 连接建立的并发数比较大,端口来不及回收。比如,对于高并发的 TCP 短连接,例如 未开启 keep-alive 选项 的 HTTP 短连接。
补充,HTTP 协议中是服务端主动关闭连接 。
CLOSE_WAIT
非常多的情况已经知道,
CLOSE_WAIT
是被动关闭连接的一方,传输最后的剩余数据的状态。它是当服务端收到对方
FIN
包时,主动进入的一个状态。所以,一般地,是由于应用程序未调用或太忙未来得及调用
close
关闭连接导致。比如程序编写遗忘连接关闭调用代码,或者负载过高,未及时执行关闭连接, 导致
CLOSE_WAIT
状态的socket
积压。
TCP 协议头 ¶
TCP 的协议头部总共有 20
个字节。
下面是 TCP 协议头的栏位示意图,其中大部分字段已经在上面讲到。
概念图谱总览 ¶
最后,对本文的知识做一个树状图梳理。
结语 ¶
纵观全文,TCP 协议的众多机制源于,万恶之源:网络是不可靠的,丢包、乱序、延时。
(完)
本文原始链接地址: https://writings.sh/post/network-tcp