计算机网络 – TCP 协议原理总结

2021年8月16日14:26:28 发表评论 72 views

本文总结 TCP 协议的原理,包括:

本文笔记目的,内容较多。 TCP 协议非常复杂, 读完本文需要许多耐心 

网络是不可靠的 

计算机网络是不可靠的,存在 丢包乱序延时 

这是众多 TCP 协议机制的设计出发点,万恶之源,将贯穿全文。

TCP 概念和特点 

TCP 协议全称 传输控制协议, 是一种 面向连接的可靠的面向字节流的 传输层通信协议。

TCP 在 TCP/IP 协议模型 中位于传输层。

计算机网络 - TCP 协议原理总结
图 1.1 - TCP 位于 TCP/IP 分层中的传输层

TCP/IP 模型中各层数据包结构的关系如下图。

计算机网络 - TCP 协议原理总结
图 1.2 - TCP/IP 模型中各层数据包结构的关系

TCP 协议的主要特点:

  1. 面向连接,一对一通信。
  2. 可靠交付:保序、不重复、不丢失。
  3. 全双工通信:双方均可收发。
  4. 和上层应用进程的交互方式是 面向字节流的

TCP 和上层应用进程的交互方式:

  1. 发送方可以发送不同大小的数据块到发送缓存。
  2. 先会对发送缓存内的数据分段,打包成 TCP 数据包再发送。
  3. 在接收一侧,数据包会先进入接收缓存区。
  4. 当一定数量的数据包全部到达,重排、组装,以数据流方式吐给接收方。
计算机网络 - TCP 协议原理总结
图 1.3 - TCP 和应用程序的交互方式

虽然 TCP 是面向字节流的,但是 TCP 所处理的数据单元却是面向报文段的, 也就是本文所说的数据包。

将看到,短短 可靠交付 四字背后并不简单。

可靠传输的基础机制 

若不止考虑 TCP 协议本身的实现,如何设计可靠的网络通信?

首先,由于 丢包的可能性,要实现可靠通信:

  1. 发送方要知道对方接收成功,因此需要接收方回复确认 即 ACK。
  2. 如果丢包发生,发送方需要重传。

一种触发重传的方式是,超时重传 (也有其他触发方法,见后续 TCP 重传机制 )。

此外,无论往返中哪个包丢失或延迟, 发送方认为对方没有收到,就会重传 

计算机网络 - TCP 协议原理总结
图 2.1 - 确认和超时重传机制的几种情况

网络延时发生时,重传可能会导致重复:

  1. 接收方会丢弃收到的重复数据包,但是仍然回复确认。
  2. 发送方会丢弃收到的重复确认包。

其次,对于如何发送确认包和重传包,有两种方式。

一问一答的方式

也叫做 停止并等待 ARQ 协议, 是指 发送方等到接收方的确认包后,再发下一个数据包 

类似乒乓方式,具体来说:

  1. 如果时限内收到对方确认,才发送下一个数据包。
  2. 否则,重传当前数据包。
计算机网络 - TCP 协议原理总结
图 2.2 - 一问一答的方式

可以看到这种方式下,发送方大部分时间在等待,效率非常低。

流水线传输的方式

叫做 连续 ARQ 协议 , 是指 发送方会连续发送一组数据包,同时等待这些数据包的确认 

具体来说:

  1. 发送方发送一批数据包。
  2. 同步地接收对方的确认包。

简单来说, 发送方不闲着,一边发送,一边等回复 

计算机网络 - TCP 协议原理总结
图 2.3 - 流水线传输的方式

可以看到这种方式相对一问一答的方式,效率要高。

如果发生丢包或延时,需要重传,有两种方式

  1. 回退 N 重传

    发送方每发送一个数据包,都会发起一个定时器。

    一旦一个某个定时器触发,就会重传。

    发送指针回退到未拿到确认的数据包处,以实现重传。

    计算机网络 - TCP 协议原理总结
    图 2.4 - 回退 N 重传的方式

    可以看到,此方法下, 会重传后面所有的数据包

  2. 选择重传

    同样,每发送一个数据包,都会发起一个定时器。

    不同点仅在于, 只重传未拿到确认的数据包,不回退发送指针。

    计算机网络 - TCP 协议原理总结
    图 2.5 - 选择重传的方式

