Linux--TCP 网络传输控制协议简述

一、TCP协议介绍

TCP(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层协议。它是互联网协议套件中最重要的协议之一,用于在网络上可靠地传输数据。

面向连接:"面向连接"是一种网络通信的方式,其中通信的两个端点在进行数据传输之前需要建立一个连接。这个连接提供了一个可靠的、双向的通信通道,使得数据可以在两个端点之间按照顺序进行传输,且为一对一。

可靠的:tcp提供多种机制来确保确保数据的完整性、顺序性和可靠性,适应网络的变化,并提供流量控制和拥塞控制机制,以确保数据的可靠传输和接收

字节流(byte-oriented protocol):tcp将数据视为一连串的字节流,而不考虑消息的边界,数据被分割成称为 TCP 报文段(TCP segments)的较小单元进行传输,这些报文段包含了数据以及与 TCP 相关的控制信息,如序列号、确认应答等

 

1.1 TCP头部

  • 源端口号、目标端口:16位长,标识出远端和本地的端口号
  • 序号:SEQ,32位长,标识发送的数据包的顺序,防止数据包乱序
  • 确认号:32位长,接收方对发送方发送来的TCP报文段的响应,其值是对收到的报文序号加1,用于解决不丢包的问题
  • TCP头长:4位头长,标识tcp头部可以有多少个32bit,即多少个4字节,因为头长是4位,最大能表示15,所以TCP头部最大就是15*4等于60个字节,也就是说TCP包头最长可以有60个字节
  • URG:表示紧急指针是否有效
  • ACK:ACK位置1表明确认号是合法的,如果ACK为0,那么数据包不包含确认信息,确认字段被省略
  • PSH:提示接收端应用程序应该立即从TCP接收缓冲区中读走数据,为接收后续数据腾出空间
  • RST:表示要求对方重新建立连接,用于复位由于主机崩溃或其他原因而出现的错误的连接,还可以用于拒绝非法的数据报或拒绝连接请求
  • SYN:表示请求建立连接
  • FIN:表示通知对方要关闭连接了
  • 窗口大小:16位长,是一种流量控制的手段,这个窗口,指的是接收通告窗口,它告诉对端本端的TCP缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度
  • 校验和:16位长,由发送端填充,接收端根据该值,校验接收到的TCP报文段在传输过程中是否损坏,校验包括头部和数据部分,它是TCP可靠传输的一个重要保障
  • 紧急指针:16位长,是一个正的偏移量,它和序号相加表示最后一个紧急数据的下一个字节的序号,确切的说,它是紧急指针相对当前序号的偏移,是发送端向接收端发送紧急数据的办法
  • 可选项:TCP头部最长可以有60个字节,而前部分已知的字段占用了20个字节,所以选项最多可以有40个字节,包括最大TCP载荷,窗口比例、选择重复数据报等选项

 

位长(Length):表示TCP头部的长度,以字节为单位,32位即4字节

注:TCP数据包是没有IP地址的,只有端口,IP地址在ip头部里

 

1.2 TCP四元组

TCP四元组指的是TCP协议中唯一标识一个TCP连接的四个参数,它们分别是源IP地址、源端口号、目标IP地址和目标端口号。

在TCP连接建立时,客户端和服务器之间需要交换这四个参数,以确认彼此的身份和建立连接

 

二、三次握手,四次挥手及连接状态

2.1 三次握手

三次握手(Three-Way Handshake)是在TCP/IP网络中建立一个可靠的连接的过程。它用于确保客户端和服务器之间的通信能够正常进行。

  1. 第一次握手(SYN):

    • 源主机向目标主机发送一个带有SYN(同步)标志的TCP报文段。
    • 源主机选择一个初始序列号(ISN)并将其包含在报文段中。
    • 源主机将连接状态设置为"SYN_SENT",表示正在发起连接。
  2. 第二次握手(SYN-ACK):

    • 目标主机收到源主机发送的SYN报文段后,向源主机回复一个带有SYN/ACK(同步/确认)标志的TCP报文段。
    • 目标主机选择一个初始序列号(ISN)并将其包含在报文段中。
    • 目标主机确认源主机的SYN报文段,并将连接状态设置为"SYN_RECEIVED",表示收到连接请求。
  3. 第三次握手(ACK):

    • 源主机收到目标主机发送的SYN/ACK报文段后,向目标主机发送一个带有ACK(确认)标志的TCP报文段。此次握手可携带数据
    • 源主机确认目标主机的SYN/ACK报文段,并将连接状态设置为"ESTABLISHED",表示连接已建立。
    • 目标主机收到源主机发送的ACK报文段后,将连接状态也设置为"ESTABLISHED"。

 

当第一次握手的SYN报文丢失时,触发重传机制,客户端会重传SYN报文,重传的 SYN 报文的序列号都是一样的,重传次数由 tcp_syn_retries 决定,默认值是5,超过最大重传次数则断开此次连接

当第二次握手的SYN+ACK报文丢失时,客户端依旧会触发重传机制,重传SYN报文;而服务端由于收不到客户端的ACK确认报文,也会触发重传机制,重传SYN+ACK报文,重传次数由 tcp_synack_retries 决定,默认值是5

当第三次握手的ACK确认报文丢失时,服务端由于收不到客户端的ACK确认报文,会触发重传机制,重传SYN+ACK报文

ACK 报文是不会有重传的,当 ACK 丢失了,就由对方重传对应的报文,因为ACK只是用于确认已经接收到的数据段

 

2.1.1 TCP 半连接队列与全连接队列

在 TCP 连接过程中,存在两种队列:半连接队列(SYN 队列)和全连接队列(Accept 队列)。

 

  1. 半连接队列(SYN 队列)当服务器收到客户端的 SYN 报文(请求建立连接)时,服务器会将该连接信息存储在半连接队列中,等待后续的握手过程完成。这个队列也被称为 SYN 队列。半连接队列中保存着尚未完成三次握手的连接请求。在半连接队列中,每个连接请求会占用一些系统资源(如内存),因此半连接队列的长度是有限的。如果半连接队列已满,新的连接请求将被拒绝,客户端可能会收到连接超时或连接被拒绝的错误。
  2. 全连接队列(Accept 队列)当服务器完成三次握手,建立起 TCP 连接后,连接会从半连接队列移动到全连接队列,也被称为 Accept 队列。全连接队列存储着已经完成握手的连接,服务器准备接受客户端的请求。全连接队列的大小也是有限的,它表示服务器同时能够处理的连接数。如果全连接队列已满,新的连接请求将被拒绝,客户端可能会收到连接被拒绝的错误。

 

 

 

 

accept():accept()是一个系统调用函数,在 TCP 中,服务器在 LISTEN 状态下等待传入的连接请求。当有客户端发起连接请求时,服务器通过调用accept()来接受这个连接请求,并创建一个新的已连接套接字来处理该连接。

 

2.1.2 半连接队列

没有命令可以直接查看半连接队列的情况,但由于其特性,处于 SYN_RECV 状态的 TCP 连接,其实就是 TCP 半连接队列,所以可以过滤出SYN_RECV状态的连接

netstat -talnp | grep SYN_RECV

查看半连接队列溢出情况

netstat -s | grep "SYNs to LISTEN"

 

2.1.2.1 修改半连接队列最大值
#查看
cat /proc/sys/net/ipv4/tcp_max_syn_backlog

#临时修改
echo 655350 > /proc/sys/net/ipv4/tcp_max_syn_backlog

#永久修改
vim /etc/sysctl.conf
net.ipv4.tcp_max_syn_backlog = 655350
sudo sysctl -p

 

注:当半连接队列已满,且未开启tcp_syncookies功能(下节介绍),服务端将丢弃客户端的这个SYN包;当半连接队列未满,但全连接队列满了,且已经有在重传的SYN+ACK包,则也会丢弃当前客户端发来的SYN包

 

2.1.2.2 如何防范SYN攻击

先了解下SYN攻击:SYN 攻击(SYN Flooding Attack)是一种常见的网络攻击方式,利用了 TCP 协议中的漏洞,攻击者伪造大量的 SYN 请求并发送给目标服务器,但在完成握手的最后一步 ACK 确认之前,攻击者不再继续发送 ACK 请求。这样,服务器就会一直等待攻击者发送 ACK 确认,服务器的半连接队列很快被填满,而无法为其他合法的连接提供服务。

 

1、开启tcp_syncookies功能

当启用 tcp_syncookies 参数后,服务器可以使用 SYN Cookies 机制来处理 SYN 请求。具体来说,当SYN队列满时,服务器在收到 SYN 请求后,不再将其放入半连接队列,而是根据一定的算法生成一个 SYN Cookie,并将其作为 SYN-ACK 响应发送给客户端。客户端在收到 SYN Cookie 后,使用 Cookie 的值计算并发送 ACK 请求,用于验证身份和请求的有效性。服务器在接收到 ACK 请求后,验证 Cookie 并恢复连接状态。此时连接不进入Accept 队列,直接在SYN阶段确认连接。

开启/关闭tcp_syncookies

#1为开启,0为关闭
echo 1 > /proc/sys/net/ipv4/tcp_syncookies

#永久修改
vim /etc/sysctl.conf
net.ipv4.tcp_syncookies = 1
sudo sysctl -p

 

2、增大半连接队列

增大防范如2.1.2.1,注意,不能只单纯增大半连接队列的值,还要增大全连接队列的值。

 

3、减少 SYN+ACK 重传次数

由于SYN攻击者不会回复ACK包给服务端,故服务端会重传SYN+ACK包,这无疑加重了资源负载,用 tcp_synack_retries 参数来控制重传SYN+ACK包的次数(重传时间间隔是超时重传的时间间隔),也可有效防范SYN攻击,

#临时,默认值是5
echo 1 > /proc/sys/net/ipv4/tcp_synack_retries

#永久修改
vim /etc/sysctl.conf
net.ipv4.tcp_synack_retries= 1
sudo sysctl -p

 

2.1.3 TCP 全连接队列

查看全连接队列情况,使用ss命令。

注:ss 命令获取的 Recv-Q/Send-Q 在「LISTEN 状态」和「非 LISTEN 状态」所表达的含义是不同的

在LISTEN状态下:

  • Recv-Q表示当前全连接队列的大小,也就是当前已完成三次握手并等待服务端 accept() 的 TCP 连接
  • Send-Q表示当前全连接最大队列长度

在非LISTEN状态下:

  • Recv-Q表示已收到但未被应用进程读取的字节数(单位字节Bytes),即已经到达接收缓冲区但尚未被应用进程处理
  • Send-Q表示已发送但未收到确认(ACK)的字节数(tcp的确认应答机制:发送数据后,需要接收方回一个确认应答ACK给发送方,告知数据已收到,否则发送方将重传这个数据),当应用进程发送数据时,数据会被放置在发送缓冲区中,并逐渐被发送出去

接收缓冲区:用于存储从网络接收到的数据的内存区域。当数据到达网络接口时,操作系统内核将数据复制到接收缓冲区中,并等待应用程序读取。接收缓冲区的大小可以影响应用程序接收数据的能力和效率。

发送缓冲区:用于存储应用程序要发送的数据。当应用程序调用发送操作将数据发送到套接字时,数据会被复制到发送缓冲区中,并由操作系统内核负责将数据从发送缓冲区发送到网络。发送缓冲区的大小可以影响应用程序发送数据的能力和效率。

 

 netstat -s | grep overflowed  查看全连接队列溢出情况

 

2.1.3.1 tcp_abort_on_overflow

tcp_abort_on_overflow 是一个 TCP 协议栈的参数,用于控制在接收队列溢出时的行为。当接收队列溢出时,即接收缓冲区无法容纳更多的数据时,该参数决定是继续接收数据还是中止当前连接。

当为0时(默认值),表示在接收队列溢出时继续接收数据。这意味着当接收队列已满时,新的数据包将继续被接收,当接收缓冲区无法容纳更多的数据时,接收方会选择丢弃新的数据包,或者降低接收窗口大小;此时发送方的连接状态还是ESTABLISHED,但由于收不到确认应答ACK,所以会重传数据,直至收到应答ACK,或者超时断连

当为1时,表示在接收队列溢出时中止当前连接。这意味着当接收队列已满时,新的数据包将被丢弃,并且相应的连接将被中止。接收方会发送一个RST包给发送方,发送方收到这个包后,中止发送进程。

 

修改该参数:

#临时修改
sysctl -w net.ipv4.tcp_abort_on_overflow=0
echo 0 > /proc/sys/net/ipv4/tcp_abort_on_overflow

#永久修改
vim /etc/sysctl.conf
net.ipv4.tcp_abort_on_overflow = 0
sysctl -p

 

2.1.3.2 修改全连接队列最大值

全连接队列的大小受两个参数的影响,somaxconn 和 backlog

  1. somaxconn 参数:somaxconn 是操作系统级别的参数,用于设置系统范围内全连接队列的最大长度。它控制着所有监听套接字共享的全连接队列的容量。这个参数的值通常是操作系统默认设置的,它限制了整个系统能够同时处理的全连接数。

  2. backlog 参数:backlog 是 listen() 函数中的参数,用于设置特定监听套接字的已完成连接队列的最大长度。这个参数控制着每个单独监听套接字的已完成连接队列的容量。它是在应用程序级别上用于调整特定套接字的连接请求排队的能力。

修改somaxconn

#临时
echo 5000 > /proc/sys/net/core/somaxconn

#永久
vim /etc/sysctl.conf
net.core.somaxconn = 5000
sudo sysctl -p

 

修改backlog

这每个应用程序都不一样,需要在程序代码里设置,如ngxin(默认值511):

vim nginx.conf
...
server {
    listen 80 default backlog=5000;
}
...

 

2.2 四次挥手

TCP四次挥手是指在TCP连接关闭时,通信双方通过交换一系列的报文段来协商关闭连接的过程。连接双方都可发起四次挥手,以下假设由客户端发起关闭请求:

  1. 第一次挥手(FIN):
    客户端发送一个带有FIN标志的报文段,表示自己已经没有数据要发送了,请求关闭连接。客户端进入FIN_WAIT_1状态,等待服务端的确认。

  2. 第二次挥手(ACK):
    服务端收到FIN报文后,会发送一个确认报文(ACK)给客户端,表示已经接收到关闭请求。服务端进入CLOSE_WAIT状态,此时出于半关闭状态,仍可以发送数据给客户端。

  3. 客户端在接收到服务端的ACK确认报文后,进入FIN_WAIT_2状态,等待服务端发送最后的确认消息,以完成连接的关闭过程。
  4. 第三次挥手(FIN):
    当服务端处理完数据后,准备关闭时,它会发送一个带有FIN标志的报文段给客户端,表示自己也没有数据要发送了。服务端发送FIN报文后,进入LAST_ACK状态,等待最后的确认。

  5. 第四次挥手(ACK):
    客户端接收到服务端的FIN报文后,会发送一个确认报文(ACK)给服务端,并进入TIME_WAIT状态,等待一段时间(等待2倍的最长报文段生存时间),以确保网络中的延迟报文段都被丢弃,完成等待后,连接正式关闭。

  6. 服务端在接收到最后的ACK报文后,就直接关闭了连接,处于CLOSED状态。

 

当第一次挥手的FIN报文丢失时,客户端会触发超时重传机制,重传 FIN 报文,重发次数由 tcp_orphan_retries 控制,默认值是8,达到最大重传次数后,断开连接

当第二次挥手的ACK报文丢失时,还是由客户端触发超时重传机制,重传 FIN 报文

当第三次挥手的FIN报文丢失时,服务端就会重发 FIN 报文,重发次数由 tcp_orphan_retries 控制

当第四次挥手的ACK报文丢失时,由服务端重发 FIN 报文

 

2.2.1 最大报文段生存时间(MSL)

最大报文段生存时间(Maximum Segment Lifetime,MSL)是指在TCP协议中,报文段在网络中允许存活的最长时间。MSL是一个时间间隔,通常被定义为30秒或60秒。

在TCP连接关闭时,客户端和服务器都会进入TIME_WAIT状态,等待2倍MSL的时间,以确保之前的报文段在网络中被丢弃完全,并且不会和后续连接的报文段产生冲突。

 

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

TIME_WAIT 状态的等待时间是2倍MSL的一个通用约定,用于确保之前的连接彻底关闭,避免可能出现的报文段冲突和连接混乱。

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

 

2.2.2 为什么需要 TIME_WAIT 状态

1.保证连接双方都正确的关闭

TIME_WAIT状态允许TCP连接在完全关闭之前等待一段时间。在此期间,连接的双方仍有机会接收到最后一次传输的报文段,以确保连接的可靠关闭。TIME_WAIT状态的持续时间是为了处理网络延迟和乱序传输等情况,以便接收到可能被延迟的报文段或重复的报文段,并进行适当的处理。

2.防止历史连接中的数据,被后面的相同四元组的连接错误接收

TCP连接的四元组(源IP地址、目标IP地址、源端口号、目标端口号)在网络中应该是唯一的。TIME_WAIT状态的持续时间确保之前的连接完全关闭,并释放了与该连接相关的资源,包括套接字和端口号。这样可以避免之前的连接与后续的连接混淆,确保正确的连接建立和通信。

 

2.2.3 修改time_wait时间

临时修改:

#查看当前机器的timeout时间
cat /proc/sys/net/ipv4/tcp_fin_timeout

#修改,单位:秒
echo 60 > /proc/sys/net/ipv4/tcp_fin_timeout

 

永久修改:

#修改内核参数,或在 /etc/sysctl.d/ 目录下创建一个新的配置文件,如:/etc/sysctl.d/time_wait.con
vim /etc/sysctl.conf
net.ipv4.tcp_fin_timeout = 60

#执行以下命令使新的设置生效
sysctl -p

 

2.2.4 服务器上time_wait过多的影响及优化

当服务器上time_wait状态过多时,会造成资源浪费,连接延迟和性能下降,因为time_wait状态的连接会占用机器的端口、内存、cpu等资源,而且阻塞新的连接请求;当大量的 TIME_WAIT 状态连接存在时,还可能会导致网络堵塞和拥塞,影响网络通信的流畅性和吞吐量

 

如何判断机器上的time_wait状态是否过多

这个没有一个标准的阈值,它取决于服务器的硬件配置、网络环境和应用程序需求,通常来说,当 TIME_WAIT 状态的数量超过服务器资源所能承受的范围时,就可以认为是过多的。

以下是一些常见的指导原则和建议:

  1. 观察服务器的性能表现:当 TIME_WAIT 状态过多时,服务器的性能可能会受到影响。如果服务器在处理连接时出现延迟、负载增加或资源不足等问题,可能是 TIME_WAIT 过多所导致的。

  2. 参考经验值:根据一些经验值,通常认为 TIME_WAIT 状态的数量超过几千到一万个可能是过多的。然而,这仅仅是一个参考值,实际情况可能因系统配置、网络环境和应用程序需求而有所不同。

  3. 评估服务器资源和需求:考虑服务器的硬件资源(如处理器、内存、网络带宽)以及应用程序的性能需求。如果 TIME_WAIT 状态过多导致服务器资源不足或无法满足应用程序的连接需求,就需要采取措施来减少 TIME_WAIT 状态的数量。

  4. 比较和分析数据:收集历史数据并进行比较和分析,观察 TIME_WAIT 状态的数量是否呈现明显的增长趋势。如果数量不断增加,可能需要进一步调查并采取相应的措施。

 

time_wait状态过多的优化方法
  1. 缩短time_wait的时间,使其更快的消散,参考本文2.2.3
  2. 开启连接复用,使其可以为新连接所使用:在 /etc/sysctl.conf 里修改内核参数 net.ipv4.tcp_tw_reuse = 1 ,1为开启,0为关闭
  3. 开启time_wait状态连接快速回收:在 /etc/sysctl.conf 里修改内核参数 net.ipv4.tcp_tw_recycle = 1 ,1为开启
  4. 控制time_wait的最大连接数:由 tcp_max_tw_buckets 参数决定,默认值为18000,超过这个值,新的连接将无法进入 TIME_WAIT 状态,可能会被直接丢弃
  5. 服务端不要主动断开连接,让客户端去断开,由客户端去承受time_wait
  6. 使用连接池,避免频繁的连接建立和关闭操作
  7. 在高负载情况下,可以考虑使用负载均衡器来分发连接到多个后端服务器

 

连接复用:允许新的连接使用处于 TIME-WAIT 状态的端口。换句话说,如果有新的连接请求到达,且该请求的四元组(源 IP、源端口、目标 IP、目标端口)与处于 TIME-WAIT 状态的连接的四元组相同,内核可以重用该端口,而不需要等待 TIME-WAIT 状态的结束。

快速回收:内核会在接收到客户端发起的新连接请求时,检查收到的 SYN 包中的时间戳选项(Timestamp Option)字段。如果时间戳选项合法且在一定范围内,内核会认为这是一个新的连接,即使该连接的四元组(源 IP、源端口、目标 IP、目标端口)与之前的 TIME-WAIT 状态冲突。然后,内核会忽略冲突的 TIME-WAIT 状态,并将新的连接建立起来。这种快速回收机制的思想是,通过利用时间戳选项来区分不同的连接,并假设位于同一客户端主机的连接在一个较短的时间段内不会重叠。

 

2.3 四次挥手变为三次挥手

在四次挥手中,服务端会发送ACK报文给客户端,自身进入CLOSE_WAIT状态,当没有数据需要传输后,再发送ACK报文,等待最后关闭

那么,如果客户端第一次发送FIN报文请求关闭连接时,此时服务端已没有数据需要传输,是否可以把ACK+FIN报文合并,一起发送给客户端,从而将四次挥手变为三次挥手呢?

答案是可以的,但这需要服务端开启TCP延迟确认机制

 

2.3.1 TCP 延迟确认(Delayed ACK)

TCP 延迟确认(Delayed ACK)是一种优化技术,用于减少在 TCP 连接中发送的确认报文(ACK)的数量,从而减少网络上的报文传输量和延迟。

在标准的 TCP 协议中,接收方通常会立即发送 ACK 报文以确认接收到的数据。这意味着每次接收方收到数据时都会发送一个 ACK 报文,这可能导致在某些情况下发送大量的小型 ACK 报文,增加网络的负载。

通过启用延迟确认机制,接收方可以将接收到的多个数据包的确认合并为一个 ACK 报文,从而减少发送的 ACK 报文数量。具体来说,接收方会等待一小段时间(通常是几毫秒)或者等待接收到一定数量的数据后,才发送 ACK 报文作为确认

延迟确认的优点是减少网络上的报文传输量和网络延迟。通过合并确认,可以减少发送的 ACK 报文数量,从而降低网络负载和传输延迟。在某些情况下,特别是在高延迟的网络环境中,启用延迟确认可以提高性能和吞吐量。

然而,延迟确认也可能带来一些副作用。例如,在某些实时应用程序中,延迟确认可能导致一定的传输延迟,因为发送方需要等待确认才能继续发送数据。此外,在某些特定的网络拓扑和负载情况下,延迟确认可能导致网络拥塞或传输丢失的问题。

 

设置延迟时间

#临时,单位毫秒
sysctl -w net.ipv4.tcp_delack_min=100

#永久
vim /etc/sysctl.conf
net.ipv4.tcp_delack_min=100
sysctl -p

修改"延迟确认阈值"(Delayed ACK threshold):这个值通常是固定的,通常是两个数据包或等于 MSS 的整数倍,在此不再修改

 

注:当接收方收到一个带有 PSH(Push)标志的数据包时,它通常会立即响应,而不等待延迟时间,PSH 标志用于指示接收方应该立即将数据交给应用程序,而不需要等待缓冲区中的其他数据或延迟确认。

 

2.4 补充

2.4.1 net.ipv4.ip_local_port_range

net.ipv4.ip_local_port_range 是一个 Linux 内核参数,用于定义本地端口的可用范围。默认情况下,该参数的值通常设置为 32768-60999。

该参数的作用是控制在客户端和服务器端建立连接时可用的本地端口范围。当应用程序需要建立一个新的连接时,会从这个指定的范围中选择一个可用的本地端口。

查看:
sysctl net.ipv4.ip_local_port_range

cat /proc/sys/net/ipv4/ip_local_port_range
 
临时修改:
echo "1024 65535" > /proc/sys/net/ipv4/ip_local_port_range

 

永久修改:

vim /etc/sysctl.conf
net.ipv4.ip_local_port_range = 1024 65535

sysctl
-p

 

三、保活机制与长短链接

3.1 tcp保活机制:TCP keepalive

TCP保活机制也叫TCP keepalive,是一种用于保持TCP连接活跃的机制。它旨在防止长时间处于空闲状态的TCP连接被路由器、防火墙或其他网络设备断开。

TCP keepalive通过定期向对端发送特殊的空闲探测报文(keepalive探测报文)来检测连接的活跃性。如果对端在一定时间内没有响应,就可以判断连接已经失效,从而可以采取相应的措施,如进行连接重连或关闭连接。

当一个 TCP 连接建立之后,开启 TCP keepalive 的一端会启动一个计时器,当这个计时器数值到达 0 之后(也就是经过 tcp_keepalive_time 时间后,当然每次传输数据都将重置计时器数值),会发送一个保活探测报文。探测报文不包含任何数据,或者包含一个无意义的字节,当保活探测报文发送后,如果对端没有响应,发送方将继续重复发送保活探测报文,直到达到保活探测次数的上限。如果达到上限仍未收到对端的响应,发送方将认为连接已经失效,并终止连接。

 

常见的TCP保活机制包括以下几个关键参数:

  1. Keep-Alive定时器(net.ipv4.tcp_keepalive_time):用于设置保活探测报文的发送间隔。通常,默认的Keep-Alive定时器间隔为7200s(2小时)。这意味着在两小时内没有数据传输时,发送方将开始发送保活探测报文。

  2. 保活探测时间间隔(net.ipv4.tcp_keepalive_intvl):定义了在未收到对端响应的情况下,连续发送保活探测报文的间隔时间,默认值通常为75s。
  3. 保活探测次数(net.ipv4.tcp_keepalive_probes):指定了在没有收到对端响应的情况下,发送方愿意发送的保活探测报文的最大次数。通常,默认的保活探测次数为9次。如果连续发送指定次数的保活探测报文后仍未收到对端的响应,连接将被判定为失效。

修改这些内核参数,可直接修改 /proc/sys/net/ipv4/ 下对应的文件,或者修改 /etc/sysctl.conf 里的参数值,进行永久修改,参考前文相关参数的修改方法

 

开启/关闭tcp keeplive

linux系统:修改相应内核参数的值即可,如当 net.ipv4.tcp_keepalive_time = 0 时,即表示关闭,其他正值表示开启

编程语言:不同的编程语言有不同的方式,这里介绍go语言的开启/关闭方法

package main

import (
    "net"
    "syscall"
    "time"
)

func enableKeepalive(conn net.Conn) error {
    tcpConn, ok := conn.(*net.TCPConn)
    if !ok {
        return nil // 不支持TCP Keepalive
    }

    file, err := tcpConn.File()
    if err != nil {
        return err
    }
    defer file.Close()

    fd := int(file.Fd())

    // 开启TCP Keepalive
    err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 1)
    if err != nil {
        return err
    }

    // 设置保活时间间隔
    err = syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, 60) // 60秒
    if err != nil {
        return err
    }

    // 设置保活探测次数
    err = syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPCNT, 3)
    if err != nil {
        return err
    }

    // 设置保活探测间隔
    err = syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, 10) // 10秒
    if err != nil {
        return err
    }

    return nil
}

