传输控制协议---TCP协议【详解】

目录

一、协议简介

二、TCP报文格式

1、字段解析

2、报文解析

 三、TCP的重传机制

1、超时重传

2、快速重传

3、 SACK 方法

4、Duplicate SACK

四、TCP的滑动窗口机制

五、流量控制

六、拥塞机制

1、慢启动

2、拥塞避免算法

3、拥塞发生

4、快速恢复

七、TCP的三次握手

八、TCP的四次挥手


一、协议简介

TCP(Transmission Control Protocol)传输控制协议是一种面向连接的、可靠的、基于字节流的传输层协议。TCP为了保证不发生丢包,就给每个字节一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的字节发回一个相应的确认(ACK); 如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据(假设丢失了)将会被重传。TCP用一个校验和函数来检验数据是否有错误;在发送和接收时都要计算校验和。

首先,TCP建立连接之后,通信双方都同时可以进行数据的传输,其次,他是全双工的;在保证可靠性上,采用超时重传和捎带确认机制。

在流量控制上,采用滑动窗口协议,协议中规定,对于窗口内未经确认的分组需要重传。

在拥塞控制上,采用慢启动算法。

TCP协议的特点:

  1. 面向连接;
  2. 每一条TCP连接只能有两个端点,每一条TCP连接只能是点对点的;
  3. TCP提供可靠交付的服务;
  4. TCP提供全双工通信。数据在两个方向上独立的进行传输。因此,连接的每一端必须保持每个方向上的传输数据序号;
  5. 面向字节流。面向字节流的含义:虽然应用程序和TCP交互是一次一个数据块,但TCP把应用程序交下来的数据仅仅是一连串的无结构的字节流

官方指导文档参考:https://datatracker.ietf.org/doc/html/rfc793

传输控制协议---TCP协议【详解】_第1张图片

二、TCP报文格式

1、字段解析

传输控制协议---TCP协议【详解】_第2张图片

TCP报文各段说明:

  • 源端口:16位,数据发起者的端口号
  • 目的端口: 16位,数据接收者的端口号
  • 序号: 32位,TCP 连接中传送的数据流中的每一个字节都编上一个序号,序号字段的值则指的是本报文段所发送的数据的第一个字节的序号
  • 确认号:32位,是期望收到对方的下一个报文段的数据的第一个字节的序号
  • 数据偏移/首部长度:4 位,它指出 TCP 报文段的数据起始处距离 TCP 报文段的起始处有多远.单位是 32 位字(以 4 字节为计算单位)
  • 保留: 6 位,保留为今后使用,目前应置为 0 
  • 紧急URG:1位,当URG=1 时,它告诉系统此报文段中有紧急数据,应尽快传送
  • 确认ACK:1位,只有当 ACK=1 时确认号字段才有效;当 ACK=0 时,确认号无效
  • PSH:1位,当 PSH = 1时,应尽快地交付接收应用进程,而不再等到整个缓存都填满了后再向上交付
  • RST :1位,当 RST=1 时,表明 TCP 连接中出现严重差错,必须释放连接,然后再重新建立运输连接
  • 同步SYN:1位,建立连接的时候使用:当SYN=1,ACK=0时,表示为请求连接;当SYN=1,ACK=1时,表示为同意连接的请求应答
  • 终止FIN:1位,用来释放一个连接,当FIN=1 表明此报文段的发送端的数据已发送完毕,并要求释放运输连接
  • 窗口:16位,表示接受端的接收窗口的大小,用于实现流量控制。将接收端发送过去的窗口大小设置成发送端的发送窗口大小,从而控制了发送端的发送效率
  • 检验和:占16位,用于检测发送过程中是否出现错误
  • 紧急指针:占 16 位,指出在本报文段中紧急数据共有多少个字节(紧急数据放在本报文段数据的最前面)
  • 选项:长度可变,TCP 最初只规定了一种选项,即最大报文段长度 MSS.MSS 告诉对方 TCP:“我的缓存所能接收的报文段的数据字段的最大长度是 MSS 个字节.” [MSS(Maximum Segment Size)是 TCP 报文段中的数据字段的最大长度.数据字段加上 TCP 首部才等于整个的 TCP 报文段]
  • 填充:这是为了使整个首部长度是 4 字节的整数倍