综合以上,得出的结论是,不考虑 TCP 协议的具体实现的话, 要实现可靠的网络通信,需要依赖确认和重传机制, 并且一个好的办法是采用流水线传输的方式。

而流水线传输方式,正是下一个部分 TCP 协议中的 滑动窗口机制 的引子。

TCP 滑动窗口机制 

滑动窗口机制是 TCP 协议的精髓所在,它是 TCP 协议设计的基本框架 

滑动窗口机制就是 流水线传输方式 在 TCP 协议中的细化设计, 发送方一边连续地发送数据包,一边等待接收方的确认。

滑动窗口分为两种:发送窗口 和 接收窗口

由于 TCP 是全双工的, 所以通信的每一端都会同时维护两种窗口 

数据包序号

在 可靠传输的基础机制 中, 接收的数据包可能是重复的、乱序的,因此 TCP 会对每一个数据包进行唯一标号, 叫做数据包的序号。

数据包的序号是 TCP 协议头 中的一个 32 比特大小的整数字段。

每次发送一个包,这个序号就会增加一。

TCP 是全双工的,两个通信端各自维护自己的序号。

因为 网络延时不可控, 如果两次连接建立时差很短、或者连接重建后老连接的数据包延迟到达, 会造成序号冲突。

所以,序号并非由固定数字初始化。可以综合时间、随机数来生成等。

确认号和累计确认

在 TCP 中,一个用以确认的回复包,会有确认号。

如果一个数据包同时也是一个确认包,那么它也会有确认号。

一个序号为 SEQ 的数据包,其确认包的确认号会是 SEQ+1 。

同样,确认号也是 TCP 协议头 中的一个 32 比特大小的整数字段。

可以理解为,接收方已收到序号为 SEQ 的数据包,期待发送方下一次给 SEQ+1 的包。

更广义的理解是, 确认号是接收方期望对方发送的下一个包的序号 

下图中,发送方连续发送一组包,如果中间有丢包, 接收方则期待序号最小的丢失的包。当重传成功后, 接收方仍然期待下一个未拿到的数据包:

计算机网络 - TCP 协议原理总结
图 3.1 - 接收方的确认号

接收方所期待的是序号最小的没拿到的数据包 

这种确认号的机制,即可实现累计确认机制:

接收方确认了标号为 SEQ 的数据包, 即代表确认了所有小于 SEQ 的数据包, 此时接收方给的确认号是 SEQ+1 

计算机网络 - TCP 协议原理总结
图 3.2 - 累计确认机制

累计确认其实是一种批量确认的机制,以减少确认包的数量。

此外,如果接收方恰好需要发送数据,确认号可以直接标在数据包上,即捎带确认。

一个问题是,如何控制累计确认的时机?

TCP 协议中的 Nagle 算法 给出的办法是 延迟确认

其大概的原理是,未确认的包达到一定量、或者达到一个时间阈值,才回复一次确认。

所以说, 在默认的 TCP 协议中,确认不是立即回复的,而是延迟的 

不过,接收方的延迟确认不应该过分延迟,否则会造成发送方的重传,浪费网络资源。

可以设置 TCP_NODELAY 选项 来禁用 Nagle 算法。

TCP 的累计确认机制,是累计确认和延迟确认两个策略的综合。

此外,TCP 协议还有另外一种确认机制,叫做 选择确认机制 ,将会后面讲到。

发送机制

已经讲过,TCP 协议默认的 Nagle 算法 采用了延迟确认的方法。

对应的,发送的时机如何确定?

办法是类似的,大概是,未发送的包达到一定量、或者达到一个时间阈值,才发送一次。

计算机网络 - TCP 协议原理总结
图 3.2.1 - Nagle 算法的发送机制

TCP 的发送机制,是累计发送和延迟发送两个策略的综合。

同样可以设置 TCP_NODELAY 选项 来关闭延迟发送的行为。

发送窗口

发送窗口的示意图如下,当收到对方的累计确认后,则向右滑动。

计算机网络 - TCP 协议原理总结
图 3.3 - 发送窗口