func disableKeepalive(conn net.Conn) error {
    tcpConn, ok := conn.(*net.TCPConn)
    if !ok {
        return nil // 不支持TCP Keepalive
    }

    file, err := tcpConn.File()
    if err != nil {
        return err
    }
    defer file.Close()

    fd := int(file.Fd())

    // 关闭TCP Keepalive
    err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 0)
    if err != nil {
        return err
    }

    return nil
}

func main() {
    // 创建TCP连接
    conn, err := net.Dial("tcp", "example.com:80")
    if err != nil {
        // 错误处理
        return
    }

    // 开启TCP Keepalive
    err = enableKeepalive(conn)
    if err != nil {
        // 错误处理
        return
    }

    // 执行其他操作...

    // 关闭TCP Keepalive
    err = disableKeepalive(conn)
    if err != nil {
        // 错误处理
        return
    }

    // 关闭连接
    conn.Close()
}

 

3.2 HTTP长连接:HTTP Keep-Alive

首先需要明确下,http的keep-alive与tcp的keepalive是不一样的,它们是两个不同的概念

TCP的Keepalive是一种TCP协议的机制,用于保持TCP连接的活跃状态。当TCP连接长时间处于空闲状态时,Keepalive机制会发送探测报文以确认连接是否仍然有效,以防止连接被意外终止。这是TCP协议层面的机制,与具体的应用程序无关。

