TCP/IP详解——TCP 协议

发布时间:2023年12月17日

一、传输层协议

传输层负责建立端到端的连接。负责数据在端到端之间的传输。

传输层通过端口号来区分上下层服务。

  • TCP是面向字节流服务。

  • UDP是面向数据报服务

1. TCP

TCP协议(Transmission Control Protocol),全称"传输控制协议"。是一种面向连接的,可靠的,基于字节流的传输层通信协议,是建立在网络层之上的端到端的协议

  • TCP的数据报在三次握手的时候会协商最大传输单元,这个一定会小于最大MTU的,也就是TCP出现分片的可能性不大。
  • 当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
  • 当TCP收到发自TCP连接另一端的数据,它将发送一个确认。这个确认不是立即发送,通常将推迟几分之一秒。
  • TCP将保持它首部和数据的校验和。
  • 既然TCP报文段作为IP数据报来传输,而IP数据报的到达可能会失序,因此TCP报文段的到达也可能会失序。如果必要,TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层。
  • 既然IP数据报会发生重复,TCP的接收端必须丢弃重复的数据
  • TCP还能提供流量控制。TCP连接的每一方都有固定大小的缓冲空间。TCP的接收端只允许另一端发送接收瑞缓冲区所能接纳的数据。这将防止较快主机致使较慢主机的缓冲区溢出。
  • TCP为应用层提供全双工服务。这意味数据能在两个方向上独立地进行传输。因此连接的每一端必须保特每个方向上的传输数据序号。
  • TCP可以表述一个没有选择确认或者选择否认的滑动窗口协议
    • TCP缺少选择确认是因为TCP首部中的确认序号表示发送方已成功收到字节,但还不包含确认序号所指的字节。当前还无法对数据流中选定的部分进行确认。例如,如果11024字节已经成功收到,下报文段中包含序号从20493072的字节,收端并不确认这个新的报文段。它所能故的就是发回一个确认序号为1025的ACK。
    • 它也无法对一个报文段进行否认。例如,如果收到包含1025~2048字节的报文段,但它的检验和发生错误,TCP接收端所能做的就是发回一个确认序号为1025的ACK。

特点

  • 全双工通信:数据可在同一时间内双向流动;
  • 面向连接的(三次握手),分段传输,传输确认;
  • 是可靠的传输协议;
  • 差错控制;
  • 流量控制;
  • 拥塞控制。

image-20231113164222153

image-20230719142625318

1.1 TCP 的字节流

两个应用程序通过TCP连接交换8bit字节构成的字节流。TCP不在字节流中插入记录标识符。将这称为字节流服务。如果一方的应用程序先传10字节,又传20字节,再传50字节,连接的另一方将无法了解发送方每次发送了多少字节。接收方可以分4次接收这80个字节,每次接收20字节。一端将字节流放到TCP连接上,同样的字节流将出现在TCP连接的另一端。

另外,TCP对字节流的内容不作任何解释。TCP不知道传输的数据字节流是二进制数据,还是ASCI字符、EBCDIC字符或者其他类型数据。对字节流的解释由TCP连接双方的应用层解释。

1.2 TCP 端口号

image-20230719142643007

  • TCP允许一个主机运行多个应用程序,每台主机拥有多个应用端口,每对端口号,源IP和目的IP组合唯一标识一个对话。
  • 端口分为知名端口(0-1023,如FTP、HTTP等)和动态端口(1024-65535,不固定分配给某个任务,只要运行程序提取访问网络申请,系统会分配端口号供程序使用)。

大多数知名端口为奇数的由来

如果仔细检查这些标准的简单服务以及其他标准的TCP/IP服务(如Telnet、FTP、SMTP等)的端口号时,发现它们都是奇数。这是因为这些端口号都是从NCP端口号派生出来的(NCP即网络控制协议,是ARPANET的运输层协议,是TCP的前身)。NCP是单工的,不是全双工的,因此每个应用程序需要两个连接,需预留一对奇数和偶数端口号,一个端口号发另一个端口接收。当TCP和UDP成为标准的运输层协议时,每个应用程序只需要一个端口号,因此就使用了NCP中的奇数。

1.3 TCP 头部

image-20230719143409874

image-20231113174849114

字段解析

  • Source Port和Destination Port:16位源端口号(源主机的某个应用程序所使用的端口号)和16位目标端口号(目的主机的某个应用程序所使用的端口号)加上IP Header头部中的源IP和目的IP唯一确定一个TCP连接。
  • Sequence Number:32位序列号,用于标识发送端发出不同的TCP数据段的序号,成功传输一个字节序列号加一。接收端根据此序列号,可重组数据。
  • Acknowledge Number:32位确认序列号,标识接收端确认收到数据段,序列号加1。
  • Header Length:4位头部长度,0101表示头部有5个4字节的数目,即20字节,最大60个字节。
  • Resv6位,保留位全0。
  • Window:16位窗口大小,表示接收端单次确认所接收到的数据大小,16位对应窗口最大值为65535字节,用于流量控制。每一个TCP数据包都会通告窗口大小。
    • TCP的流量控制由连接的每一端通过声明的窗口大小来提供。窗口大小为字节数,起始于确认序号字段指明的值,确认序号加上窗口大小限定了对端能发送的数据范围。窗口大小是一个16位字段,因而窗口最大为65535字节。使用窗口扩大选项可以扩大首部中指定的窗口大小。
    • UDP在一次性发送多个数据包,如果将UDP的输入队列占满,那么后续发送的UDP数据包就会被丢弃。
    • 而TCP客户端发送三个数据包,应用程序还没有读取数据,这个时候本地缓存空间为0。应用程序发送ACK的确定,告诉应用程序窗口大小为0,说明没有缓存空间来接受额外的数据了。而应用程序收到信息后就不在发送数据包了。
  • Chacksum:16位校验和,检验TCP报文段,包括TCP头部和数据。该值由发送端计算和记录,接收端验证。
  • CWR/ECE:作用是让TCP协议感受到网络的拥塞,从而进行窗口的缩减。
    • 将ECE写成1的话证明网络发生了拥塞。
    • 将CWR写成1的话代表已经减少窗口。
  • URG:紧急指针,如果发送的数据是紧急数据的话将该位置为1。
  • ACK:确认位,如果数据包有确认数据的功能,那么将该位置为1。
  • PSH:表示开始传输应用层的数据,那么将该位置为1。
  • RST:如果该位为1的话,那么就将整个连接重置掉。
  • SYN:建立连接的时候使用。消耗一个序列号,占一个字节。
  • FIN:拆除连接的时候使用。消耗一个序列号,占一个字节。
  • Urgent Pointer:紧急指针位,如果网络中真正的出现了紧急数据,并且URG为1。紧急指针位就来标明紧急数据在哪里。
  • Options:选项部分,最大为40个字节。