注意的是 窗口大小 是有限的(稍后将讨论它的受限情况), 发送方只能发送窗口内的数据包 

接收窗口

接收窗口的示意图如下,当回复对方确认后,则向右滑动。

计算机网络 - TCP 协议原理总结
图 3.4 - 发送窗口

仍需注意 窗口大小 是有限的(稍后将讨论它的受限情况), 接收方只能接收窗口内的数据包 

由于 网络数据包是乱序的 , 所以接收后的数据包会按照序号重新排序,才可以交付给应用程序。

窗口大小

窗口的大小是受限的,也是动态的 

窗口大小是 TCP 协议头 中的一个 16 比特大小的整数字段。

首先,显然 接收窗口受限于接收缓存区的大小, 发送窗口受限于发送缓存区的大小 

其次指出, 发送窗口大小也受限于接收窗口大小 

这其实比较容易理解,发送方是生产者,接收方是消费者, 如果生产速度快而消费速度慢,则会导致接收方来不及接收。

计算机网络 - TCP 协议原理总结
图 3.5 - 生产者和消费者

所以,发送方只能发送窗口内的数据包 。

其实,这就是 流量控制机制 : 由接收方控制的、调节发送方生产速度的机制 , 其具体的实现方式,就是在回复时设置 TCP 协议头 中的窗口大小字段。

窗口的大小也是动态变化的, 因为两端接收和发送能力是动态变化的 

  1. 接收能力的变化导致窗口大小的变化,即后面所讲的 TCP 流量控制机制
  2. 发送能力的变化导致窗口大小的变化,即后面所讲的 TCP 拥塞控制机制

可以看到,滑动窗口的大小是一个贯穿式的重要概念。

但是,如果窗口要缩小,窗口的前沿是不可以向前收缩的。

窗口收缩的方法则是,慢慢地,随着已发送的数据包得到确认, 保持窗口前沿不动,前移窗口后沿。

计算机网络 - TCP 协议原理总结
图 3.6 - 窗口前沿不可以收缩

因此, 发送窗口也并不总和接收窗口一样大 

窗口大小的初始化,是在 连接建立 过程中两端协商确定的, 在后续的传输阶段,它会因主动或被动的原因而动态变化。

选择确认机制

和前面所讲的 累计确认机制 一样, 都是 TCP 协议中用来控制如何回复确认包的机制。

因为网络丢包、延时、乱序的不确定性,序号大的包可能先到达。

如果仅采用累计确认机制,发送方并不知道大号的包传输成功了, 它只会执行 回退 N 重传 , 将后面的所有包重传。

下图中,如果接收方的回复包丢失或延迟、 或发送方的速率较快,就会导致短时间内多次无效重传。 接收方则不得不丢弃重复数据。 造成信道资源浪费。

计算机网络 - TCP 协议原理总结
图 3.7 - 累计确认机制的无必要重传

选择确认机制,简记作 SACK,是指 接收方在回复确认包 ACK 的同时, 告诉对方已收到数据包的序号区间 

比如,在回复对方 ACK=10 的时候,同时回复 SACK=20,39 , 这表示接收方已经收到 20~39序号的数据包。 发送方只需要重传 10~19 就可以了。

下图中同样的场景,可以看到开启选择确认机制, 可以一定程度上优化无意义的重传。

计算机网络 - TCP 协议原理总结
图 3.8 - 选择确认机制

如果接收方采用了选择确认机制, 那么发送方就可以采用前面所讲的 选择重传 。

所以,选择确认机制是搭配选择重传机制一块使用的。

这个机制是可选的,需要两端都支持,在 TCP 连接建立 阶段, 两端协商确定是否采用此机制。

TCP 重传机制 

重传机制是 TCP 协议中比较复杂的部分。

其触发方式有两种:超时重传 和 快速重传

其具体实施方式又有两种,前面所讲的 回退 N 重传 和 选择重传 。

超时重传和超时计算

前面 可靠传输的基础机制 有讲到超时重传, TCP 协议每发送一个数据包,就对这个数据包设置一次计时器 ,超时即触发重传。

超时重传中的一个问题是:超时的时间是多少呢?