HTTP的Keep-Alive是一种HTTP协议的机制,用于在单个TCP连接上保持多个HTTP请求和响应的持久性,也被称为HTTP 长连接。

在HTTP的早期版本中,每次请求都需要建立一个新的TCP连接,这会产生较大的开销。为了减少连接建立的开销,HTTP的Keep-Alive机制允许多个HTTP请求和响应通过同一个TCP连接进行传输,从而提高性能。

在HTTP/1.0中,长连接(Keep-Alive)是可选的,并且需要在请求头中显式指定Connection: keep-alive。而在HTTP/1.1中,默认开启了长连接,除非在请求头中显式指定Connection: close来关闭连接。

 

示例(go):

package main

import (
    "fmt"
    "net/http"
    "time"
)

func main() {
    // 创建一个自定义的 HTTP 客户端
    client := &http.Client{
        Transport: &http.Transport{
            // 开启或关闭 HTTP 长连接
            DisableKeepAlives: true, // 设置为 true 关闭长连接,设置为 false 开启长连接
        },
        Timeout: 10 * time.Second, // 设置超时时间为 10 秒
    }

    // 发送 GET 请求
    resp, err := client.Get("http://example.com")
    if err != nil {
        fmt.Println("Error sending request:", err)
        return
    }
    defer resp.Body.Close()

    // 处理响应
    // ...
}

 