说明

  • TCP的流量控制由连接的每一端通过声明的窗口大小来提供。窗口大小为字节数,起始于确认序号字段指明的值,这个值是接收端正期望接收的字节。窗口大小是个16bit字段,因而窗口大小最大为65535字节。新的窗口刻度选项,它允许这个值按比例变化以提供更大的窗口。
  • 最常见的TCP可选字段是最长报文大小,又称为MSS(Maximum Segment Size)。每个连接方通常在通信的第一个报文段(为建立连接而设置SYN标志的那个段)中指明这个选项。它指明本端所接收的最大长度的报文段。
  • TCP 报文段中的数据部分是可选的。双方对换的报文段仅有TCP 首部。如果方没有数据要发送,也使用没有任何数据的首部来确认收到的数据。在处理超时的许多情况中,也会发送不带任何数据的报文段。

TCP数据被封装在IP数据报中。

image-20231122092908737

说明

  • TCP通常使用IP作为网络层协议,TCP数据段封装在IP数据包内。
  • TCP由Header和Data组成。TCP最多可有60字节的头部,如果没有Options字段,正常长度为20字节。

1.4 TCP 选项部分字段

image-20231122150415271

image-20231113181132399

  • 在有些书中,将它看作可"协商”选项。它并不是任何条件下都可协商。当建立一个连接时,每方都有用于通告它期望接收的MSS选项(MSS选项只能出现在SYN报文段中)。如果一方不接收来自另一方的MSS值,则MSS就定为默认值536字节

  • 一般说来,如果没有分段发生,MSS还是越大越好。报文段越大,允许每个报文段传送的数据就越多,相对IP和TCP首部有更高的网络利用率。当TCP发送一个SYN时,或者是因为一个本地应用进程想发起一个连接,或者是因为另一端的主机收到了一个连接请求,它能将MSS值设置为外出接口上的MTU长度减去固定的 IP 首部和 TCP 首部长度。对于一个以太网,MSS值可达1460字节使用IEEE802.3的封装,它的MSS可达1452字节

  • 如果目的IP地址为"非本地的(nonlocal)”,MSS通常的默认值为536。而区分地址是本地还是非本地是简单的,如果目的IP地址的网络号与子网号和我们的相同,则是本地的;如果目的IP地址的网络号与我们的完全不同,则是非本地的;如果目的IP地址的网络号与我们的相同而子网号与我们的不同,则可能是本地的,也可能是非本地的。大多数TCP实现版都提供了一个配置选项让系统管理员说明不同的子网是属于本地还是非本地。这个选项的设置将确定MSS可以选择尽可能的大(达到外出接口的MTU长度)或是默认值536。

TCP包头选项字段解码。

image-20231113203207645

Wireshark抓包查看

image-20231113203807960

image-20231122152113165

说明:Shift Count(窗口放大因子)可以发送更多的数据,窗口大小和窗口放大因子相乘。

1.5 TCP 三次握手

image-20231128174249048

image-20230719143849548

三次握手没有传输数据,三次握手成功后才开始传输数据

说明

  • TCP面向连接,可靠的全双工传输层协议。
  • 如图所示,TCP连接建立是三次握手的过程。
  • 首先主机发送SYN数据段(序列号seq为a),请求建立连接。
  • 服务器回复SYN数据段和ACK数据段(序列号seq为b,确认序列号ack为a+1),确认主机A的SYN报文。
  • 主机A发送ACK数据段(序列号seq为a+1,确认序列号ack为b+1)确定服务器SYN报文。

Wireshark抓包查看

image-20231122112116831

TCP三次握手的SYN数据包

image-20231122110715001

TCP三次握手的SYN,ACK数据包

image-20231122110955398

TCP三次握手的ACK数据包

image-20231122111140070

1.6 TCP 三次握手不成功

1.6.1 TCP 拒绝(被RST重置)

image-20231113170921595

被客户端方向RST:可能是存在网络扫描的行为,只是试探某些端口是否开放。

被服务器方向RST:可能是存在网络扫描的行为,只是试探某些端口是否开放。如果服务器的端口没有打开,那么服务器就会回复ACK,RST 数据段不进行建立连接。

OmniPeek抓包查看

首先在本机进行Telnet虚拟机中一个未开放的端口

image-20231122144127897

虚拟机中抓包查看

image-20231122144246122

image-20231122144343265

1.6.2 TCP 半连接

TCP半连接:客户端在发出连接请求后不在发送任何数据。

image-20231113171835200

半连接扫描通过不完全建立TCP连接的方式其扫描过程如下:

  1. 发送SYN包:向目标服务器发送一个TCP SYN包,表示要建立一个TCP连接。
  2. 探测响应:如果目标服务器的端口处于开放状态,那么它将返回一个SYN-ACK包作为响应;如果端口关闭,通常会返回一个RST包。
  3. 关闭连接:客户端在收到响应后,不会发送ACK包来确认连接,而是直接发送一个RST包,关闭连接。
1.6.3 TCP 连接无响应

客户端在和服务器建立连接的时候,服务器没有响应。有可能是对方服务器没有开启,或者服务器有防火墙将我们的数据包丢弃了。

详细说明

  1. 网络问题:
    • 检查网络连接是否正常,确保网络连通性。
    • 检查防火墙设置,防止阻塞了TCP连接。
  2. 目标主机问题:
    • 检查目标主机是否正常运行、可访问。
    • 确保目标主机的服务正在监听相应的端口。
    • 检查目标主机的防火墙设置,确保允许TCP连接通过。
  3. 客户端问题:
    • 检查客户端应用程序是否正确配置了TCP连接参数(如目标IP地址和端口号)。
    • 确保客户端的防火墙设置不会阻塞TCP连接。
    • 尝试使用其他客户端设备或软件进行连接,以确定问题是否与特定设备或软件相关。
  4. 负载问题:
    • 如果目标主机或网络负载过高,可能导致TCP连接无响应。
  5. 软件问题:
    • 检查目标主机上的服务是否正常运行,并且没有发生异常或错误。
    • 确保客户端和服务器之间使用相同的协议和版本,以避免不兼容性问题。
    • 尝试更新客户端和服务器软件的版本,以修复可能存在的错误或漏洞。

1.7 TCP 传输过程及原理

1.7.1 TCP 传输过程

image-20230719144223771

说明

  • TCP 可靠传输体现在确认技术上。
  • 重点在于确认号上。目的设备收到数据段,会向源发送确认报文,源收到后继续发送数据,如此重复。
  • 如图所示,每次传输大约500个字节的数据(载荷长度为500),如果服务器收到前M+1499的字节,会以M+1499+1的序列号进行确认。如果服务器A没收到M+1500系列号的字节,服务器会再次以序列号M+1500进行确认,主机A重发数据,直到收到数据为止。如果成功收到以M+3000的序列号进行确认。

注意:在建立三次握手的时候是+1,而在后续发送数据的时候是发送多少个字节加多少个字节。

1.7.2 TCP 传输原理

image-20231128173629672