这是 TCP 协议中又一个复杂的问题。

  1. 如果超时重传时间设置太短,会引起不必要的重传。
  2. 如果设置太长,会使得网络空闲时间增大,降低传输效率。

TCP 采用一种自适应算法,具体地,是一种加权平均的方法。

首先,记录每个数据包发出去后到收到确认包的时间差,叫做往返时间 RTT 。

计算机网络 - TCP 协议原理总结
图 4.1 - 往返时间的方差可能会很大

具体的,假设加权平均后的数据叫做 T ,其计算方式:

Tn=(1−α)×Tn−1+α×RTTn

其中 0≤α<0 。

这个计算式的意思是,一部分权重考虑实时数据,另一部分权重考虑历史沉淀。

可以知道,α 越大,新数据的贡献越大,T 跟随 RTT 的就越快。 反之,α 越小,新数据的贡献越小,T 跟随 RTT 的就越慢。

按这个计算式迭代下去,历史数据的权重会越来越小,T 会跟随实时数据的趋势。

同时,由于综合了历史数据,又可以消除毛刺。

快速重传机制

TCP 的另一种重传机制,是快速重传机制,它不再以超时作为触发标准, 而是观察确认包的情况。

具体来说, 如果收到同一个数据包的多次确认,立即发起重传 

一般取 3 次作为阈值,常叫做 3ACK 方法。

快速重传机制的作用在于,将有可能在超时触发之前,提前发起重传。

计算机网络 - TCP 协议原理总结
图 4.2 - 收到同一个数据包的 3 次确认即发起重传

细节地, 因为每发送一个数据包,就会对这个包设置一次定时器, 所以,快速重传触发后不会造成超时重传的重新触发。

TCP 流量控制机制 

前面 窗口大小 部分已经提及, 如果数据发送过快,接收方就会来不及接收。

流量控制,就是接收方调控对方的发送速度不要太快的机制 

发送窗口的大小受限于接收窗口 , 接收方在回复时通过设置 TCP 协议头 中的窗口大小字段, 来限制发送方的发送窗口大小。

需要注意的是, 流量控制过程是动态的 

其原因在于,两端的发送和接收数据的能力是动态变化的 

以接收方为例, 哪怕我们假设缓存区的大小未发生变化,上层应用程序读取数据数据的能力也是动态变化的。

比如,接收方应用程序因为负载升高,无法及时读取缓存区数据, 导致已回复数据包大量积压,从而挤压接收窗口的大小。

计算机网络 - TCP 协议原理总结
图 5.1 - 接收窗口缩小的示例
零窗口死锁问题

一个特殊的场景是,如果接收方的窗口变为 0 ,发送方的窗口也会被限制到 0 。

此时,发送方窗口是 0 ,无法发送新数据包。而接收方在等待数据包到来, 也不会回复任何确认包,即使接收方的窗口已经可以变大,也没办法告知对方。

这样,两端都在等待,造成零窗口死锁问题。

计算机网络 - TCP 协议原理总结
图 5.2 - 零窗口死锁问题

在 TCP 协议中,打破死锁的方法,是采用 持续计时器机制。

具体来说,发送方收到零窗口通知时,启动计时器, 当计时器超时,就发送一个 仅携带一个字节数据的探测数据包 

如此,接收方就可以回复确认包,如果此时接收方的窗口可以恢复, 双方就回到正常轨道。

计算机网络 - TCP 协议原理总结
图 5.3 - 持续计时器解决零窗口死锁问题

一个细节是,在发送探测数据包时,持续定时器会重新开始计时。 也就是说, 发送方会周期性地进行窗口探测,这样可以应对探测数据包丢失的情况 

如果接收方拿到探测数据包后, 仍坚持零窗口, 那么发送方的计时器就重新计时。 如此往复一定次数后仍无法恢复,则关闭连接。

糊涂窗口综合征

糊涂窗口综合征 的通俗理解是, 双方处理速度不一致,会导致通信演化为小包通信的问题 

如果接收方的处理速度跟不上发送方的生产速度, 最终接收方的数据积压在缓存区来不及拿走,接收窗口宣告为 0 , 此时发送方停止发送。