2、报文解析

TCP报文Wireshark抓包示例:

传输控制协议---TCP协议【详解】_第3张图片

(1)源端口

从下图可以明显看出,源端口号为:02 2a,即554。

传输控制协议---TCP协议【详解】_第4张图片

 (2)目的端口

从下图可以明显看出,目的端口号为:e2 3f,即57919。

传输控制协议---TCP协议【详解】_第5张图片

 (3)序号

从下图可以明显看出,序号为:47 05 60 6a,即1191534698。

传输控制协议---TCP协议【详解】_第6张图片

 (4)确认号

从下图可以明显看出,确认号为:d0 42 0d b4,即3493989812。

传输控制协议---TCP协议【详解】_第7张图片

 (5)数据偏移/首部长度

从下图可以明显看出,数据便宜/首部长度的四位长度为:1000,即8,所以首部长度为:8*4=32字节。

传输控制协议---TCP协议【详解】_第8张图片

 (6)保留/紧急URG/确认ACK/PSH/RST/同步SYN/终止FIN

从下图可以明显看出:

  1. 6位保留位全部为0
  2. 紧急URG为0
  3. 确认ACK为1
  4. PSH为0
  5. RST为0
  6. 同步SYN为1
  7. 终止FIN为0

SYN=1,且ACK=1,表示为同意连接的请求应答

传输控制协议---TCP协议【详解】_第9张图片

 (7)窗口

从下图可以明显看出,窗口为:ff ff,即65535。

传输控制协议---TCP协议【详解】_第10张图片

(8)校验和

从下图可以明显看出,校验和为:4b 3b

传输控制协议---TCP协议【详解】_第11张图片

(9)紧急指针

从下图可以明显看出,紧急指针为:00 00

传输控制协议---TCP协议【详解】_第12张图片

(10)选项

从下图可以明显看出,MSS为65495

传输控制协议---TCP协议【详解】_第13张图片

 三、TCP的重传机制

在 TCP 中,当发送端的数据到达接收主机时,接收端主机会返回一个确认应答消息,表示已收到消息。

传输控制协议---TCP协议【详解】_第14张图片

 这是正常的数据传输,但在错综复杂的网络,并不一定能如上图那么顺利能正常的数据传输,万一数据在传输过程中丢失了呢?

1、超时重传

 超时重传机制其实就是在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的 ACK 确认应答报文,就会重发该数据。

TCP 会在以下两种情况发生超时重传:

  • 数据包丢失
  • 确认应答丢失

传输控制协议---TCP协议【详解】_第15张图片

那我们的超时时间应该设置为多少呢?

  • 当超时时间设置过大时,重发就慢,丢了老半天才重发,没有效率,性能差;
  • 当超时时间设置过小时,会导致可能并没有丢就重发,于是重发的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发

讲到这里,我们先来了解一下什么是 RTT(Round-Trip Time 往返时延),从下图我们就可以知道:

传输控制协议---TCP协议【详解】_第16张图片

RTT 就是数据从网络一端传送到另一端所需的时间,也就是包的往返时间。

超时重传时间是以 RTO (Retransmission Timeout 超时重传时间)表示。

根据上述图,我们可以得知,超时重传时间 RTO 的值应该略大于报文往返 RTT 的值。当然,我们的网络是时常变化的,所以「报文往返 RTT 的值」是经常变化的,所以「超时重传时间 RTO 的值」应该是一个动态变化的值

2、快速重传

TCP 的快速重传(Fast Retransmit)机制,它不以时间为驱动,而是以数据驱动重传

传输控制协议---TCP协议【详解】_第17张图片