3.2.1 http连接与tcp连接的关系

HTTP 连接和 TCP 连接是相关但不同的概念,它们之间存在一种层级关系。

HTTP(超文本传输协议)是一种应用层协议,主要用于在客户端和服务器之间传输超文本资源。HTTP 协议通过底层的传输协议进行数据的传输,而最常用的底层传输协议是 TCP(传输控制协议)。

TCP 是一种可靠的传输协议,它提供了面向连接的、可靠的字节流传输。当使用 HTTP 协议时,通常会在 TCP 连接上进行传输

  1. 建立TCP连接:在进行HTTP通信之前,客户端和服务器需要先建立一个TCP连接。TCP连接的建立通常使用三次握手过程,确保双方的通信能够正常进行。

  2. 发送HTTP请求:一旦TCP连接建立成功,客户端就可以发送HTTP请求。HTTP请求包括请求行(包含请求方法、目标URL和协议版本)、请求头部(包含各种请求相关的头部字段)和请求体(对于一些请求,如POST请求,可能包含请求体数据)。

  3. 服务器处理请求并发送HTTP响应:服务器收到客户端的HTTP请求后,会解析请求内容并根据请求的要求进行相应的处理。处理完成后,服务器会生成HTTP响应,包括响应状态行(包含响应的状态码和协议版本)、响应头部(包含各种响应相关的头部字段)和响应体(包含实际的响应数据)。

  4. 接收HTTP响应:TCP连接上的数据流会将服务器发送的HTTP响应传递给客户端。客户端接收到响应后,会对响应进行解析和处理。

  5. 关闭TCP连接:一旦HTTP响应传输完成,客户端和服务器之间的TCP连接可以被关闭。在HTTP/1.1中,默认情况下会启用Keep-Alive机制,即保持TCP连接的持久性,以便在同一个连接上进行多次请求和响应。