一旦接收方的应用进程处理了一个字节,接收窗口变为 1 , 发送方就可以发送一个字节。

如果发送方总是足够快地填充接收方的窗口, 这个过程会不断循环下去,导致每次通信的数据包都只有一个字节, 显然降低了通信效率。

计算机网络 - TCP 协议原理总结
图 5.4 - 糊涂窗口综合征

此问题的解决措施有多种,其中包括 Nagle 算法的 累计与延迟确认 和 累计与延迟发送 。

  1. 接收方 过一定时间 或 累计一定数量的包 后再确认。
  2. 发送方 过一定时间 或 累计一定数量的包 后再发送。

此外,还有一种关键的措施,就是 延迟宣告窗口 。

大概意思是,接收方的窗口稍稍变大时,不要急于告知对方, 而是达到一定阈值才告知对方,如此发送方就会等接收方窗口足够大时将数据包一并发送, 从而解决小包传输问题。

或者是,接收方的窗口变的足够小的时候,直接宣告窗口关闭,阻止发送方再发数据。 直到接收窗口可以变的足够大,再恢复大包传输。

下图是延迟宣告窗口的一种推演过程。

计算机网络 - TCP 协议原理总结
图 5.5 - 延迟宣告窗口 - 解决糊涂窗口综合征

可以看出, 窗口探测实际是探测一个足够大的窗口,而不是完全非零的窗口 

TCP 拥塞控制机制 

网络拥塞是指,对网络中某种资源的总需求量大于总可用量的情况。

直接的理解就是, 要传输的数据量超过网络负荷 

在网络拥塞的情况下,TCP 协议的重传机制反而会加剧拥塞情况。

虽然网络拥塞是一个全局的、宏观的问题, 但是 TCP 协议对网络拥塞的缓解措施,是从个体角度出发的。

简而言之,TCP 协议的拥塞控制的办法是, 发送方主动减少发送量 

拥塞控制和 流量控制 是不同的事情:

  • 拥塞控制是发送方主动减少数据传输,解决的宏观网络的超负荷问题的机制。
  • 流量控制是接收方调控发送方数据传输量,以平衡生产消费速度的机制。
网络拥塞的判断

拥塞判断的方法有许多种,这里主要说明两种。

这两种就是 TCP 协议中重传机制的两种触发方式 

  1. 超时标准

    前面 超时重传和超时计算 部分有讲, TCP 协议每发送一个数据包,就会对它设置一次计时器。

    当超时发生时,意味着数据包丢失、或延迟,此时网络可能发生拥塞。

  2. 3ACK 标准

    前面 快速重传机制 部分有讲, 如果收到同一个数据包的多次确认,意味着数据包丢失、或延迟, 此时网络可能发生拥塞。

拥塞窗口算法

TCP 协议仍然基于滑动窗口的机制,进行拥塞控制。

发送方会维护一个拥塞窗口 CWND ,此窗口范围内的数据才允许被发送。

因此实际的发送窗口的大小是:Min(对方接收窗口大小,自身拥塞窗口大小) 

拥塞窗口算法就是如何调整拥塞窗口大小的办法。

它主要有四个策略:慢开始、拥塞避免、快重传、快恢复。

虽然有四个策略,但是它们其实是 一套算法

计算机网络 - TCP 协议原理总结
图 6.1 - 拥塞控制算法图示 - 图片来自 计算机网络(谢希仁第七版)-方老师

首先,「快重传」就是前面所讲的 快速重传 , 也就是说, 如果遭遇 3ACK ,就立即发起重传 

慢开始

发送方每收到一个对新数据包(就是不包括重传包)的确认, 就让拥塞窗口增一。

虽然叫做慢开始,但是其实并不慢, 拥塞窗口是倍增的 

简而言之,如果没有遭遇超时 或 3ACK, 拥塞窗口就扩大一倍。

计算机网络 - TCP 协议原理总结
图 6.2 - 拥塞控制 - 慢开始策略
拥塞避免

慢开始的增长速度是非常快的, 发送量越大,就离拥塞越近。

所以有一个门限值 ssthresh 的概念。

拥塞窗口倍增到门限值后,就改为线性增长,比如每次增大一 