在上图,发送方发出了 1,2,3,4,5 份数据:

  • 第一份 Seq1 先送到了,于是就 Ack 回 2;
  • 结果 Seq2 因为某些原因没收到,Seq3 到达了,于是还是 Ack 回 2;
  • 后面的 Seq4 和 Seq5 都到了,但还是 Ack 回 2,因为 Seq2 还是没有收到;
  • 发送端收到了三个 Ack = 2 的确认,知道了 Seq2 还没有收到,就会在定时器过期之前,重传丢失的 Seq2。
  • 最后,收到了 Seq2,此时因为 Seq3,Seq4,Seq5 都收到了,于是 Ack 回 6 。

所以,快速重传的工作方式是当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文段。

快速重传机制只解决了一个问题,就是超时时间的问题,但是它依然面临着另外一个问题。就是重传的时候,是重传之前的一个,还是重传所有的问题。

比如对于上面的例子,是重传 Seq2 呢?还是重传 Seq2、Seq3、Seq4、Seq5 呢?因为发送端并不清楚这连续的三个 Ack 2 是谁传回来的。

根据 TCP 不同的实现,以上两种情况都是有可能的。可见,这是一把双刃剑。

为了解决不知道该重传哪些 TCP 报文,于是就有 SACK 方法。

3、 SACK 方法

还有一种实现重传机制的方式叫:SACK( Selective Acknowledgment 选择性确认)。

这种方式需要在 TCP 头部「选项」字段里加一个 SACK 的东西,它可以将缓存的地图发送给发送方,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据

如下图,发送方收到了三次同样的 ACK 确认报文,于是就会触发快速重发机制,通过 SACK 信息发现只有 200~299 这段数据丢失,则重发时,就只选择了这个 TCP 段进行重复。

传输控制协议---TCP协议【详解】_第18张图片

4、Duplicate SACK

Duplicate SACK 又称 D-SACK,其主要使用了 SACK 来告诉「发送方」有哪些数据被重复接收了。

(1)ACK 丢包:

传输控制协议---TCP协议【详解】_第19张图片

  • 「接收方」发给「发送方」的两个 ACK 确认应答都丢失了,所以发送方超时后,重传第一个数据包(3000 ~ 3499)
  • 于是「接收方」发现数据是重复收到的,于是回了一个 SACK = 3000~3500,告诉「发送方」 3000~3500 的数据早已被接收了,因为 ACK 都到了 4000 了,已经意味着 4000 之前的所有数据都已收到,所以这个 SACK 就代表着 D-SACK
  • 这样「发送方」就知道了,数据没有丢,是「接收方」的 ACK 确认报文丢了。

(2)网络延时

传输控制协议---TCP协议【详解】_第20张图片

  • 数据包(1000~1499) 被网络延迟了,导致「发送方」没有收到 Ack 1500 的确认报文。
  • 而后面报文到达的三个相同的 ACK 确认报文,就触发了快速重传机制,但是在重传后,被延迟的数据包(1000~1499)又到了「接收方」;
  • 所以「接收方」回了一个 SACK=1000~1500,因为 ACK 已经到了 3000,所以这个 SACK 是 D-SACK,表示收到了重复的包。
  • 这样发送方就知道快速重传触发的原因不是发出去的包丢了,也不是因为回应的 ACK 包丢了,而是因为网络延迟了。

可见,D-SACK 有这么几个好处:

  1. 可以让「发送方」知道,是发出去的包丢了,还是接收方回应的 ACK 包丢了;
  2. 可以知道是不是「发送方」的数据包被网络延迟了;
  3. 可以知道网络中是不是把「发送方」的数据包给复制了;

四、TCP的滑动窗口机制

我们都知道 TCP 是每发送一个数据,都要进行一次确认应答。当上一个数据包收到了应答了, 再发送下一个。

传输控制协议---TCP协议【详解】_第21张图片

但是,这样的传输方式有一个缺点:数据包的往返时间越长,通信的效率就越低

为解决这个问题,TCP 引入了窗口这个概念,窗口大小就是指无需等待确认应答,而可以继续发送数据的最大值,该值由接收方的TCP 头中Window字段指定,这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。