HTTP利用TCP传输数据的整个过程中,TCP协议负责提供可靠的、面向连接的通信通道,确保数据能够按照顺序传输、不丢失和不损坏。HTTP协议则负责定义了请求和响应的格式、语义和语法,规定了客户端和服务器之间的交互行为。HTTP在TCP连接的基础上,通过发送HTTP请求和接收HTTP响应来进行数据的传输和通信。

 

3.3 长连接与短连接

长连接和短连接是指在网络通信中,源主机和目标主机之间建立的连接的持续时间不同。

  1. 长连接:

    • 长连接是指在建立连接后,源主机和目标主机保持连接的状态较长时间,可以进行多次的数据传输或通信。
    • 长连接适用于需要频繁通信或实时交互的应用场景,如聊天应用、实时数据传输等。
    • 长连接可以减少建立和断开连接的开销,提高通信效率。
    • 在长连接中,源主机和目标主机之间可以保持会话状态,避免每次请求都需要重新认证或重新建立连接。
  2. 短连接:

    • 短连接是指在每次通信完成后,源主机和目标主机立即关闭连接。
    • 短连接适用于单次请求-响应的场景,如网页浏览、文件下载等。
    • 短连接在每次通信后立即释放资源,可以节省系统资源并提高并发性能。
    • 在短连接中,每次请求都需要重新建立连接和进行握手,会增加一定的开销和延迟。

选择使用长连接还是短连接取决于具体的应用需求和场景:

  • 长连接适用于需要频繁通信、实时交互或保持会话状态的应用。它可以减少连接建立和断开的开销,提高通信效率。
  • 短连接适用于单次请求-响应的场景,特别是在请求量较大且并发性能较重要的情况下。它可以快速释放资源,提高系统的并发处理能力。

 

设置长连接或短连接通常是通过在应用程序或网络协议中进行配置来实现的。具体的设置方法取决于所使用的编程语言、框架或网络库。

下面是一些常见的设置方法示例:

  1. 长连接设置:

    • 对于基于TCP的应用程序,可以通过设置TCP套接字的选项来实现长连接。例如,在使用Socket编程时,可以将套接字的keep-alive选项设置为true,以保持连接的活跃状态。
    • 对于Web应用程序,可以使用HTTP的持久连接(HTTP Keep-Alive)来实现长连接。在HTTP请求头中添加"Connection: keep-alive"字段,告知服务器保持连接的状态。
  2. 短连接设置:

    • 对于基于TCP的应用程序,短连接是默认行为,不需要特别设置。每次请求完成后,关闭套接字即可。
    • 对于Web应用程序,可以在HTTP请求头中添加"Connection: close"字段,告知服务器在响应完成后关闭连接。

需要注意的是,设置长连接或短连接时,还需考虑以下因素:

  • 服务器配置:确保服务器的配置允许长连接或短连接,并具备足够的资源来处理连接的维护或关闭。
  • 网络环境:对于跨网络的通信,如互联网,需要确保网络设备或防火墙不会主动关闭长连接或短连接。
  • 应用程序设计:根据应用需求和具体场景,合理选择长连接或短连接,避免资源浪费或连接过多造成的性能问题。

 

四、TCP重传

TCP重传是指在网络通信中,当发送方发送的TCP报文段未能及时收到确认(ACK)时,发送方会重新发送该报文段,以确保数据的可靠传输。TCP重传的目的是通过在丢失或损坏的报文段未能到达接收方时进行补发,保证数据的完整性和可靠性。

下面是TCP重传的一般流程:

  1. 发送方发送TCP报文段。
  2. 发送方设置一个定时器,等待一段时间(称为超时时间)来接收接收方的确认。
  3. 如果在超时时间内未收到确认,发送方认为该报文段已丢失或损坏。
  4. 发送方重新发送丢失的报文段。
  5. 接收方收到重传的报文段后,发送确认给发送方,以告知发送方该报文段已成功接收。
  6. 发送方根据收到的确认更新重传计时器,等待下一个报文段的确认或超时。

TCP重传的实施是通过序列号(Sequence Number)和确认号(Acknowledgment Number)来实现的。发送方在每个TCP报文段中都会附带一个序列号,用于标识数据的顺序,接收方在确认报文段中会包含一个确认号,表示已成功接收的最后一个报文段的序列号。

通过序列号和确认号的比较,发送方可以确定接收方是否已成功接收报文段,并进行相应的重传操作。如果发送方未收到确认,或者收到的确认号小于等于已发送的报文段的序列号,就会触发重传。

TCP重传的机制是TCP协议中的一个重要特性,它能够提供可靠的数据传输。通过重传,TCP可以处理报文段的丢失、延迟、重排序等问题,确保数据的完整性和可靠性。

 

4.1 超时重传

TCP超时重传是TCP协议中一种基于超时的重传机制,用于保证数据的可靠传输。当发送方发送一个TCP报文段后,如果在一定时间内未收到接收方对该报文段的确认,发送方会认为该报文段可能丢失或损坏,因此会触发超时重传操作,重新发送该报文段

 

RTT(Round Trip Time):指一个数据包从发送方发送到接收方并返回的往返时间,它表示了数据包在网络中传输的延迟。RTT包括了数据包从发送方发送出去后经过网络传输的时间以及接收方将确认消息发送回发送方的时间。

RTO(Retransmission Timeout):指重传超时时间,也可以称为重传定时器,它是TCP协议中用于控制重传时间的参数。RTO的值表示在发送方发送一个数据包后,等待接收方的确认的最长时间。如果在RTO时间内未收到确认,发送方会认为数据包丢失或损坏,触发重传机制,重新发送数据包。

RTO的计算通常基于往返时间(RTT)和时延变化(Variation in Delay,Var)。TCP协议中采用了一些算法来动态地调整RTO的值,以适应网络的变化和拥塞状况。常见的算法包括指数加权移动平均(Exponential Weighted Moving Average,EWMA)和加权平均偏差(Weighted Average Deviation,WAD)等。