其后,线性增大拥塞窗口的过程,就是拥塞避免阶段。

慢开始和拥塞避免,都是在探测拥塞边缘 : 一开始疯狂试探,到达门限值后,慢慢试探。

拥塞控制的惩罚

如果出现 拥塞的征兆,拥塞窗口的大小会受到惩罚,让它减小。

  1. 超时条件

    超时未确认,意味着发生丢包或者较大延迟,所以处罚更为严厉。

    拥塞窗口打回初始值 1 , 门限值改为当前拥塞窗口大小的一半。

    总而言之,从新进入慢启动 

  2. 3ACK 条件

    收到 3 个相同的确认包,意味着发生少许延迟,所以处罚相对柔和。

    同样,慢启动的门限值减半。

    不同的是,此时不必要从新进入慢启动, 而是减少一些拥塞窗口的值,跳过慢启动,直接重新进入拥塞避免阶段。

    减少到多少呢?减少到最新门限值大小,也就是从新进入拥塞避免。

    这种直接跳过慢启动,直接进入拥塞避免的方式,就叫做 快恢复 

拥塞控制算法总览
计算机网络 - TCP 协议原理总结
图 6.3 - 拥塞控制算法总览 - 图片来自 计算机网络(谢希仁第七版)-方老师

总结如下:

  1. 慢开始阶段,倍增到门限值,激进探测拥塞边缘。
  2. 到达门限值,进入拥塞避免,线性增加,小心探测拥塞边缘。
  3. 遭遇超时,惩罚严厉,从新进入慢开始。
  4. 遭遇 3ACK,惩罚柔和,快恢复,门限减半,直接进入拥塞避免。

    同时,3ACK 时,立即重传,即 快重传。

TCP 连接建立和释放 

TCP 的三次握手和四次握手,已经是老生常谈了。

连接建立 - 三次握手

首先,TCP 连接建立过程要解决的问题:

  1. 确定双方都可以正常收发 TCP 数据包
  2. 双方协商一些参数和可选项(例如前面所讲的 选择确认 、窗口大小 等), 尤其是交换初始序号
  3. 对资源进行分配,比如缓存区、端口号。

下面是 TCP 协议三次握手建立连接的过程示意图,其中

  1. SYN(synchronous) 是同步标志位 ,表示开始传输数据的意思。
  2. ACK(acknowledgement) 是确认标志位,表示是否启用确认机制。
  3. ack(小写)是前面所说的 确认号
  4. seq 是前面所说的 序号 。
计算机网络 - TCP 协议原理总结
图 7.1 - TCP 协议三次握手过程

具体过程的描述如下:

  1. 客户端发送一个 TCP 数据包,主动发起连接

    设置 SYN=1 标志位,表示发起连接,想要传输数据。

    客户端初始化自己的数据包序号 seq=x ,一同发送给对方。

  2. 服务端收到对方的连接请求,回复对方确认包

    设置回复标志位 ACK=1 ,表示确认。

    根据 确认号的规则, 同时设 ack=x+1 表示期待对方下一次发过来的数据包序号为 x+1

    因为 TCP 是全双工的,服务端也会设置 SYN=1 , 并初始化自己的数据包序号 seq=y ,捎带回复给客户端。

    此时,服务端可以确定: 客户端可以发送 TCP 数据包,自身可以接收 TCP 数据包 

  3. 客户端收到对方的确认包,并回复对方确认

    同样,设置回复标志位 ACK=1,表示确认。

    对方的序号是 y , 回复对方 ack=y+1 表示期待对方下一次发来的数据包序号为 y+1 。

    同时,自身的数据包序号需要自增 seq=x+1 。

    自己发送上一个数据包,对方收到了,并且收到了对方回复。

    所以客户端可以确定: 服务端可以正常收发 TCP 数据包, 自身也可以正常收发 TCP 数据包 

  4. 服务端收到客户端的确认,三次握手结束

    此时服务端可以进一步确定:

    客户端端可以正常接收 TCP 数据包, 自身也可以正常发送 TCP 数据包 

三次握手结束后,双方都可以确定对方是可以正常收发 TCP 数据的。