窗口的实现实际上是操作系统开辟的一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。

假设窗口大小为 3 个 TCP 段,那么发送方就可以「连续发送」 3 个 TCP 段,并且中途若有 ACK 丢失,可以通过「下一个确认应答进行确认」。如下图:

传输控制协议---TCP协议【详解】_第22张图片

上图中的 ACK 600 确认应答报文丢失,也没关系,因为可以通过下一个确认应答进行确认,只要发送方收到了 ACK 700 确认应答,就意味着 700 之前的所有数据「接收方」都收到了。这个模式就叫累计确认或者累计应答

我们先来看看发送方的窗口,下图就是发送方缓存的数据,根据处理的情况分成四个部分,其中深蓝色方框是发送窗口,紫色方框是可用窗口:

传输控制协议---TCP协议【详解】_第23张图片

在下图,当发送方把数据「全部」都一下发送出去后,可用窗口的大小就为 0 了,表明可用窗口耗尽,在没收到 ACK 确认之前是无法继续发送数据了。

传输控制协议---TCP协议【详解】_第24张图片

在下图,当收到之前发送的数据 32~36 字节的 ACK 确认应答后,如果发送窗口的大小没有变化,则滑动窗口往右边移动 5 个字节,因为有 5 个字节的数据被应答确认,接下来 52~56 字节又变成了可用窗口,那么后续也就可以发送 52~56 这 5 个字节的数据了。

传输控制协议---TCP协议【详解】_第25张图片

接下来我们看看接收方的窗口,接收窗口相对简单一些,根据处理的情况划分成三个部分:

传输控制协议---TCP协议【详解】_第26张图片

问题:接收窗口和发送窗口的大小是相等的吗?

并不是完全相等,接收窗口的大小是约等于发送窗口的大小的。

因为滑动窗口并不是一成不变的。比如,当接收方的应用进程读取数据的速度非常快的话,这样的话接收窗口可以很快的就空缺出来。那么新的接收窗口大小,是通过 TCP 报文中的 Windows 字段来告诉发送方。那么这个传输过程是存在时延的,所以接收窗口和发送窗口是约等于的关系。

五、流量控制

发送方不能无脑的发数据给接收方,要考虑接收方处理能力。如果一直无脑的发数据给对方,但对方处理不过来,那么就会导致触发重发机制,从而导致网络流量的无端的浪费。

为了解决这种现象发生,TCP 提供一种机制可以让「发送方」根据「接收方」的实际接收能力控制发送的数据量,这就是所谓的流量控制。

在介绍之前,先参考一下发送方的来熟悉一下,几个指针:

传输控制协议---TCP协议【详解】_第27张图片

  • SND.WND:表示发送窗口的大小(大小是由接收方指定的);

  • SND.UNA:是一个绝对指针,它指向的是已发送但未收到确认的第一个字节的序列号,也就是 #2 的第一个字节。

  • SND.NXT:也是一个绝对指针,它指向未发送但可发送范围的第一个字节的序列号,也就是 #3 的第一个字节。

  • 指向 #4 的第一个字节是个相对指针,它需要 SND.UNA 指针加上 SND.WND 大小的偏移量,就可以指向 #4 的第一个字节了。

下面举个正常场景的栗子,为了简单起见,假设以下场景:

  • 客户端是接收方,服务端是发送方
  • 假设接收窗口和发送窗口相同,都为 200
  • 假设两个设备在整个传输过程中都保持相同的窗口大小,不受外界影响

传输控制协议---TCP协议【详解】_第28张图片

根据上图的流量控制,说明下每个过程:

  1. 客户端向服务端发送请求数据报文
  2. 服务端收到请求报文后,发送确认报文和 80 字节的数据,于是可用窗口 Usable 减少为 120 字节,同时 SND.NXT 指针也向右偏移 80 字节后,指向 321,这意味着下次发送数据的时候,序列号是 321。
  3. 客户端收到 80 字节数据后,于是接收窗口往右移动 80 字节,RCV.NXT 也就指向 321,这意味着客户端期望的下一个报文的序列号是 321,接着发送确认报文给服务端。
  4. 服务端再次发送了 120 字节数据,于是可用窗口耗尽为 0,服务端无法再继续发送数据。
  5. 客户端收到 120 字节的数据后,于是接收窗口往右移动 120 字节,RCV.NXT 也就指向 441,接着发送确认报文给服务端。
  6. 服务端收到对 80 字节数据的确认报文后,SND.UNA 指针往右偏移后指向 321,于是可用窗口 Usable 增大到 80。
  7. 服务端收到对 120 字节数据的确认报文后,SND.UNA 指针往右偏移后指向 441,于是可用窗口 Usable 增大到 200。
  8. 服务端可以继续发送了,于是发送了 160 字节的数据后,SND.NXT 指向 601,于是可用窗口 Usable 减少到 40。
  9. 客户端收到 160 字节后,接收窗口往右移动了 160 字节,RCV.NXT 也就是指向了 601,接着发送确认报文给服务端。
  10. 服务端收到对 160 字节数据的确认报文后,发送窗口往右移动了 160 字节,于是 SND.UNA 指针偏移了 160 后指向 601,可用窗口 Usable 也就增大至了 200。

上面的例子是理想情况下的流量控制,但是实际上,网络是波动的,所以,可能会出现,读速度跟不上写速度的场景,例如:

  • 客户端作为发送方,服务端作为接收方,发送窗口和接收窗口初始大小为 360
  • 服务端非常的繁忙,当收到客户端的数据时,应用层不能及时读取数据。

传输控制协议---TCP协议【详解】_第29张图片

根据上图的流量控制,说明下每个过程:

  1. 客户端发送 140 字节数据后,可用窗口变为 220 (360 - 140)。
  2. 服务端收到 140 字节数据,但是服务端非常繁忙,应用进程只读取了 40 个字节,还有 100 字节占用着缓冲区,于是接收窗口收缩到了 260 (360 - 100),最后发送确认信息时,将窗口大小通告给客户端。
  3. 客户端收到确认和窗口通告报文后,发送窗口减少为 260。
  4. 客户端发送 180 字节数据,此时可用窗口减少到 80。
  5. 服务端收到 180 字节数据,但是应用程序没有读取任何数据,这 180 字节直接就留在了缓冲区,于是接收窗口收缩到了 80 (260 - 180),并在发送确认信息时,通过窗口大小给客户端。
  6. 客户端收到确认和窗口通告报文后,发送窗口减少为 80。
  7. 客户端发送 80 字节数据后,可用窗口耗尽。
  8. 服务端收到 80 字节数据,但是应用程序依然没有读取任何数据,这 80 字节留在了缓冲区,于是接收窗口收缩到了 0,并在发送确认信息时,通过窗口大小给客户端。
  9. 客户端收到确认和窗口通告报文后,发送窗口减少为 0。

如果窗口大小为 0 时,就会阻止发送方给接收方传递数据,直到窗口变为非 0 为止,这就是窗口关闭。当发生窗口关闭时,接收方处理完数据后,会向发送方通告一个窗口非 0 的 ACK 报文,通知对方,现在可以继续发送数据了。但是如果这个通告窗口的 ACK 报文在网络中丢失了,那该怎么办呢?

其实为了解决这个问题,TCP 为每个连接设有一个持续定时器,只要 TCP 连接一方收到对方的零窗口通知,就启动持续计时器。如果持续计时器超时,就会发送窗口探测 ( Window probe ) 报文,而对方在确认这个探测报文时,给出自己现在的接收窗口大小。

传输控制协议---TCP协议【详解】_第30张图片

  • 如果接收窗口仍然为 0,那么收到这个报文的一方就会重新启动持续计时器;
  • 如果接收窗口不是 0,那么就继续发送数据

 窗口探测的次数一般为 3 次,每次大约 30-60 秒(不同的实现可能会不一样)。如果 3 次过后接收窗口还是 0 的话,有的 TCP 实现就会发 RST 报文来中断连接。

六、拥塞机制

我们知道,在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时 TCP 就会重传数据,但是一重传就会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这个情况就会进入恶性循环被不断地放大….

所以,TCP 不能忽略网络上发生的事,当网络发送拥塞时,TCP 会自我牺牲,降低发送的数据量。于是,就有了拥塞控制,控制的目的就是避免「发送方」的数据填满整个网络。

那么怎么知道当前网络是否出现了拥塞呢?其实只要「发送方」没有在规定时间内接收到 ACK 应答报文,也就是发生了超时重传,就会认为网络出现了用拥塞。

为了在「发送方」调节所要发送数据的量,定义了一个叫做「拥塞窗口」的概念:拥塞窗口 cwnd是发送方维护的一个的状态变量,它会根据网络的拥塞程度动态变化的

我们在前面提到过发送窗口 swnd 和接收窗口 rwnd 是约等于的关系,那么由于加入了拥塞窗口的概念后,此时发送窗口的值是swnd = min(cwnd, rwnd),也就是拥塞窗口和接收窗口中的最小值。

拥塞窗口 cwnd 变化的规则:

  • 只要网络中没有出现拥塞,cwnd 就会增大;
  • 但网络中出现了拥塞,cwnd 就减少

拥塞控制主要是四个算法:

  • 慢启动
  • 拥塞避免
  • 拥塞发生
  • 快速恢复

1、慢启动

TCP 在刚建立连接完成后,首先是有个慢启动的过程,这个慢启动的意思就是一点一点的提高发送数据包的数量,如果一上来就发大量的数据,这不是给网络添堵吗?

慢启动的算法记住一个规则就行:当发送方每收到一个 ACK,拥塞窗口 cwnd 的大小就会加 1。

这里假定拥塞窗口 cwnd 和发送窗口 swnd 相等,下面举个栗子:

  • 连接建立完成后,一开始初始化 cwnd = 1,表示可以传一个 MSS 大小的数据。
  • 当收到一个 ACK 确认应答后,cwnd 增加 1,于是一次能够发送 2 个
  • 当收到 2 个的 ACK 确认应答后, cwnd 增加 2,于是就可以比之前多发2 个,所以这一次能够发送 4 个
  • 当这 4 个的 ACK 确认到来的时候,每个确认 cwnd 增加 1, 4 个确认 cwnd 增加 4,于是就可以比之前多发 4 个,所以这一次能够发送 8 个。

传输控制协议---TCP协议【详解】_第31张图片

可以看出慢启动算法,发包的个数是指数性的增长。 

那慢启动涨到什么时候是个头呢?

有一个叫慢启动门限 ssthresh (slow start threshold)状态变量。

  • 当 cwnd < ssthresh 时,使用慢启动算法。
  • 当 cwnd >= ssthresh 时,就会使用「拥塞避免算法」。

2、拥塞避免算法

前面说道,当拥塞窗口 cwnd 「超过」慢启动门限 ssthresh 就会进入拥塞避免算法。

一般来说 ssthresh 的大小是 65535 字节。

那么进入拥塞避免算法后,它的规则是:每当收到一个 ACK 时,cwnd 增加 1/cwnd。

接上前面的慢启动的栗子,现假定 ssthresh 为 8

  • 当 8 个 ACK 应答确认到来时,每个确认增加 1/8,8 个 ACK 确认 cwnd 一共增加 1,于是这一次能够发送 9 个 MSS 大小的数据,变成了线性增长。

传输控制协议---TCP协议【详解】_第32张图片

所以,我们可以发现,拥塞避免算法就是将原本慢启动算法的指数增长变成了线性增长,还是增长阶段,但是增长速度缓慢了一些。

就这么一直增长着后,网络就会慢慢进入了拥塞的状况了,于是就会出现丢包现象,这时就需要对丢失的数据包进行重传。

当触发了重传机制,也就进入了「拥塞发生算法」。

3、拥塞发生

当网络出现拥塞,也就是会发生数据包重传,重传机制主要有两种:

  • 超时重传
  • 快速重传

这两种使用的拥塞发送算法是不同的,接下来分别来说说。

这个时候,ssthresh 和 cwnd 的值会发生变化:

  • ssthresh 设为 cwnd/2
  • cwnd 重置为 1

传输控制协议---TCP协议【详解】_第33张图片

拥塞发送 —— 超时重传

接着,就重新开始慢启动,慢启动是会突然减少数据流的。这真是一旦「超时重传」,马上回到解放前。但是这种方式太激进了,反应也很强烈,会造成网络卡顿。

就好像本来在秋名山高速漂移着,突然来个紧急刹车,轮胎受得了吗。。。

发生快速重传的拥塞发生算法

还有更好的方式,前面我们讲过「快速重传算法」。当接收方发现丢了一个中间包的时候,发送三次前一个包的 ACK,于是发送端就会快速地重传,不必等待超时再重传。

TCP 认为这种情况不严重,因为大部分没丢,只丢了一小部分,则 ssthresh 和 cwnd 变化如下:

  • cwnd = cwnd/2 ,也就是设置为原来的一半;
  • ssthresh = cwnd;
  • 进入快速恢复算法

4、快速恢复

快速重传和快速恢复算法一般同时使用,快速恢复算法是认为,你还能收到 3 个重复 ACK 说明网络也不那么糟糕,所以没有必要像 RTO 超时那么强烈。

正如前面所说,进入快速恢复之前,cwnd 和 ssthresh 已被更新了:

  • cwnd = cwnd/2 ,也就是设置为原来的一半;
  • ssthresh = cwnd;

然后,进入快速恢复算法如下:

  • 拥塞窗口 cwnd = ssthresh + 3 ( 3 的意思是确认有 3 个数据包被收到了);
  • 重传丢失的数据包;
  • 如果再收到重复的 ACK,那么 cwnd 增加 1;
  • 如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态;

传输控制协议---TCP协议【详解】_第34张图片

也就是没有像「超时重传」一夜回到解放前,而是还在比较高的值,后续呈线性增长。

拥塞算法示意图

好了,以上就是拥塞控制的全部内容了,看完后,你再来看下面这张图片,每个过程我相信你都能明白:

传输控制协议---TCP协议【详解】_第35张图片

七、TCP的有限状态机

TCP涉及连接建立和连接终止的操作可以用状态转换图(state transition diagram)来说明,我们用粗实线表示通常的客户状态转换,用粗虚线表示通常的服务器状态转换:

传输控制协议---TCP协议【详解】_第36张图片

 TCP状态及描述:

  • CLOSED:无连接是活动的或正在进行
  • LISTEN:服务器在等待进入呼叫
  • SYN_RECV:一个连接请求已经到达,等待确认
  • SYN_SENT:应用已经开始,打开一个连接
  • ESTABLISHED:正常数据传输状态
  • FIN_WAIT1:应用说它已经完成
  • FIN_WAIT2:另一边已同意释放
  • ITMED_WAIT:等待所有分组死掉
  • CLOSING:两边同时尝试关闭
  • TIME_WAIT:另一边已初始化一个释放
  • LAST_ACK:等待所有分组死掉
     

八、TCP的三次握手

TCP 是面向连接的协议,所以使用 TCP 前必须先建立连接,而建立连接是通过三次握手而进行的。

传输控制协议---TCP协议【详解】_第37张图片

1、一开始,客户端和服务端都处于 CLOSED 状态。先是服务端主动监听某个端口,处于 LISTEN 状态

2客户端会随机初始化序号(client_isn),将此序号置于 TCP 首部的「序号」字段中,同时把 SYN 标志位置为 1 ,表示 SYN 报文。接着把第一个 SYN 报文发送给服务端,表示向服务端发起连接,该报文不包含应用层数据,之后客户端处于 SYN-SENT 状态。

传输控制协议---TCP协议【详解】_第38张图片

传输控制协议---TCP协议【详解】_第39张图片

 2、服务端收到客户端的 SYN 报文后,首先服务端也随机初始化自己的序号(server_isn),将此序号填入 TCP 首部的「序号」字段中,其次把 TCP 首部的「确认应答号」字段填入 client_isn + 1, 接着把 SYN 和 ACK 标志位置为 1。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于 SYN-RCVD 状态。

传输控制协议---TCP协议【详解】_第40张图片

传输控制协议---TCP协议【详解】_第41张图片

3、客户端收到服务端报文后,还要向服务端回应最后一个应答报文,首先该应答报文 TCP 首部 ACK 标志位置为 1 ,其次「确认应答号」字段填入 server_isn + 1 ,最后把报文发送给服务端,这次报文可以携带客户到服务器的数据,之后客户端处于 ESTABLISHED 状态。第三次握手是可以携带数据的,前两次握手是不可以携带数据的

传输控制协议---TCP协议【详解】_第42张图片

 传输控制协议---TCP协议【详解】_第43张图片

 4、服务器收到客户端的应答报文后,也进入 ESTABLISHED 状态。

一旦完成三次握手,双方都处于 ESTABLISHED 状态,此致连接就已建立完成,客户端和服务端就可以相互发送数据了。

小结

TCP 建立连接时,通过三次握手能防止历史连接的建立,能减少双方不必要的资源开销,能帮助双方同步初始化序列号。序列号能够保证数据包不重复、不丢弃和按序传输。

不使用「两次握手」和「四次握手」的原因:

  • 「两次握手」:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号;

  • 「四次握手」:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数。

九、TCP的四次挥手

天下没有不散的宴席,对于 TCP 连接也是这样, TCP 断开连接是通过四次挥手方式。

双方都可以主动断开连接,断开连接后主机中的「资源」将被释放。

传输控制协议---TCP协议【详解】_第44张图片

1、客户端打算关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1 的报文,也即 FIN 报文,之后客户端进入 FIN_WAIT_1 状态

传输控制协议---TCP协议【详解】_第45张图片

 2、服务端收到该报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSED_WAIT 状态

传输控制协议---TCP协议【详解】_第46张图片

 3、客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态

4、等待服务端处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态

传输控制协议---TCP协议【详解】_第47张图片

5、客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态

传输控制协议---TCP协议【详解】_第48张图片

6、服务器收到了 ACK 应答报文后,就进入了 CLOSE 状态,至此服务端已经完成连接的关闭

7、客户端在经过 2MSL 一段时间后,自动进入 CLOSE 状态,至此客户端也完成连接的关闭 

为什么 TIME_WAIT 等待的时间是 2MSL?

MSL 是 Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。因为 TCP 报文基于是 IP 协议的,而 IP 头中有一个 TTL 字段,是 IP 数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减 1,当此值为 0 则数据报将被丢弃,同时发送 ICMP 报文通知源主机。

MSL 与 TTL 的区别: MSL 的单位是时间,而 TTL 是经过路由跳数。所以 MSL 应该要大于等于 TTL 消耗为 0 的时间,以确保报文已被自然消亡。

TIME_WAIT 等待 2 倍的 MSL,比较合理的解释是: 网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间。

比如如果被动关闭方没有收到断开连接的最后的 ACK 报文,就会触发超时重发 Fin 报文,另一方接收到 FIN 后,会重发 ACK 给被动关闭方, 一来一去正好 2 个 MSL。

2MSL 的时间是从客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内,因为客户端的 ACK 没有传输到服务端,客户端又接收到了服务端重发的 FIN 报文,那么 2MSL 时间将重新计时。

在 Linux 系统里 2MSL 默认是 60 秒,那么一个 MSL 也就是 30 秒。Linux 系统停留在 TIME_WAIT 的时间为固定的 60 秒。

再来回顾下四次挥手双方发 FIN 包的过程,就能理解为什么需要四次了。

  • 关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。

  • 服务器收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。

你可能感兴趣的:(常用网络协议解析,tcp/ip,网络协议,传输控制协议,TCP,传输层)