RTO的合理设置对于TCP的性能和可靠性非常重要。如果RTO设置得过长,会导致不必要的等待和延迟;而设置得过短,可能会导致过多的冗余重传。因此,TCP协议通常会根据网络的条件和拥塞状况来自适应地调整RTO的值,以在可靠性和性能之间取得平衡。

 

手动调整

设置超时时间:使用 net.ipv4.tcp_retrans_time 参数,默认为300,单位毫秒

#临时修改
echo 300 > /proc/sys/net/ipv4/tcp_retrans_time

sysctl -w net.ipv4.tcp_retrans_time=300

#永久修改
vim /etc/sysctl.conf
net.ipv4.tcp_retrans_time = 300
sysctl -p

 

设置重传次数:使用 net.ipv4.tcp_retries2 参数,表示TCP重传次数的最大值,当达到该次数后,如果仍未收到对应报文段的确认,TCP会放弃该连接,默认值为15。

#临时修改
echo 15 > /proc/sys/net/ipv4/tcp_retries2

sysctl -w net.ipv4.tcp_retries2=15

#永久修改
vim /etc/sysctl.conf
net.ipv4.tcp_retries2 = 15

 

超时间隔加倍

在TCP超时重传中,超时时间间隔加倍是一种常见的拥塞控制机制,旨在逐渐增加超时重传的等待时间,以应对网络拥塞和丢包的情况。当TCP发送方发现一个报文段丢失并进行超时重传时,如果连续多个重传仍未收到确认,就会将超时时间间隔加倍。

超时时间间隔加倍机制可以使TCP在网络拥塞时更加谨慎地进行重传,以避免过多的重传和拥塞加剧。这种机制使得TCP能够根据网络状况逐渐调整重传等待时间,以提供更好的性能和拥塞控制。

 

4.2 快速重传

TCP快速重传(Fast Retransmit)是一种TCP拥塞控制算法,旨在加快丢包的检测和恢复。当TCP发送方发现一个丢失的报文段时,根据传统的超时重传算法,需要等待超时时间后才会重传该报文段。而快速重传算法则允许TCP发送方在接收到连续重复的确认(ACK)时,快速重传丢失的报文段,而无需等待超时。

  1. TCP发送方发送一个报文段,并启动一个定时器以跟踪该报文段是否被确认。

  2. TCP接收方收到报文段后,如果发现有丢失的报文段,则会发出重复的确认,指示需要重传的报文段。

  3. TCP发送方接收到连续的重复确认时,可以推断出对应报文段已经丢失。在接收到一定数量(通常是3个)的连续重复确认时,发送方不再等待定时器超时,而是立即进行快速重传。

  4. 快速重传后,发送方会重传丢失的报文段,并重新启动定时器。

通过快速重传算法,TCP发送方能够更快地检测和恢复丢失的报文段,从而减少等待超时的延迟。这有助于提高TCP的性能和拥塞控制机制的响应能力

需要注意的是,快速重传算法基于连续的重复确认,因此在网络存在乱序或冗余的情况下,可能触发不必要的快速重传。因此,快速重传算法通常与其他拥塞控制算法和丢包恢复机制(如快速恢复)配合使用,以提供更可靠和高效的TCP传输。

 

开启/关闭

#临时,1为开启,0为关闭
echo 1 > /proc/sys/net/ipv4/tcp_fastretrans

#永久
vim /etc/sysctl.conf
net.ipv4.tcp_fastretrans = 1
sysctl -p

 

4.3 SCAK

快速重传遗留下一个问题,就是重传的时候是重传连续重复确认的那一个包,还是重传自那一个包后所有的包(因为发送方无法确认有多少包发送失败),对此衍生出了一种TCP拓展机制:SCAK。

SACK(Selective Acknowledgment,选择性确认)是一种TCP拓展机制,用于改进丢包恢复的效率。传统的TCP只能使用累积确认(Cumulative Acknowledgment)来确认已成功接收的数据,而无法指示哪些具体的报文段丢失。而SACK允许TCP接收方明确指示已成功接收的连续和不连续的报文段,从而提供更精确的丢包信息。

  1. TCP接收方收到一个有序的、非连续的报文段序列。

  2. TCP接收方生成一个SACK选项,其中包含了已成功接收的连续和不连续的报文段的范围

  3. TCP接收方将SACK选项添加到对应的确认报文段中,并发送给TCP发送方。

  4. TCP发送方接收到带有SACK选项的确认报文段后,可以知道哪些报文段已成功到达接收方,哪些报文段丢失

  5. TCP发送方可以根据SACK选项中提供的信息,有选择性地重传丢失的报文段,而无需等待超时。

此方法需要使用双方都支持SACK功能,通过 net.ipv4.tcp_sack 参数可控制。

vim /etc/sysctl.conf    
net.ipv4.tcp_sack = 1    #1为开启,0为禁用
sysctl -p    #重新加载配置文件使生效

 

使用SACK方法可以提高TCP的丢包恢复效率,减少不必要的重传。它允许TCP发送方根据接收方提供的明确信息,只重传丢失的部分,而不是重传整个连续的数据段。

 

4.4 D-SACK

SACK在实际使用过程中,因网络丢包,报文段乱序到达或TCP协议栈中的错误等原因,会出现重复的确认范围或区间的问题,当发送方收到带有重复SACK的确认报文段时,它可能会将其中的重复确认范围误解为新的丢失报文段,从而触发不必要的重传操作,导致网络资源的浪费和性能下降,因此又扩展了D-SACK机制。

D-SACK(Duplicate Selective Acknowledgment,重复选择性确认):是TCP中的一种拓展选项,用于更准确地指示接收方已成功接收的报文段范围,特别是在存在重复报文段的情况下

D-SACK选项允许TCP接收方向发送方提供更详细的信息,以指示接收方已收到的重复报文段。当接收方收到重复的报文段时,它可以使用D-SACK选项来指示发送方已成功接收的报文段范围,以避免发送方错误地重传这些重复的报文段。

D-SACK选项通常包含以下信息:

  1. 重复报文段的起始和结束序列号:指示接收方收到的重复报文段的序列号范围。

  2. 原始报文段的起始和结束序列号:指示接收方之前成功接收的原始报文段的序列号范围。

发送方收到带有D-SACK选项的确认报文段后,可以根据D-SACK提供的信息,避免不必要的重传。它可以识别出重复报文段和已成功接收的报文段,并只重传确实丢失的报文段。

此方法需要使用双方都支持D-SACK功能,通过 net.ipv4.tcp_dsack 可控制

vim /etc/sysctl.conf
net.ipv4.tcp_dsack = 1
sysctl -p

命令行启用

sysctl -w net.ipv4.tcp_dsack=1

 

五、滑动窗口

5.1 背景

在tcp连接中,发送方传输数据到接收方后,接收方需要发送一个确认包(ACK)给发送方

TCP使用确认机制来确保可靠的数据传输。当接收方成功接收到数据后,它会发送一个带有确认序号(ACK number)的ACK包给发送方,以告知发送方已经成功接收到数据。

确认包的目的是让发送方知道它所发送的数据已经被接收方正确接收,这样发送方可以继续发送下一批数据。如果发送方在超时时间内没有收到确认包,它将重传之前发送的数据段,以确保数据的可靠性。

在TCP中,确认包通常会携带一个确认序号,表示接收方期望接收的下一个数据段的序号。这个确认序号是上一次成功接收到的数据段的序号加上数据段的长度。

 

但这样的传输方式有一个缺点:包的往返时间越长,通信性能就越低。因此,tcp引入了窗口的概念。

 