计算机网络 - TCP 协议原理总结
图 7.2 - 三次握手过程中双方逐步确定收发能力正常
三次握手的必要性

一个经常出现的问题是: 两次握手可以吗?

当然是不可以的,可以分几个方面解释:

  1. 三次握手是为了确定双方的收发能力

    如果缺少最后一次 ACK 的话,服务端就无法知道客户端是否可以接收 TCP 数据, 也无法知道自己的发送是否成功,就无法做到可靠传输。

  2. 序号同步的确定性

    如果缺少最后一次 ACK 的话,服务端就无法确定对方有无收到自己的初始序号。

    前面讲到,数据包的序号是 TCP 滑动窗口机制的基本字段,它如果是不可靠的, 后续的确认机制、重传机制等都无从谈起。

    网络是不可靠的,存在丢包,所以连接建立时, 双方都必须确定初始序号交到了对方手中。

    三次握手过程,也是可靠地交换初始序号的过程 

    计算机网络 - TCP 协议原理总结
    图 7.3 - 三次握手也是可靠地交换初始序号的过程
  3. 历史失效连接请求的乱序问题

    网络中,数据传输可能存在延迟、乱序。

    如果一个老的失效的连接请求,延迟到达服务端,倘若缺少第三次握手, 服务端会建立一条新连接,直接进入 ESTABLISHED 状态。

    客户端收到服务端的回复包时,可以根据序号是否过期判断是否是失效连接。

    在三次握手的情况下,客户端就进一步可以发送 RST 标志,终止连接。

    如果不是历史失效连接,客户端则发送 ACK 标志,正常建立连接。

三次握手中丢包的情况

另一个常见的问题是: 如果第三次 ACK 丢包呢?

无论哪一次通信发生丢包或延迟,都出发 TCP 的 重传机制 。

简单说,再发一次,如果达到一定次数,则放弃连接。

对于第三次 ACK 丢包的情况,重试一定次数后,服务端会关闭连接,回收资源。

不过,服务端单方面关闭连接,客户端并不知晓,它认为连接已经建立。

如果客户端向服务端发送数据,会被服务端打回 RST 报文, 借此客户端可知道连接失效。

计算机网络 - TCP 协议原理总结
图 7.3.1 - 第三次 ACK 包丢失的最坏情况
数据传输的开始时机

上面的 三次握手图示 中,一个细节是:

  • 客户端在两次握手之后就进入了 ESTABLISHED 状态。
  • 服务端则是在三次握手之后才进入 ESTABLISHED 状态。

其原因在于, 在 第二次握手 后, 客户端已经可以确定双方都可以正常收发数据包 

所以,客户端可以提前进入 ESTABLISHED 状态,分配端口,开始通信。

因此, 第三次 ACK 是可以捎带客户端的数据一并发送的 

服务端直到 第三次握手 成功之后,才可以确定双方都正常, 所以它会稍晚进入 ESTABLISHED

连接关闭 - 四次挥手

TCP 的连接释放过程,需要注意两个点:

  1. TCP 是全双工的。
  2. 双方都可以主动释放连接。

释放连接的过程有一个重要的目的:

双方都要确定地知道对方不再发送数据了,连接才正式关闭 

假设客户端主动关闭,下面是 TCP 协议四次挥手释放连接的过程示意图。

其中 FIN(finish)是结束数据传输的标志位。

计算机网络 - TCP 协议原理总结
图 7.4 - TCP 协议四次挥手过程

具体过程的描述如下:

  1. 客户端发送 FIN 数据包,不再发送数据

    设置 FIN=1 标志位,表示想要关闭连接,不再发送数据。

    但是,此时服务端还可以继续发送数据 

  2. 服务端收到 FIN 数据包,回复确认 。

    服务端对于 FIN 包也会进行确认。

    此时服务端明确知道,客户端不再发送数据了 

  3. 服务端传输完剩余数据 。

    由于 TCP 是全双工的, 连接关闭由客户端主动发起,并不意味着服务端的数据已传输完。

    服务端需要把剩余数据传输完毕。

    此时服务端知道,双方都不再发送数据了。 

  4. 服务端发送 FIN 数据包,不再发送数据包 。

    同样,设置 FIN=1 标志位,表示服务端不再发送数据。

  5. 客户端收到对方 FIN 包后,回复确认 。

    任何一方结束数据发送,都要确保对方是知道的,就需要对方确认。

    此时客户端知道,双方都不再发送数据了。

  6. 客户端等待 2MSL 时间等待后,四次挥手结束,释放所有资源