应用层将数据准备好后,然后给到TCP协议,TCP协议负责分段发送。通过发送缓存然后将数据分段,发送到对方服务器的接受缓存中。

TCP 累计确认

image-20231113205047370

累计确认:对最后一个Seq数据包的确认,意味着前面所有的数据包也已经确认了。

说明:五个包的编号是6、7、8、9、10号包,两个ACK包的编号为11、12号包。其中11号包是对6号包+7号包部分内容的确认,12号包是对7、8、9、10号包的确认。这几个包从Server的接收发送顺序应该是6、7、11、8、9、10、12,但是由于数据包捕获位置和网络时延原因,所以从Client位置看到的顺序就变成了6、7、8、9、10、11、12。

1.7.3 判断重传与丢包

image-20231113205929926

说明

  • 如果发现两个序列号一样的数据包,或者说后发现的数据包比前面发现的数据包要小,那么可能出现重传。
  • 如果发现当前数据包的序列号大于上一个数据包的next sequence number,说明产生了丢包的行为。

1.8 TCP 流量控制

image-20230719144600543

TCP滑动窗口技术通过改变窗口大小实现流量控制,如图所示,服务器以ACK 3073响应,调整窗口大小3072,主机A改变其发送数据包大小。

1.9 TCP 四次挥手

image-20230719144825065

说明

  • TCP全双工传输数据,TCP三次握手建立的是两个方向的连接,传输完毕后两个方向连接也要都关闭。关闭为四次握手。
  • 主机A想终止连接,发送标识FIN,ACK数据段(序列号为a,确认序列号为b)。
    • 为什么还发送ACK数据段呢?因为在结束连接的同时需要对上一个数据包进行一个确认。
  • 服务器发送ACK数据段(序列号为b,确认序列号为a+1),对于主机A的FIN确认。
  • 服务器A也要终止连接,向主机发送FIN,ACK数据段(序列号为b,确认序列号为a+1)。(如果没有传输完不会发送该包)
  • 主机A回复了ACK数据段(序列号为a+1,确认序列号为b+1),对FIN报文的确认。
  • 以上四次交互完成两个方向连接的关闭。

Wireshark抓包查看

image-20231113174340432

TCP四次挥手的FIN,ACK的数据包

image-20231122131341397

TCP四次挥手的ACK的数据包

image-20231122131527864

TCP四次挥手的FIN,ACK的数据包。

image-20231122131650301

TCP四次挥手的ACK的数据包

image-20231122131807478

OminPeek抓包查看

image-20231122132509662

TCP四次挥手的FIN,ACK的数据包。

image-20231122132916981

TCP四次挥手的ACK的数据包。

image-20231122132933998

TCP四次挥手的FIN,ACK的数据包。

image-20231122133202175

TCP四次挥手的ACK的数据包。

image-20231122133222145

1.10 TCP 状态

image-20231122140312243

TCP状态如下

  • CLOSED状态:初始状态,表示TCP连接是“关闭的”或者“未打开的”。
  • LISTEN状态:表示服务端的某个端口正处于监听状态,正在等待客户端连接的到来。
  • SYN_SENT状态:当客户端发送SYN请求建立连接之后,客户端处于SYN_SENT状态,等待服务器发送SYN+ACK。
  • SYN_RCVD状态:当服务器收到来自客户端的连接请求SYN之后,服务器处于SYN_RCVD状态,在接收到SYN请求之后会向客户端回复一个SYN+ACK的确认报文。
  • ESTABLISED状态:当客户端回复服务器一个ACK和服务器收到该ACK(TCP最后一次握手)之后,服务器和客户端都处于该状态,表示TCP连接已经成功建立。
  • FIN_WAIT_1状态:当数据传输期间当客户端想断开连接,向服务器发送了一个FIN之后,客户端处于该状态。
  • FIN_WAIT_2状态:当客户端收到服务器发送的连接断开确认ACK之后,客户端处于该状态。
  • CLOSE_WAIT状态:当服务器发送连接断开确认ACK之后但是还没有发送自己的FIN之前的这段时间,服务器处于该状态。
  • TIME_WAIT状态:当客户端收到了服务器发送的FIN并且发送了自己的ACK之后,客户端处于该状态。
  • LAST_ACK状态:表示被动关闭的一方(比如服务器)在发送FIN之后,等待对方的ACK报文时,就处于该状态。
  • CLOSING状态:连接断开期间,一般是客户端发送一个FIN,然后服务器回复一个ACK,然后服务器发送完数据后再回复一个FIN,当客户端和服务器同时接受到FIN时,客户端和服务器处于CLOSING状态,也就是此时双方都正在关闭同一个连接。

cmd输入如下命令:

netstat -ano

image-20231122140452150

1.10.1 2MSL 等待状态

TIME WAIT状态也称为2MSL等待状态。每个具体TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime)。它是任何报文段被丢弃前在网络内的最长时间。

  • RFC 793 [Postel 1981c] 指出MSL为2分钟。然而在具体的实现中,常用值是30秒,1分钟,或2分钟。
  • 对一个具体实现所给定的MSL值,处理的原则是:当TCP执行一个主动关闭,并发回最后一个ACK,该连接必须在TIME WAIT状态停留的时间为2倍的MSL。
    • 这样可让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的FIN),这种2MSL等待的另一个结果是这个TCP连接在2MSL等待期间,定义这个连接的插口(客户的IP地址和端口号,服务器的IP地址和端口号)不能再被使用。这个连接只能在2MSL结束后才能再被使用。
1.10.2 FIN_WAIT_2 状态

在FIN_WAIT_2状态是已经发出了FIN,并且另一端也已对它进行确认。除非在实行半关闭,否则将等待另一端的应用层意识到它已收到一个文件结束符说明,并向我们发一个 FIN 来关闭另一方向的连接。只有当另一端的进程完成这个关闭,我们这端才会从FIN_WAIT_2状态进入TIME WAIT状态。这意味着我们这端可能永远保持这个状态。另一端也将处于CLOSE WAIT状态,并一直保持这个状态直到应用层决定进行关闭。

1.11 TCP 同时连接及同时关闭

TCP为应用层提供全双工服务。

同时连接

image-20231122150030869

同时关闭

image-20231122150130388

1.12 TCP 的交互数据流

如果按照分组数量计算,约有一半的TCP报文段包含成块数据(如FTP、电子邮件等)另一半则包含交互数据(如Telnet 和 Rlogin [也是一种远程登录协议] )。如果按字节计算,则成块数据与交互数据的比例约为90%和10%。TCP需要同时处理这两类数据,但使用的处理算法则有所不同。

本章将以Rlogin应用为例来观察交互数据的传输过程。

在TCP交互数据流中,数据被分割成称为数据段(segment)的小块,这些数据段会通过网络从发送方传输到接收方。每个数据段都包含了序列号(sequence number)来标识数据在整个数据流中的顺序,并且有确认号(acknowledgment number)用于确认已经接收到的数据。

1.12.1 交互式输入

对于交互式输入,客户端可以向服务器发送用户输入的命令或数据,然后服务器接收并处理这些数据,并将结果返回给客户端。客户端再次接收服务器返回的结果,并根据需要继续发送更多的命令或数据。

image-20231122152654403

通常每一个交互按键都会产生一个数据分组。客户端发一个字节,服务器确认一个字节。

这样会产生4个报文段:

  1. 来自客户的交互按键。
  2. 来自服务器的按键确认。
  3. 来自服务器的按键回显。
  4. 来自客户的按键回显确认。
1.12.2 经受时延的确认

经受时延的确认(Delayed Acknowledgment)是指 TCP 协议中一种优化技术,它可以将多个 ACK 确认报文合并成一个,从而减少网络上的报文交换次数,提高网络性能和吞吐量。

TCP 协议中规定,接收方必须对每个成功接收的数据包发送一个 ACK 确认报文。但是,如果接收方每次都立即返回 ACK 确认报文,那么网络上的报文交换次数会明显增加,导致网络性能下降。因此在实际应用中,通常会使用经受时延的确认技术来优化 TCP 连接的性能。

image-20231122153221192

  • 通常客户端在接收到数据时并不立即发送ACK,相反它推迟发送,以便将ACK与需要沿该方向发送的数据一起发送(有时称这种现象为数据捎带ACK)。绝大多数实现采用的时延为200ms,也就是说,TCP将以最200ms的时延等待是否有数据一起发送。
  • svr4不仅对发送的数据进行了确认,还进行了回显。
  • 客户端一般有延时确认,服务器端没有延时确认。
1.12.3 Nagle 算法

Nagle 算法的核心思想是将多个较小的数据块合并成一个较大的数据块,并在延迟一定时间后才发送。具体当发送方准备发送数据时,如果之前的数据还没有收到确认,那么当前的数据将被缓存起来,等待之前的数据得到确认后再一起发送。这样做的目的是避免发送过多的小数据包,从而减少网络上的报文交换次数。所以在客户端的窗口大小是小于4096的,而服务器端的窗口大小通常是全窗口大小8192。

image-20231122161830168

该算法要求一个TCP连接上最多只能有一个未被确认的小分组,在该分组的确认到达之前不能发送其他的小分组。相反,TCP收集这些小分组,并在确认到来时以一个分组(包含多个小分组的数据)的方式发出去。该算法的优越之处在于它是自适应的:确认到达得越快,数据也就发送得越快。默认情况下,Nagle算法都是启用的。

注意到从左到右待发数据的长度是不同的分别为:1、1、2、1、2、2、3、1和3个字节。这是因为客户只有收到前一个数据的确认后才发送已经收集的数据。通过使用Nagle算法,为发送16个字节的数据客户只需要使用9个报文段,而不再是16个。

窗口大小

  • 报文段5通告的窗口大小为4095个字节,这意味着在TCP的缓冲区中仍然有一个字节等待应用程序(Rlogin客户)读取。同样,来自客户的下一个报文段声明其窗口大小为4094个字节,这说明仍有两个字节等待读取。
  • 服务器通告窗口大小为8192个字节,这是因为服务器在读取并回显接收到的数据之前,其TCP客户没有数据发送。当服务器已经读取了来自客户的输入后,来自服务器的数据将被发送。
  • 然而,在ACK到来时,客户的TCP总是有数据需要发送。这是因为它在等待ACK的过程中缓存区接收到的字符。当客户TCP发送缓存的数据时,Rlogin客户没有机会读取来自服务器的数据,因此客户通告的窗口大小总是小于4096。
1.12.4 关闭 Nagle 算法

有时我们也需要关闭Nagle算法。比如终端的功能键(F1键等)通常可以产生多个字符序列。当开启Nagle算法时,TCP很可能会发送序列中的第一个字符,然后缓存其他字符并等待对该字符的确认。但当服务器接收到该字符后,它并不发送确认,而是继续等待接收序列中的其他字符。这就会经常触发服务器的经受时延的确认算法。对交互用户而言,这将产生明显的时延。

1.13 TCP 的成块数据流

TFTP使用了停止等待协议。数据发送方在发送下一个数据块之前需要等待接收对已发送数据的确认。

TCP使用滑动窗口协议,该协议允许发送方在停止并等待确认前可以连续发送多个分组。由于发送方不必每发一个分组就停下来等待确认,因此该协议可以加速数据的传输。

1.13.1 正常数据流

通常使用的 “隔一个报文段确认” 的策略。在线路上看到的分组顺序依赖于许多无法控制的因素:发送方TCP的实现、接收方TCP的实现、接收进程读取数据(依赖于操作系统的调度和网络的动态性(如以太网的冲突和退避等)。对这两个TCP而言,没有一种单一的、正确的方法来交换给定数量的数据。

image-20231122201935863

说明

  1. 报文段7、14和16中的ACK确认了两个收到的报文段。使用TCP的滑动窗口协议时,接收方不必确认每一个收到的分组。在TCP中,ACK是累积的—它们表示接收方已经收到了一直到确认序号减1的所有字节。
  2. 报文段8的窗口大小为3072。这说明接收方的缓冲区还有1024字节数据等待应用层读取。
1.13.2 快的发送方和慢的接受方

下图是从一个快的发送方发送8092字节数据到一个慢的接收方产生的数据流:

image-20231122202749644

  • 接收方发送ACK(报文段8)但通告其窗口大小为0,这说明接收方已收到所有数据,但这些数据都在接收方的 TCP 缓冲区因为应用程序还没有机会读取这些数据。
  • 另一个ACK(报文段9,称为窗口更新)在17.4ms后发送,表明接收方现在可以接收另外的4096个字节的数据。虽然这看起来像一个ACK,但由于它并不确认任何新数据,只是用来增加窗口的右边沿,因此被称为窗口更新。
1.13.3 滑动窗口

下图用可视化的方法显示了上面观察到的滑动窗口协议:

image-20231122203513980

待发送的数据字节从1至11标号,这些数据分为如下四部分:

  1. 已经发送并被确认(1~3)。

  2. 已经发送但未被确认(4~6)。

  3. 能够发送(7~9)。

  4. 不能够发送(10~11)。

image-20231122204334614

接收方通告的窗口被称为提供的窗口,它覆盖49字节的区域(窗口大小为6)。发送方计算它的可用窗口(79),该窗口表明多少数据可以立即被发送。使用三个术语来描述窗口左右边沿的运动:

  1. 称窗口左边沿向右边沿靠近为窗口合拢。这种现象发生在数据发送和被确认时。
  2. 当窗口右边沿向右移动时将允许发送更多的数据,我们称之为窗口张开。这种现象发生在另一端的接收进程读取已经确认的数据并释放了TCP的接收缓存时
  3. 当右边沿向左移动时,我们称之为窗口收缩。RFC1122强烈建议不要使用这种方式

如果左边沿到达右边沿,则称其为一个零窗口,此时发送方不能够发送任何数据。

下图说明了第一个图中所示的数据传输过程中滑动窗口协议的动态性:

image-20231122201935863

image-20231122204631922

以该图为例可以总结如下几点:

  1. 发送方不必发送一个全窗口大小的数据。
  2. 来自接收方的一个报文段确认数据并把窗口向右边滑动。这是因为窗口的大小是相对于确认序号的。
  3. 正如从报文段7到报文段8中变化的那样,窗口的大小可以减小(发了数据,但是没有读取),但是窗口的右边沿却不能够向左移动。
  4. 接收方在发送一个ACK前不必等待窗口被填满才确认。在前面看到许多实现每收到两个报文段就会发送一个ACK。
1.13.4 窗口大小通告

由接收方提供的窗口的大小和接收缓冲区大小相关,这会影响TCP的性能。缓冲区(接收和发送)大小可以由接收进程设置。

在4.3 BSD中,发送方和接收方的默认缓冲区大小为4096个字节。4.4 BSD中则使用更大的数值,如8192或16384。一份研究表明:对以太网而言,默认的4096字节并不是最理想的缓冲区大小,将大小增加到16384个字节可以增加约40%左右的吞吐量。

BSD(Berkeley Software Distribution)是一个基于Unix操作系统的开源软件项目。

下图是一个接收方使用6144字节大小的缓冲区的例子:

image-20231122210338191

注意的是14、15和16这三个连续的窗口更新报文段。(也就是表示放大窗口,告诉客户端这里有多余的空间,可以进行发送数据。)

1.13.5 PUSH 标志
  • 发送方使用PUSH标志通知接收方将接收缓冲区中所有的数据提交给接收进程
  • 如果待发送数据将清空发送缓存, 则大多数的源于伯克利的实现能够自动设置 PUSH标志。比如图一中观察到所有的8个数据报文段(46、9、1113和15)都设置了PUSH标志。
  • 目前大多数的API没有向应用程序提供通知其TCP设置PUSH标志的方法。许多程序认为PUSH标志已经过时,一个好的TCP实现能够自行决定何时设置这个标志。
1.13.6 慢启动

在所有的例子中,发送方一开始便向网络发送多个报文段,直至达到接收方通告的窗口大小为止。当发送方和接收方处于同一个局域网时,这种方式是可以的。但是如果在发送方和接收方之间存在多个路由器和速率较慢的链路时,就有可能出现一些问题。

为解决这些问题,TCP使用一种被称为 “慢启动(slow start)” 的算法。该算法通过观察到新分组进入网络的速率应该与另一端返回确认的速率相同而进行工作

慢启动为发送方的TCP增加了另一个窗口:拥塞窗口(congestion window),记为cwnd。当与另一个网络的主机建立TCP连接时,拥塞窗口被初始化为1个报文段(即另一端通告的报文段大小)。每收到一个ACK,拥塞窗口就增加一个报文段(cwnd以字节为单位,但是慢启动以报文段大小为单位进行增加)。发送方取拥塞窗口与通告窗口中的最小值作为发送上限。拥塞窗口是发送方使用的流量控制,而通告窗口则是接收方使用的流量控制

发送方开始时发送一个报文段,然后等待ACK。当收到该ACK时拥塞窗口从1增加为2,即可以发送两个报文段。当收到这两个报文段的ACK时,拥塞窗口就增加为4。这是一种指数增加的关系。

image-20231122213547106

示例

image-20231122214539931

对发送端:

  • 第一次,发 1 个MSS。

  • 等待这1个MSS的ACK,并收到 1 个ACK。由于原来可以发1个MSS,现在又增加了一个。因此,第二次可以发2个MSS。

  • 第二次,发 2 个MSS。

  • 等待这2个MSS的ACK, 并收到2个MSS的ACK。由于原来有2个MSS,现在收到2个ACK,又可以增加2个MSS。因此,下一次可以发送2+2=4个MSS。

  • 第三次,发送4个MSS。

对于发送端:

  1. 初始化cwnd为1个报文段,发送了一个报文段;
  2. 收到了ACK。这时,cwnd为2了,可以发送2个报文段(MSS)的大小(可发送多少个报文段,取cwnd和对端通告窗口大小中的小者,注意单位不一样),也就是说,可以累积发送但未确认的报文段这时是2个。
  3. 然后发送了2个报文段(报文段3和4)。
  4. 收到了一个ACK,这时cwnd变为了3,已发送但未收到确认的报文段为1个(上次发了2个报文段,这里收到了一个回复ACK)。这时,总共可以发送的报文段(累积未确认)个数为3(cwnd和对端通告窗口中的小者)。因此接下来,又发送了3-1=2个报文段。

注意:这中间拥塞窗口cwnd不是跳跃增长的(如,1,2,4,8,16),而是每收到一个ACK之后,都会增长1,而不是等所有的ACK都到了之后,一次性跳跃增长。(见上图中的cwnd值:1,2,3,4,5)。而且,只要能发送数据(当前已发送的数据量小于对端的通告窗口和cwnd中的小者),就会发送数据。实际上,并不存在所谓的“第二次必须一次性发2个MSS”、“第三次一次性发4个MSS”的说法。整个发送过程和接收ACK的过程,都是连续的,而非集中和跳跃的。

1.13.7 成块数据的吞吐量

image-20231123090716253

在时间0,发送方发送了一个报文段。由于发送方处于慢启动中(其拥塞窗口为1个报文段),因此在继续发送以前它必须等待该数据段的确认。在时间1, 2和3,报文段从左向右移动一个时间单元。在时间4接收方读取这个报文段并产生确认。经过时间5、6和7,ACK移动到左边的发送方。 于是我们有了一个8个时间单元的往返时间(Round-TripTime,RTT)。

当发送方收到ACK后,在时间8和9发送两个报文段(标记为2和3)。此时它的拥塞窗口为2个报文段。这两个报文段向右传送到接收方,在时间12和13接收方产生两个ACK。

image-20231123090759813

2个ACK的到达使得拥塞窗口从2个报文段增加为4个,而这4个报文段在时间1619时被发送。第1个ACK在时间23到达。4个ACK的到达使得拥塞窗口从4个报文段增加为8个,并在时间2431发送8个报文段。

在时间31及其后续时间,发送方和接收方之间的管道被填满。此时不论拥塞窗口和通告窗口是多少,它都不能再容纳更多的数据。每当接收方在某一个时间单位从网络上移去一个报文段,发送方就再发送一个报文段到网络上。但是不管有多少报文段填充了这个管道,返回路径上总是具有相同数目的ACK。这就是连接的理想稳定状态。

1.13.8 带宽时延乘积

现在来回答窗口应该设置为多大的问题。 在例子中,作为最大的吞吐量,发送方在任何时候有8个已发送的报文段未被确认。接收方的通告窗口必须不小于这个数目,因为通告窗口限制了发送方能够发送的段的数目。

可以计算通道的容量为:带宽 * 往返时间

  • capacity(bit)=bandwidth(b/s) × round-trip time(s)

一般称之为带宽时延乘积。

1.13.9 拥塞

当数据到达一个大的管道(如一个快速局域网)并向一个较小的管道(如一个较慢的广域网)发送时便会发生拥塞。当多个输入流到达一个路由器,而路由器的输出流小于这些输入流的总和时也会发生拥塞。

下图显示了一个典型的大管道向小管道发送报文的情况:

image-20231123091749607

在该图中,已经标记路由器 R1 为“瓶颈”,因为它是拥塞发生的地方。它从左侧速率较高的局域网接收数据并向右侧速率较低的广域网发送。 当路由器R2将所接收到的分组发送到右侧的局域网时,这些分组之间维持与其左侧广域网上同样的间隔,尽管局域网具有更高的带宽。类似地,返回的确认之间的间隔也与其在路径中最慢的链路上的间隔一致。

1.14 TCP 的超时和重传

TCP提供可靠的传输层。它使用的方法之一就是确认从另一端收到的数据。但数据和确认都有可能会丢失。TCP通过在发送时设置一个定时器来解决这种问题。如果当定时器溢出出时还没有收到确认,它就重传该数据。对任何实现而言,关键之处就在于超时和重传的策略,即怎样决定超时间隔和如何确定重传的频率

对每个连接,TCP管理4个不同的定时器。

  1. 重传(retransmit)定时器使用于当希望收到另一端的确认。
  2. 持续(persist)定时器使窗口大小信息保持不断流动,即使另一端关闭了其接收窗口。
  3. 保活(keepalive)定时器可检测到一个空闲连接的另一端何时崩溃或重启。
  4. 2MSL定时器测量一个连接处于TIME_WAIT状态的时间。

示例

image-20231123093144241

从上图中观察连续重传之间的时间差(称为超时重传时间,RTO),它们取整后分别为1、3、6、12、24、48和多个64秒。这个倍乘关系被称为 “指数退避(exponential backoff)”。在多个64秒超时之后,TCP发送复位报文段。注意:实际上TCP设置的第一个超时时间是1.5秒,而不是1秒。

1.14.1 往返时间测量
  • RTT(round trip time):往返时间。RTT 表示一次重返时间,用来衡量网络的时延。
  • RTO(retransmissiontimeout):重传超时时间。

目前TCP的实现,重传超时时间都是以RTT的幂次倍来实现的,即RTO = RTT * 2 ^n, n表示第几次重传,即超时时间是指数递增,而非线性递增,RTO的最大值为超过64秒。

需要注意的是,RTT测量采样时,不计入重传的报文段。这是因为,一个报文段一旦重传,无法区分收到的该报文段的ACK对应于哪次发送的报文段。

image-20231123093905664

重传多义性问题

在一个分组(数据包)重传时会产生这样一个问题:假定一个分组(数据包)被发送。当超时发生时,RTO正进行指数退避,分组以更长的RTO进行重传,然后收到一个确认。那么这个ACK是针对第一个数据包的还是针对第二个数据包呢?

Karn算法

当一个超时和重传发生时,在重传数据的确认最后到达之前,不能更新RTT估计器。

1.14.2 往返时间RTT示例

image-20231123094433290

看到TCP在5秒的时间内获取到了3次RTT值(重传超时时间值),分别是1.061秒、0.808秒和1.015秒。

大多数的TCP实现在任何时候对每个连接仅测量一次RTT值

下图显示了本例中实际RTT与时钟滴答计数之间的关系:

image-20231123094814587

虚线是500ms定时器溢出的时刻。以1.061秒的RTT为例:TCP发送报文段1时的时刻为0,此时开始对报文段计时。在0.03秒的时刻,500ms定时器第一次溢出,计数器的值为1。在0.53秒的时刻,定时器第二次溢出,计数器值为2。在1.03秒的时候定时器第三次溢出,计数器的值为3。在1.061秒的时刻,收到包含报文段1的数据起始序号的ACK,因此不再计数,这个报文段的RTT为3个滴答,也就是1.5秒。

对每个连接而言,除了这个滴答计数器,报文段中数据的起始序号也被记录下来。当收到一个包含这个序号的确认后,该定时器就被关闭。如果ACK到达时数据没有被重传,则被平滑的RTT和被平滑的均值偏差将基于这个新测量进行更新

1.14.3 拥塞举例

下图是例子中发生拥塞时的一个数据传输过程:

image-20231123095942516

说明

  • 看来报文段45丢失或损坏了,这一点无法从该输出上进行辨认。能够在主机slip上看到的是对第6657字节(报文段58)以前数据的确认(不包括字节6657在内)。紧接着的是带有相同序号的8个ACK。正是接收到报文段62,也就是第3个重复ACK,才引起自序号6657开始的数据报文段(报文段63)进行重传。TCP实现对收到的重复ACK进行计数,当收到第3个重复的ACK时,就假定一个报文段已经丢失并重传自那个序号起的一个报文段。
  • 注意到在重传后(报文段63),发送方继续正常的数据传输(报文段67、69和71)。TCP不需要等待对方确认重传。
  • 目前TCP尚无办法告诉对方缺少一个报文段,也无法确认失序数据。此时主机vangogh所能够做的就是继续发送确认序号为6657的ACK。当缺少的报文段(报文段63)到达时,接收方TCP在其缓存中所保存第6657~8960字节的数据,并将这2304字节的数据一并交给用户进程。所有这些数据在报文段72中进行确认。请注意此时该ACK通告窗口大小为5888(8192-2304),这是因为用户进程没有机会读取这些已准备好的2304字节的数据。
1.14.4 拥塞避免算法

拥塞避免算法是一种处理丢失分组的方法

该算法假定由于分组受到损坏引起的丢失是非常少的(远小于1%),因此分组丢失就意味着在源主机和目的主机之间的某处网络上发生拥塞。

有两种分组丢失的指示:发生超时和接收到重复的确认。如果使用超时作为拥塞指示,则需要使用一个好的RTT算法。

拥塞避免算法和慢启动算法是两个目的不同、独立的算法。当拥塞发生时,降低分组进入网络的传输速率,于是可以调用慢启动来作到这一点。在实际中这两个算法通常在一起实现。

拥塞避免算法和慢启动算法需要对每个连接维持两个变量:一个拥塞窗口cwnd和一个慢启动阀值ssthresh。这样得到的算法的工作过程如下:

  1. 对一个给定的连接,初始化cwnd(拥塞窗口)为1个报文段,ssthresh(慢启动阀值)为65535个字节。

  2. TCP输出例程的输出不能超过cwnd和接收方通告窗口的大小。拥塞避免是发送方使用的流量控制,而通告窗口则是接收方进行的流量控制前者是发送方感受到的网络拥塞的估计,而后者则与接收方在该连接上的可用缓存大小有关

  3. 当拥塞发生时(超时或收到重复确认),ssthresh被设置为当前窗口大小(cwnd和接收方通告窗口大小的最小值,但最少为2个报文段)的一半。此外,如果是超时引起了拥塞,则cwnd被设置为1个报文段(这就是慢启动)。

    注意:拥塞发生的两种情况:

    • 超时:是发送端发送了数据后,启动重传定时器,在规定时间内未收到ACK,于是发生了超时。
    • 重复ACK:是指在接收方收到乱序报文时,所发出的一类TCP报文。重复ACK,是因为数据未按序到达接收端造成的,是由于接收端触发的,是对到目前为止收到的连续数据段的确认,重复ACK,是接收端触发并通知到发送端;例如,现在接收端已经收到0-35个字节,已给客户端回复ACK=36,第36以后的字节未收到;但这时收到了40-43字节,但由于第36-39字节并未收到,于是回复给发送端的ACK仍是ACK=36,这对发送端来说,ACK=36 即是一个重复ACK;这时,又收到了44-47字节,仍是因为未收到36~39字节,所以必须回复给发送端ACK=36,这对发送端来说,ACK=36,又是一个重复确认的ACK。
  4. 当新的数据被对方确认时,就增加cwnd,但增加的方法依赖于我们是否正在进行慢启动或拥塞避免。如果cwnd小于或等于ssthresh,则正在进行慢启动,否则正在进行拥塞避免。慢启动一直持续到我们回到当拥塞发生时所处位置一半的时候才停止,然后转为执行拥塞避免。

image-20231123102930903

慢启动算法及拥塞避免算法的区别

  • 慢启动算法初始设置cwnd为1个报文段,此后每收到一个确认就加1。正如前面描述的那样,这会使窗口按指数方式增长:发送1个报文段,然后是2个,接着是4个……。
  • 拥塞避免算法要求每次收到一个确认时将cwnd增加1/cwnd。与慢启动的指数增加比起来,这是一种线性增长。我们希望在一个往返时间内最多为cwnd增加1个报文段(不管在这个RTT中收到了多少个ACK),然而慢启动将根据这个往返时间中所收到的确认的个数增加cwnd。

下图是慢启动和拥塞避免的一个可是化描述:

image-20231123103103556

说明:以段为单位来显示 cwnd和 ssthresh(慢启动阀值),但它们实际上都是以字节为单位进行维护的。 在该图中,假定当cwnd为32个报文段时就会发生拥塞。于是设置ssthresh为16个报文段,而cwnd为1个报文段。在时刻0发送了一个报文段,并假定在时刻1接收到它的ACK,此时cwnd增加为2。接着发送了2个报文段,并假定在时刻2接收到它们的ACK,于是cwnd增加为4(对每个ACK增加1次)。这种指数增加算法一直进行到在时刻3和4之间收到8个ACK后cwnd等于ssthresh时才停止,从该时刻起,cwnd以线性方式增加,在每个往返时间内最多增加1个报文段。

1.14.5 拥塞避免示例

image-20231123111935174

在该例中,建立连接时,第一个SYN超时。

  • 假设MMS为256,发送端(发起连接端)初始设置cwnd为256(即1个报文段),ssthresh初始值设为65535,然后发出第一个SYN包。但该SYN包超时了,在定时器提示超时后,设置ssthresh为cwnd的一半,但ssthresh最小也必须是512(即2个报文段),于是设置ssthresh为512

  • 接着发出第2个SYN包。然后收到了接收端的SYN+ACK 包,这时自己这端的cwnd仍为256,仍处理慢启动,于是发出数据包,在发出数据包后接着设置自己这一端的ssthresh为2个报文段。(注意,这里在实现上是:接收到对端发来的数据包,参考自己当前的cwnd值,发出数据包,然后再设置自己的cwnd值------先发数据包后改cwnd)。

image-20231123112311752

1.14.6 快速重传算法和快速恢复算法

快速重传算法:如果一连串收到3个或3个以上的重复ACK,就非常可能是一个报文段丢失。于是就重传丢失的数据报文段,而无需等待超时定时器溢出。

快速恢复算法:快速重传后执行的不是慢启动算法,而是拥塞避免算法。

在拥塞举例中:在这种情况下没有执行慢启动的原因是由于收到重复的ACK不仅仅告诉我们一个分组丢失了。由于接收方只有在收到另一个报文段时才会产生重复的ACK,而该报文段已经离开了网络并进入了接收方的缓存。也就是说,在收发两端之间仍然有流动的数据,而我们不想执行慢启动来突然减少数据流。(降低速率)

示例

image-20231123141359382

说明:当最初的2个重复的ACK(报文段60和61)到达时它们被计数,而cwnd保持不变(也就下图中处理重传之前的平坦的一段)。然而,当第3个重复的ACK到达时,ssthresh被置为cwnd的一半(四舍五入到报文段大小的下一个倍数)而cwnd被置为ssthresh加上所收到的重复的ACK数乘以报文段大小(也即1024加上3倍的256),然后发送重传数据。又有5个重复的ACK到达(报文段64~66,68和70),每次cwnd增加1个报文段长度。最后一个新的ACK(报文段72段)到达时,cwnd被置为ssthresh(1024)并进入正常的拥塞避免过程。由于cwnd小于等于ssthresh(现在相等),因此报文段的大小增加到cwnd,取值为1280。

image-20231123140948910

image-20231123142122020

1.15 ICMP 差错

TCP能够遇到的最常见的ICMP差错就是源站抑制、主机不可达和网络不可达。对这些错误的处理是:

  • 一个接收到的源站抑制引起拥塞窗口cwnd被置为1个报文段大小来发起慢启动,但是慢启动门限ssthresh没有变化,所以窗口将打开直至它或者开放了所有的通路(受窗口大小和往返时间的限制)或者发生了拥塞。
  • 一个接收到的主机不可达或网络不可达实际上都被忽略,因为这两个差措都被认为是短暂现象。这有可能是由于中间路由器被关闭而导致选路协议要花费数分钟才能稳定到另一个替换路由。

注意:对于一条TCP连接,如果一个中间设备给发送方回复了远端主机不可达,那么发送方会忽略掉该报文,认为网络只是暂时的不可达,而不是将TCP连接关闭。

1.16 TCP 的重新分组

当TCP超时并重传时,它不一定要重传同样的报文段。相反,TCP允许进行重新分组而发送一个较大的报文段,这将有助于提高性能(当然,这个较大的报文段不能够超过接收方声明的MSS(最大报文长度))。

1.17 TCP 的坚持定时器

image-20231123153635236

image-20231122202749644

ACK的传输并不可靠,也就是说,TCP不对ACK报文段进行确认,TCP只确认那些包含有数据的ACK报文段。如上图中的报文段9丢失怎么办?

问题和解决方法

  • 问题:如果一个确认丢失了,则双方就有可能因为等待对方而使连接终止:接收方等待接收数据(因为它已经向发送方通告了一个非0的窗口)而发送方在等待允许它继续发送数据的窗口更新。
  • 解决方法:为防止这种死锁情况的发生,发送方使用一个坚持定时器(persist timer)来周期性地向接收方查询,以便发现窗口是否已增大。这些从发送方发出的报文段称为窗口探查(windowprobe)

发送端在接收到接收端通告窗口大小为0的报文后,会启动一个定时器,即坚持定时器,在一定时间内未收到接收端通告窗口大小的报文,发送方将主动发送一个查询报文,向接收端查询通告窗口大小,接收端收到该报文后,回复窗口大小的报文(如第9个报文)。

示例

image-20231123150543449

  • 其中,第13个报文,是接收方告诉发送方其接收窗口大小为0了,不能接收更多的数据。
  • 从第14个报文开始,发送方开始发出查询报文,接收方接收到查询报文后,回复自己当前的接收窗口大小。

特点

  • 计算坚持定时器时使用了普通的TCP指数退避。
  • 对一个典型的局域网连接,首次超时时间算出来是1.5秒,第2次的超时值增加一倍,为3秒,再下次乘以4为6秒,之后再乘以8为12秒等。但是坚特定时器总是在5~60秒之间。
  • 窗口探查包含一个字节的数据(序号为9217)。TCP总是允许在关闭连接前发送一个字节的数据。请注意,尽管如此,返回的窗口为0的ACK并不是确认该字节(它们确认了包括9216在内的所有数据),因此这个字节被持续重传。被特续用来探测对端窗口的变化情况。

1.18 糊涂窗口综合症

糊涂窗口综合症,SWS(Silly Window Syndrome)糊涂窗口综合症是指,接收方发送的通知窗口很小,导致发送方每次只能发送较小的报文段(远不到一个MSS,比如一次发送1Byte的数据),这样会导致整个TCP的效率非常低(因为TCP头,IP头等的封装,使得有效数据所占的比率非常低)。

该现象可发生在两端中的任何一端:接收方可以通告一个小的窗口(而不是一直等到有大的窗口时才通告),而发送方也可以发送少量的数据(而不是等待其他的数据以便发送一个大的报文段)。可以在任何一端采取措施避免出现糊涂窗口综合症的现象。

image-20231123153505582

糊涂窗口综合症解决

  1. 接收方不通告小窗口。 通常的算法是接收方不通告一个比当前窗口大的窗口 (可以为0),除非窗口可以增加一个报文段大小(也就是将要接收的 MSS)或者可以增加接收方缓存空间的一半,不论实际有多少。

  2. 发送方避免出现糊涂窗口综合症的措施是只有以下条件之一满足时才发送数据:

    • 发送方可以发送一个满长度的报文段;
    • 发送方可以发送至少是接收方通告窗口大小一半的报文段;
    • 发送方可以发送任何数据并且不希望接收 ACK(也就是说,我们没有还未被确认的数据)或者该连接上不能使用Nagle算法。

示例

image-20231123151944460

  • 报文段8是发送方的坚持定时器又到时时发送的一个字节,并在报文段9中被确认,而接收方的缓存空间还不够用(1022字节),使得通告窗口为0。(为了避免糊涂窗口综合征)
  • 发送方的坚持定时器再次到时的时候,发送了另一个字节并被确认(报文段10、11),这次接收方有了1533字节的有效缓存空间,因此通告了1533字节的窗口,而发送方只发送了1024字节(报文段12),接收方接收到后,对这1024字节的数据进行确认(报文段13),通告其窗口为509字节,这看起来与通告小窗口相抵触,但这是为了不违反窗口右边沿不能向左移动而导致窗口收缩的TCP原则。

1.19 TCP 的保活定时器

引言

如果TCP连接的双方都没有向对方发送数据,则在两个TCP模块之间不交换任何信息。

这意味着我们可以启动一个客户与服务器建立一个连接,然后离去数小时、数天、数个星期或者数月,而连接依然保持。中间路由器可以崩溃和重启,电话线可以被挂断再连通,但是只要两端的主机没有被重启,则连接依然保持建立。

保活并不是TCP规范中的一部分。 Host Requirements RFC提供了3个不使用保活定时器的理由:

  1. 在出现短暂差错的情况下,这可能会使一个非常好的连接释放掉;
  2. 它们耗费不必要的带宽;
  3. 在按分组计费的情况下会在互联网上花掉更多的钱。

然而,许多实现提供了保活定时器。保活定时器是一个有争论的功能。许多人认为如果需要,这个功能不应该在 TCP中提供,而应该由应用程序来完成。

工作细节

如果一个给定的连接在两个小时之内没有任何动作,则服务器就向客户发送一个探查报文段。客户主机必须处于以下4个状态之一。

  1. 客户主机依然正常运行,并从服务器可达。客户的TCP响应正常,而服务器也知道对方是正常工作的。服务器在两小时以后将保活定时器复位。如果在两个小时定时器到时间之前有应用程序的通信量通过此连接,则定时器在交换数据后的未来2小时再复位。
  2. 客户主机已经崩溃,并且关闭或者正在重新启动。在任何一种情况下,客户的 TCP都没有响应。服务器将不能够收到对探查的响应,并在75秒后超时。服务器总共发送10个这样的探查,每个间隔75秒。如果服务器没有收到一个响应,它就认为客户主机已经关闭并终止连接。
  3. 客户主机崩溃并已经重新启动。这时服务器将收到一个对其保活探查的响应,但是这个响应是一个复位,使得服务器终止这个连接。
  4. 客户主机正常运行,但是从服务器不可达。这与状态2相同,因为TCP不能够区分状态4与状态2之间的区别,它所能发现的就是没有收到探查的响应。

示例

远端主机关机或者崩溃

image-20231123155041658

  • 第4行开始是发送第1个保活探查的开端,发生在7200秒后,在第6行该保活探查报文段发送前,先观察到的是arp请求和应答,之后引出了接收端的响应(第7行),两小时后,第8~11行发生了同样的分组交换过程。
  • 第6行和第10行的保活探查报文中的序号字段比下一个将要发送的序号小1(本例中,下一个应该发送的序号是14,但保活探查中发送的序号是13),但报文段中没有数据,因此tcpdump不能打印出序号字段,之后服务器对其进行响应,告诉客户下一个期望的序号是14。

远端主机崩溃并重启

image-20231123155116625

  • 客户两小时后发送的第一个保活探查的响应是服务器的复位,客户进程随后打印出连接被对端复位。

远端主机不可达

image-20231123155218953

  • 在发送主机最终放弃之前,一共发送了9个保活探查,每次发送间隔75秒,之后返回给应用一个没有到达主机的路由差错。
文章来源:https://blog.csdn.net/weixin_58783105/article/details/134986501
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。