5.2 窗口

TCP窗口(Window)是TCP协议中的一个重要概念,用于控制数据传输的流量和可靠性

TCP窗口可以看作是发送方和接收方之间的一个缓冲区。发送方使用窗口大小来确定可以连续发送的数据段数量,而接收方使用窗口大小来指示发送方可以发送的数据量。这样,发送方在发送第一个数据包后,不需要等待确认应答,而是连续发送第二,第三个数据包。

TCP窗口有两种类型:

  1. 发送窗口(Send Window):发送方维护的窗口,表示发送方能够发送的数据段的范围。发送方根据接收方通知的窗口大小,确定可以连续发送的数据段数量。发送方将数据段发送到网络,并等待接收方的确认。

  2. 接收窗口(Receive Window):接收方维护的窗口,表示接收方能够接收的数据段的范围。接收方通过发送确认包(ACK)来告知发送方自己的窗口大小。发送方根据接收方的窗口大小来控制发送的数据量,以避免发送过多的数据导致接收方无法及时处理。

TCP窗口的大小是动态调整的,取决于接收方的可用缓冲区大小和当前网络环境的拥塞情况。接收方根据自身的处理能力和可用内存空间来调整窗口大小,通过确认包通知发送方。

TCP窗口是TCP协议中的一个参数,通过TCP头部中的"Window"字段来表示的。该字段在TCP头部的第 14-15 个字节位置上,使用 16 位来表示窗口大小,取值范围是 0 到 65535,以字节为单位。

 

如下图,即使发送方没有接收到ack101,ack201的确认应答报文,也没关系,只要收到了ack301的应答报文,即表示前面的数据都被接收到了,发送方就不用进行重传,这种模式为累计确认,或累计应答。

 

5.3 滑动窗口

TCP滑动窗口(Sliding Window)是一种流量控制和拥塞控制机制,用于在TCP连接中控制数据的传输速率和可靠性

滑动窗口机制允许发送方在一次发送多个数据段(segment)而不需要等待每个数据段的确认。发送方维护一个滑动窗口的概念,表示可以连续发送的数据段的范围。接收方通过确认已接收的数据段的方式,通知发送方滑动窗口的位置。

滑动窗口的大小由接收方动态调整,取决于接收方的可用缓冲区大小和当前网络环境的拥塞情况。发送方根据接收方通知的滑动窗口大小,控制发送的数据段数量,以避免发送过多的数据导致网络拥塞。

滑动窗口机制有以下特点:

  1. 可变大小:滑动窗口的大小可以根据网络条件和接收方的处理能力动态调整。

  2. 流量控制:接收方通过调整滑动窗口的大小,控制发送方的发送速率,以避免接收方无法及时处理大量的数据。

  3. 自适应拥塞控制:滑动窗口机制与TCP的拥塞控制相结合,通过调整滑动窗口的大小来响应网络的拥塞情况。

  4. 确认机制:接收方通过发送确认(ACK)来告知发送方已成功接收数据段,发送方根据接收到的确认信息来更新滑动窗口的位置。

注意:tcp窗口与tcp滑动窗口是相关但不完全相同的概念,TCP窗口是TCP协议中的一个参数,用于控制数据传输的流量和可靠性;TCP滑动窗口是一种流量控制和拥塞控制机制,基于TCP窗口的概念,通过动态调整窗口大小来控制数据的传输速率和可靠性。

 

 

六、流量控制

TCP流量控制是TCP协议中的一种机制,用于控制数据发送方向接收方发送数据的速率,以避免接收方无法及时处理大量数据导致数据丢失或网络拥塞。

接收方通过TCP报文中的确认号和窗口大小字段向发送方通告自己的接收窗口大小。窗口大小表示接收方当前能够接收的数据量。发送方根据接收方的通告窗口大小来动态调整发送窗口的大小。如果接收方的窗口变小,发送方需要减少发送的数据量;如果接收方的窗口变大,发送方可以增加发送的数据量。

当接收方窗口大小为0时(即窗口关闭),发送方不再发送数据,而是开启一个持续计时器,超过这个时间,会发送一个窗口探测报文(Window Probe 大小为固定的1字节),探测接收窗口是否开启,发送后计时器时间归0,重新计算;

当接收方确认这个报文后,返回确认号及窗口大小,发送方接收后,继续发送数据;

当接收窗口重新开启时,接收方会主动给发送方发送一个窗口更新通知报文,包含确认号及接收窗口大小,发送方接收到后,继续发送数据。

 

6.1 糊涂窗口综合症

TCP糊涂窗口综合症(TCP Silly Window Syndrome)是指在TCP通信中,由于发送方发送的数据量太小而导致网络利用率低下的一种情况

在TCP协议中,发送方根据接收方通告的窗口大小来控制发送数据的量。接收方通常会设置一个较小的窗口大小,以便及时处理接收到的数据。然而,当发送方发送的数据量较小,接收方的窗口大小被浪费在了不必要的TCP头部开销上,从而导致网络利用率降低。

比如,发送方只需要发送2字节的数据,但需要搭载至少20字节的tcp头部数据,造成资源浪费;另外,如果发送方以较低的速率发送数据,导致接收方的窗口一直处于小的状态,网络利用率也会受到影响

 

为了解决tcp糊涂窗口综合征,可以采取以下措施:

  1. Nagle算法:
    Nagle算法通过合并发送的小数据块,将它们组合成较大的数据段一起发送,以减少每个数据段的TCP头部开销。该算法的基本原理如下:

    1. 当发送方有一个小数据块要发送时,它并不立即发送该数据块,而是将其缓冲起来。
    2. 发送方继续等待,直到有足够多的数据需要发送,或者之前发送的数据得到了对方的确认。
    3. 一旦满足以下条件之一,发送方就会将缓冲的数据块合并为一个较大的数据段发送:
      • 数据块达到了MSS(Maximum Segment Size)的大小。
      • 发送方收到了之前发送的数据的确认。
    4. 通过合并数据块,Nagle算法减少了需要发送的TCP头部的数量,从而减少了头部开销。
  2. 推迟确认:
    推迟确认是接收方对接收到的数据进行确认报文的延迟发送,将多个确认合并为一个,减少了确认报文的发送频率,从而减少了头部开销。推迟确认的原理如下:

    1. 当接收方收到数据后,并不立即发送确认报文,而是等待一小段时间,看是否有更多的数据到达。
    2. 如果在等待期间有更多的数据到达,接收方会将多个确认合并为一个,并一起发送。
    3. 只有当等待时间超过一定阈值(通常是200ms),或者接收方没有更多的数据到达时,才会发送合并后的确认报文。
    4. 通过推迟确认,接收方可以减少确认报文的发送频率,从而减少了头部开销。
  3. 调整窗口大小:发送方可以根据接收方的窗口大小来调整自己的发送窗口大小,尽可能充分利用可用的窗口空间。

注意:在高带宽和低延迟的网络环境中,Nagle算法和推迟确认的效果可能不如在低带宽和高延迟的网络环境中明显。在这些情况下,可以考虑禁用Nagle算法和使用即时确认,以最大化网络的吞吐量和实时性。

 

七、拥塞控制

TCP拥塞控制是TCP协议中的一种机制,用于在网络中发生拥塞时控制数据的发送速率,以确保网络的稳定性和公平性。

一般来说,计算机网络都处在一个共享的环境,因此也有可能会因为其他主机之间的通信使得网络拥堵。在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时 TCP 就会重传数据,但是一重传就会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这个情况就会进入恶性循环被不断地放大,TCP拥塞控制的目标是避免过多的数据被注入到已经拥塞的网络中,从而减轻拥塞并保持网络的可靠性。

TCP的拥塞控制由四个核心算法组成:慢启动、拥塞避免、快重传、快恢复。

 

注意:拥塞控制是一个全局性的过程,而滑动窗口中的流量控制则是点到点通信量的控制,它们是有本质区别的。

 

7.1 拥塞窗口

这里引入一个拥塞窗口的概念。

拥塞窗口:发送方维护的一个的状态变量,根据网络的拥塞程度动态变化。发送方的发送窗口可能等于拥塞窗口,也可能因为接收方的接收缓存不够,那么发送窗口就会小于拥塞窗口。

当拥塞窗口(Congestion Window)和发送窗口(Sending Window)两者不一致时,实际生效的是两者中较小的值

拥塞窗口的大小决定了发送方可以发送的未确认数据量,它是发送方根据网络拥塞程度动态调整的。拥塞窗口的大小直接影响发送方的发送速率

 

发送方的发送速率取决于以下两个因素:

  1. 拥塞窗口大小:拥塞窗口决定了发送方可以发送的未确认数据量。发送方每发送一个数据段,就会等待接收方发送确认,确认之前的数据段被认为是未确认的。拥塞窗口的大小限制了发送方可以连续发送的数据段数量。

  2. 往返时间(Round Trip Time,RTT):往返时间是发送方发送数据到接收方并接收到确认的时间。RTT的长短反映了网络的延迟情况。发送方根据RTT来确定数据段的传输时间和确认的返回时间。

发送速率可以通过以下公式计算:

发送速率 = 拥塞窗口大小 / RTT

拥塞窗口的大小和RTT是相互关联的。随着拥塞窗口的增大,发送方可以发送更多的数据段,从而提高发送速率。而RTT的长短会影响到发送方接收到确认的速度,从而影响发送速率。

在拥塞控制算法中,发送方会根据网络的拥塞程度动态调整拥塞窗口的大小,以实现合适的发送速率。当网络拥塞时,发送方会降低拥塞窗口的大小,从而减少发送速率以避免过载网络。当网络拥塞程度减轻时,发送方会逐渐增加拥塞窗口的大小,以提高发送速率。

通过动态调整拥塞窗口的大小,TCP拥塞控制可以根据网络的状况限制发送方的发送速率,以适应网络的拥塞程度,保持网络的稳定性和公平性。

 

7.2 慢启动,拥塞避免

TCP慢启动(Slow Start)是TCP拥塞控制算法中的一种机制,用于在建立连接或者网络出现拥塞时逐步增加发送方的发送速率。

慢启动的目的是探测网络的可用带宽,并避免在网络拥塞时过快地注入过多的数据,从而导致网络性能下降和数据丢失。

TCP慢启动的工作原理如下:

  1. 初始阶段:在建立TCP连接后,发送方将初始拥塞窗口(Congestion Window)设置为一个较小的值(通常为一个最大报文段长度,即MSS)。

  2. 指数增长:发送方每次成功接收到一个确认(ACK)时,拥塞窗口的大小会加倍,即呈指数增长。这意味着发送方可以发送的数据量也会成倍增加。

  3. 当拥塞窗口的大小达到慢启动阈值时,TCP发送方会进入拥塞避免阶段,拥塞窗口的增长速率就会变为线性增长(加性增)
  4. 拥塞避免:在拥塞避免阶段,发送方每接收到一个确认时,拥塞窗口的大小逐渐增加一个较小的量(通常为1个MSS),以避免过快增加发送速率导致网络拥塞。

 

最大报文段长度(Maximum Segment Size,MSS):用于指定在TCP连接中每个数据段(报文段)的最大长度

MSS定义了在TCP层上进行分割和传输的数据单元的最大大小。每个TCP数据段(报文段)由TCP头部和应用层数据组成,MSS限制了每个数据段中应用层数据的最大长度。

MSS的值是通过TCP连接的握手过程中的选项字段进行协商的。在建立TCP连接时,发送方会向接收方提供自己的MSS值,并接收方可以选择采用较小的那个MSS值作为双方共同的MSS。

使用MSS的主要目的是优化数据传输的效率和减少传输的开销。较大的MSS值可以使每个数据段携带更多的应用层数据,减少TCP头部的开销。这样可以提高数据传输的有效载荷比例,减少网络带宽的消耗和传输延迟。

需要注意的是,MSS的大小受到网络链路的限制。在通过不同的网络链路进行传输时,可能会有不同的MSS限制。如果某个链路的MTU(最大传输单元)较小,那么MSS也需要相应地调整为较小的值,以避免数据被拆分成更小的片段进行传输。

 
慢启动阈值(Slow Start Threshold,ssthresh):用于控制拥塞窗口在慢启动阶段的增长方式,一开始通常设置为65535字节(64KB)
 
下图中,假设拥塞窗口=发送窗口,ssthresh = 8
传输轮次(Round Trip):用于衡量数据包在发送端和接收端之间的往返时间(Round Trip Time,RTT),一个传输轮次指的是从发送方发送一个数据包开始,到接收方接收到对应的确认(ACK)报文,并将该确认报文发送回发送方,再到发送方收到该确认报文的时间间隔。

传输轮次通常用于计算拥塞窗口(Congestion Window)的大小和调整发送速率。在拥塞控制算法中,发送方会根据传输轮次的时间来调整拥塞窗口的大小,以控制数据包的发送速率。

具体来说,拥塞控制算法中的慢启动和拥塞避免阶段都会使用传输轮次来进行增长和调整。在慢启动阶段,每个传输轮次,拥塞窗口的大小会翻倍。在拥塞避免阶段,每个传输轮次,拥塞窗口的大小会线性增加。

通过观察传输轮次的时间,TCP可以根据网络的延迟和拥塞程度来适应发送速率。如果传输轮次的时间较长,可能意味着网络拥塞或延迟较高,TCP会相应地减少发送速率。如果传输轮次的时间较短,可能意味着网络畅通,TCP会相应地增加发送速率。

 

7.3 拥塞发生

当网络出现拥塞,或者发送方发生超时重传时,tcp会将拥塞窗口重置为1,ssthresh值重置为当前拥塞窗口值的一半,重新进入慢启动阶段

 

上述方法有个弊端,就是一下子将拥塞窗口重置为1,这是不科学的,因为重传一次有可能是正常的,可能是由于临时的链路问题或网络闪断引起的,并不一定表示网络一直处于拥塞状态,因此将拥塞窗口和拥塞控制阈值重置为较小的值可能过于保守,导致发送方的发送速率过低,无法充分利用可用的带宽;另外假如当时发送速率很快,一下将拥塞窗口重置为1,反而会造成网络卡顿的感觉
 
故tcp需要一种更完善的拥塞算法,即快重传,快恢复
 

7.4 快重传,快恢复

TCP的快重传(Fast Retransmit)和快恢复(Fast Recovery)是一对相互关联的机制,用于在网络出现拥塞时更快地恢复发送方的发送速率,而无需完全进入慢启动阶段。

快重传即快速重传,本文4.2已介绍过。
快恢复是指在发送方进行快重传后,将拥塞窗口的大小设置为原来拥塞窗口的一半,而不是将拥塞窗口重置为1;ssthresh的值也等于原来拥塞窗口的一半,即ssthresh = new cwnd = old cwnd/2
然后,发送方进入拥塞避免阶段,以线性增加的方式增加拥塞窗口的大小。这样可以保持一定的发送速率,避免重新进入慢启动阶段,从而更快地恢复发送方的发送速率。

 

 

参考:https://xiaolincoding.com/network/3_tcp/tcp_interview.html
 
 
 
 

 

posted @ 2024-02-21 14:22  心恩惠动  阅读(195)  评论(0编辑  收藏  举报