再次明确, FIN 标志位的意思是,不再发送数据 

挥手四次的必要性

双方都必须对 FIN 包做确认, 这样对方才可以确定地知道自己不再发送数据。

TCP 连接的建立需要三次握手,是因为, 第二次握手过程服务端将设置 SYN 和 ACK 合并为一个数据包发送,所以是三次 

而对于释放连接的情况, 无法将第二次和第三次合并, 因为中间还要传输数据, 所以是四次。

如果任何一个确认包发生丢包或延迟,主动设置 FIN 的一方会触发重传。

等待 2MSL 时间的原因

首先,MSL 是指报文在网络中最大生存时间。

2MSL 就是两倍的 MSL ,也被叫做 TIME_WAIT 时间, 在 linux 中常被设置为 2*60s 。

2MSL 的值一定需要大于重传超时阈值 

为何要等待这么长时间呢?

  1. 如果最后一次 ACK 丢包呢?

    前面有说过,任何一方的 FIN 包丢失后,如果超时未收到对方确认, 就会触发重传。

    计算机网络 - TCP 协议原理总结
    图 7.5 - 最后一次 ACK 丢失,会导致服务端重传 FIN 包

    如果客户端在一定时间内未收到服务端的 FIN 包重传,说明对方已经收到 ACK 。

    否则,如果收到服务端的 FIN 包重传,自然回复 ACK

    一次 ACK 包的发送时间,再算上重传 FIN 的时间,所以叫做 2MSL 。

    不过,无论如何,2MSL 时间至少要比重传超时阈值长。

  2. 防止新老连接数据包混乱

    如果立即释放端口资源,此端口可能被一个新连接立即使用。

    如此的话,假设网络中存在老数据包的延迟,那么老数据包会被新连接接收,造成混乱。

    所以, 等待一定时间,使得网络中潜在的、可能延迟传输的数据包悉数殆尽, 才不会影响到后面的连接

综合来看,还是因为网络是不可靠的。

TCP 连接的有限状态机

综合连接建立和连接关闭的过程,整个 TCP 连接的状态机图示如下。

计算机网络 - TCP 协议原理总结
图 8.1 - TCP 有限状态机 - 图片来自 网络

两个常见的问题:

  1. TIME_WAIT 非常多的情况

    已经知道,TIME_WAIT 是主动关闭连接一方,发送完最后一次 ACK 数据包后进入的状态。

    并且 TIME_WAIT 的时间默认是 120s 。

    TIME_WAIT 非常多,说明 连接建立的并发数比较大,端口来不及回收

    比如,对于高并发的 TCP 短连接,例如 未开启 keep-alive 选项 的 HTTP 短连接。

    补充,HTTP 协议中是服务端主动关闭连接 。

  2. CLOSE_WAIT 非常多的情况

    已经知道,CLOSE_WAIT 是被动关闭连接的一方,传输最后的剩余数据的状态。

    它是当服务端收到对方 FIN 包时,主动进入的一个状态。

    所以,一般地,是由于应用程序未调用或太忙未来得及调用 close 关闭连接导致。

    比如程序编写遗忘连接关闭调用代码,或者负载过高,未及时执行关闭连接, 导致 CLOSE_WAIT 状态的 socket 积压。

TCP 协议头 

TCP 的协议头部总共有 20 个字节。

下面是 TCP 协议头的栏位示意图,其中大部分字段已经在上面讲到。

计算机网络 - TCP 协议原理总结
图 9.1 - TCP 协议头 - 图片来自 网络

概念图谱总览 

最后,对本文的知识做一个树状图梳理。

计算机网络 - TCP 协议原理总结
图 10.1 - TCP 总结图谱 - 图片来自 网络

结语 

纵观全文,TCP 协议的众多机制源于,万恶之源:网络是不可靠的,丢包、乱序、延时。

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: