TCP详解

1. TCP介绍

1.1 TCP是什么

TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。

  • 面向连接:一定是「一对一」才能连接,不能像 UDP 协议可以一个主机同时向多个主机发送消息,也就是一对多是无法做到的;

  • 可靠的:无论的网络链路中出现了怎样的链路变化,TCP 都可以保证一个报文一定能够到达接收端;

  • 字节流:用户消息通过 TCP 协议传输时,消息可能会被操作系统「分组」成多个的 TCP 报文,如果接收方的程序如果不知道「消息的边界」,是无法读出一个有效的用户消息的。并且 TCP 报文是「有序的」,当「前一个」TCP 报文没有收到的时候,即使它先收到了后面的 TCP 报文,那么也不能扔给应用层去处理,同时对「重复」的 TCP 报文会自动丢弃。

1.2 TCP连接定义

连接:用于保证可靠性和流量控制维护的某些状态信息,这些信息包括 Socket、序列号和窗口大小。

TCP四元组确定唯一的一个连接:源地址、源端口、目的地址、目的端口。

所以一个服务的某个端口,最大TCP连接数=客户端IP数 * 客户端端口数。IPv4有32位,端口数有16位,理论上最大连接数是2的48次方。
但实际上由于文件描述符限制及内存限制,不能达到此上限。

2. TCP/UDP头格式

2.1 TCP头格式

  • 源端口号/目的端口号
  • 序号
    解决包乱序问题
  • 确认序列
    目的是确认发出去对方是否有收到。如果没有收到就应该重新发送,直到送达,这个是为了解决丢包的问题。
  • 首部长度
    表示TCP首部有多少个(4字节)的值。
  • 状态位
    • URG ( urgent pointer) :紧急指针,当URG标志位被设置为1时,紧急指针被用来告诉设备发送的数据包的紧急程度。当标志位被设置为0时,紧急指针不产生任何效果。
    • ACK (acknowledgment):确认,当ACK标志位被设置为1时,确认号字段有效。TCP 规定除了最初建立连接时的 SYN 包之外该位必须设置为 1。
    • PSH (push):该位为 1 时,接收设备应该立即将这个数据包交给应用程序,而不是缓冲它。
    • RST (reset):该位为 1 时,表示 TCP 连接中出现异常必须强制断开连接。
    • SYN (synchronize):该位为 1 时,表示希望建立连接,并在其「序列号」的字段进行序列号初始值的设定。
    • FIN (finish):该位为 1 时,表示今后不会再有数据发送,希望断开连接。
  • 窗口大小
    双方都维护一个窗口(缓存大小),标识自己当前能够的处理能力。解决流量控制及拥塞控制问题。
  • 校验和
    TCP头部和数据部分的校验和,这是一个强制性的字段,由发送端计算和存储,并由接收端进行验证。其主要功能是检查数据在传输过程中是否发生了错误。
  • 紧急指针
    只在URG位字段被设置时才有效。这个“指针”是一个必须要加到报文段的序列号字段上的正偏移,以产生紧急数据的最后一个字节的序列号。TCP的紧急机制是一种让发送方给另一端提供特殊标志数据的方法。

2.2 UDP头格式

UDP 不提供复杂的控制机制,利用 IP 提供面向「无连接」的通信服务。

  • 目标和源端口:主要是告诉 UDP 协议应该把报文发给哪个进程。
  • 包长度:该字段保存了 UDP 首部的长度跟数据的长度之和。
  • 校验和:校验和是为了提供可靠的 UDP 首部和数据而设计,防止收到在网络传输中受损的 UDP 包。

2.3 TCP和UDP区别

  1. 连接
    TCP 是面向连接的传输层协议,传输数据前先要建立连接。
    UDP 是不需要连接,即刻传输数据。
  2. 服务对象
    TCP 是一对一的两点服务,即一条连接只有两个端点。
    UDP 支持一对一、一对多、多对多的交互通信
  3. 可靠性
    TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按序到达。
    UDP 是尽最大努力交付,不保证可靠交付数据。但是我们可以基于 UDP 传输协议实现一个可靠的传输协议,比如 QUIC 协议,具体可以参见这篇文章:如何基于 UDP 协议实现可靠传输?(opens new window)
  4. 拥塞控制、流量控制
    TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。
    UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率。
  5. 首部开销
    TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是 20 个字节,如果使用了「选项」字段则会变长的。
    UDP 首部只有 8 个字节,并且是固定不变的,开销较小。
  6. 传输方式
    TCP 是流式传输,没有边界,但保证顺序和可靠。
    UDP 是一个包一个包的发送,是有边界的,但可能会丢包和乱序。
  7. 分片不同
    TCP 的数据大小如果大于 MSS 大小,则会在传输层进行分片,目标主机收到后,也同样在传输层组装 TCP 数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片。
    UDP 的数据大小如果大于 MTU 大小,则会在 IP 层进行分片,目标主机收到后,在 IP 层组装完数据,接着再传给传输层。

2.4 TCP 和 UDP 应用场景

由于 TCP 是面向连接,能保证数据的可靠性交付,因此经常用于:

  • FTP 文件传输;
  • HTTP / HTTPS;
    由于 UDP 面向无连接,它可以随时发送数据,再加上 UDP 本身的处理既简单又高效,因此经常用于:
  • 包总量较少的通信,如 DNS 、SNMP 等;
  • 视频、音频等多媒体通信;
  • 广播通信;

2.5 TCP 和 UDP 可以使用同一个端口吗

可以,因为 TCP 和 UDP在内核中是两个完全独立的软件模块。而端口号的作用,是为了区分同一个主机上不同应用程序。

2. TCP连接建立

2.1 三次握手过程

image

第三次握手是可以携带应用层数据的,前两次握手是不可以携带应用层数据的。

2.2 需要三次握手的原因

  • 避免历史连接
  • 同步双方初始序列号
  • 避免资源浪费

2.2.1 避免历史连接

客户端先发送旧SYN(Seq Num=90)发起连接,由于网络问题没有达到,发送新SYN(Seq Num=100)发起另一个连接。(不是重试,重试的Seq Num不会变化)

情况一:服务端接收顺序 旧SYN - RST - 新SYN

#### 情况二:服务端接收顺序 旧SYN - 新SYN - RST 服务端连续收到SYN包时,第二次会回复服务器认为正确的Ack Num(90 + 1),客户端发现不是期望的101,依然会发送RST包。这个响应和已经建立连接的情况下,客户端发送同样ip和端口的SYN包情况相同。

如果是两次就建立连接,类似场景就会导致很多无效历史连接。

2.2.2 同步双方初始序列号

序列号作用:

  • 接收方可以去除重复的数据;
  • 接收方可以根据数据包的序列号按序接收;
  • 可以标识发送出去的数据包中, 哪些是已经被对方收到的(通过 ACK 报文中的序列号知道)

一来一回,才能确保双方的初始序列号能被可靠的同步。至少需要三步。

2.2.3 避免资源浪费

场景同避免历史连接,如果只有两次会建立很多无效连接,导致资源被浪费。

2.3 三次握手中的某一步包丢失会怎样

客户端SYN包丢失

客户端会超时重发SYN包。

服务器ACK+SYN包丢失

此时客户端已发送SYN包,由于没有收到ACK包确认,故会认为丢失触发超时重传机制。
服务器由于也不会收到客户端的ACK包,也会触发超时重传机制。

客户端ACK包丢失

ACK包不会重传,所以服务器会一直重试发送ACK+SYN包。

2.4 SYN攻击

攻击者短时间伪造不同 IP 地址的 SYN 报文,服务端每接收到一个 SYN 报文,就进入SYN_RCVD 状态,但服务端发送出去的 ACK + SYN 报文,无法得到未知 IP 主机的 ACK 应答,久而久之就会占满服务端的半连接队列,使得服务端不能为正常用户服务。

在 TCP 三次握手的时候,Linux 内核会维护两个队列,分别是:

  • 半连接队列:SYN 队列
  • 全连接队列:accept 队列

正常流程:

  1. 当服务端接收到客户端的 SYN 报文时,会创建一个半连接的对象,然后将其加入到内核的「 SYN 队列」;
  2. 接着发送 SYN + ACK 给客户端,等待客户端回应 ACK 报文;
  3. 服务端接收到 ACK 报文后,从「 SYN 队列」取出一个半连接对象,然后创建一个新的连接对象放入到「 Accept 队列」;
  4. 应用通过调用 accpet() socket 接口,从「 Accept 队列」取出连接对象。

不管是半连接队列还是全连接队列,都有最大长度限制,超过限制时,默认情况都会丢弃报文。SYN攻击便是利用的这个原理。

3. TCP连接断开

3.1 四次挥手过程

image

3.2 需要四次挥手的原因

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

3.3 有没有可能三次挥手

是可能的。
服务器「没有数据要发送」并且「开启了 TCP 延迟确认机制」,那么第二和第三次挥手就会合并传输,这样就出现了三次挥手。

TCP 延迟确认机制是默认开启的,所以只要服务器「没有数据要发送」就可能出现三次挥手。当发送没有携带数据的ACK,它的网络效率也是很低的,所以出现了TCP 延迟确认机制。
TCP 延迟确认的策略:

  • 当有响应数据要发送时,ACK 会随着响应数据一起立刻发送给对方
  • 当没有响应数据要发送时,ACK 将会延迟一段时间,以等待是否有响应数据可以一起发送
  • 如果在延迟等待发送 ACK 期间,对方的第二个数据报文又到达了,这时就会立刻发送 ACK

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

MSL 是 Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。
MSL 的单位是时间,而 IP头中TTL 是经过路由跳数。所以 MSL 应该要大于等于 TTL 消耗为 0 的时间,以确保报文已被自然消亡。TTL 的值一般是 64,Linux 将 MSL 设置为 30 秒,意味着 Linux 认为数据报文经过 64 个路由器的时间不会超过 30 秒,如果超过了,就认为报文已经消失在网络中了。

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

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

3.5 为什么需要 TIME_WAIT 状态

  • 防止历史连接中的数据,被后面相同四元组的连接错误的接收
    因为序列号是循环使用的。TIME_WAIT 状态,状态会持续 2MSL 时长,这个时间足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。

  • 保证「被动关闭连接」的一方,能被正确的关闭
    TIME-WAIT 作用是等待足够的时间以确保最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭。

    如果客户端(主动关闭方)最后一次 ACK 报文(第四次挥手)在网络中丢失了,那么按照 TCP 可靠性原则,服务端(被动关闭方)会重发 FIN 报文。客户端在收到服务端重传的 FIN 报文时,TIME_WAIT 状态的等待时间,会重置回 2MSL。

    假设没有TIME-WAIT状态,那么当客户端收到服务器的FIN报文时已经CLOSE状态,会回复RST报文强制中止连接,不够优雅。

3.6 服务器出现大量 TIME_WAIT 状态的原因有哪些?

TIME_WAIT 状态是主动关闭连接方才会出现的状态,说明服务器主动断开了很多 TCP 连接。
原因:

  • HTTP 没有使用长连接
    无论哪方未启动长连接,都是服务器端主动断开当前连接。

  • HTTP 长连接超时
    HTTP 长连接的特点是,只要任意一端没有明确提出断开连接,则保持 TCP 连接状态。但是没有任何数据传输并持续到超时后,服务器会调用回调函数主动断开连接。

  • HTTP 长连接的请求数量达到上限
    Web 服务端通常会有个参数,来定义一条 HTTP 长连接上最大能处理的请求数量,当超过最大限制时,就会主动关闭连接。
    比如 nginx 的 keepalive_requests 这个参数默认值是 100 ,意味着每个 HTTP 长连接最多只能跑 100 次请求。对于QPS较大的服务,应该将keepalive_requests参数值调大。

3.7 服务器出现大量 CLOSE_WAIT 状态的原因有哪些?

CLOSE_WAIT 状态是「被动关闭方」才会有的状态,而且如果「被动关闭方」没有调用 close 函数关闭连接,那么就无法发出 FIN 报文,从而无法使得 CLOSE_WAIT 状态的连接转变为 LAST_ACK 状态
所以,当服务端出现大量 CLOSE_WAIT 状态的连接的时候,说明服务端的程序没有调用 close 函数关闭连接

那什么情况会导致服务端的程序没有调用 close 函数关闭连接?这时候通常需要排查代码。

分析一个普通的 TCP 服务端的流程

  • 创建服务端 socket,bind 绑定端口、listen 监听端口
  • 将服务端 socket 注册到 epoll
  • epoll_wait 等待连接到来,连接到来时,调用 accpet 获取已连接的 socket
  • 将已连接的 socket 注册到 epoll
  • epoll_wait 等待事件发生
  • 对方连接关闭时,我方调用 close
  • 可能导致服务端没有调用 close 函数的原因,如下。

第一个原因:第 2 步没有做,没有将服务端 socket 注册到 epoll,这样有新连接到来时,服务端没办法感知这个事件,也就无法获取到已连接的 socket,那服务端自然就没机会对 socket 调用 close 函数了。

不过这种原因发生的概率比较小,这种属于明显的代码逻辑 bug,在前期 read view 阶段就能发现的了。

第二个原因: 第 3 步没有做,有新连接到来时没有调用 accpet 获取该连接的 socket,导致当有大量的客户端主动断开了连接,而服务端没机会对这些 socket 调用 close 函数,从而导致服务端出现大量 CLOSE_WAIT 状态的连接。

发生这种情况可能是因为服务端在执行 accpet 函数之前,代码卡在某一个逻辑或者提前抛出了异常。

第三个原因:第 4 步没有做,通过 accpet 获取已连接的 socket 后,没有将其注册到 epoll,导致后续收到 FIN 报文的时候,服务端没办法感知这个事件,那服务端就没机会调用 close 函数了。

发生这种情况可能是因为服务端在将已连接的 socket 注册到 epoll 之前,代码卡在某一个逻辑或者提前抛出了异常。之前看到过别人解决 close_wait 问题的实践文章,感兴趣的可以看看:一次 Netty 代码不健壮导致的大量 CLOSE_WAIT 连接原因分析

第四个原因:第 6 步没有做,当发现客户端关闭连接后,服务端没有执行 close 函数,可能是因为代码漏处理,或者是在执行 close 函数之前,代码卡在某一个逻辑,比如发生死锁等等。

3.8 没有 accept,能建立 TCP 连接吗

可以。
因为accpet 系统调用并不参与 TCP 三次握手过程,它只是负责从 TCP 全连接队列取出一个已经建立连接的 socket。
用户层通过 accpet 系统调用拿到了已经建立连接的 socket,才能对该 socket 进行读写操作。

4. TCP的重传机制/滑动窗口/流量控制/拥塞控制

4.1 重传机制

4.1.1 超时重传

超过指定的时间后,没有收到对方的 ACK 确认应答报文,就会重发该数据。
可能的原因有两种:

  • 数据包丢失,对方未收到
  • 对方已收到,但是ACK报文丢失

RTO值

超时重传时间 RTO (Retransmission Timeout 超时重传时间)的值应该略大于报文往返 RTT(Round-Trip Time 往返时延) 的值。
由于网络环境动态变化,RTO值会通过采样 RTT加权计算的值/RTT波动范围值 动态计算出RTO值。
每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍。两次超时,就说明网络环境差,不宜频繁反复发送。

4.1.2 快速重传

快速重传(Fast Retransmit)机制,它不以时间为驱动,而是以数据驱动重传。
当收到三个相同的 ACK 报文时,会在超时重传之前,重传丢失的报文段。

快速重传机制解决了超时重发的时间等待,但是快速重传并不清楚丢失的所有包有哪些

比如假设发送方发了 6 个数据,编号的顺序是 Seq1 ~ Seq6 ,但是 Seq2、Seq3 都丢失了,那么接收方在收到 Seq4、Seq5、Seq6 时,都是回复 ACK2 给发送方,但是发送方并不清楚这连续的 ACK2 是接收方收到哪个报文而回复的, 不能一次性精准重传缺失的Seq2、Seq3报文。

4.1.3 SACK

Selective Acknowledgmen,选择性确认。
TCP 头部「选项」字段增加SACK信息,表明已收到的数据包信息。
利用SACK和ACK,发送方可以清楚地知道有哪些包还没收到。
SACK机制中,ACK是小于SACK的范围的。ACK到SACK起始位置的数据包表示还没收到,SACK范围内的包表示接收到了。

4.1.4 D-SACK

Duplicate SACK, 使用了 SACK 来告诉「发送方」有哪些数据被重复接收了。
D-SACK机制中,ACK是大于SACK的范围的.SACK范围内的包表示被重复接收了。

4.2 滑动窗口

4.2.1 定义

为了避免通信双方一问一答的低效传输模式,TCP引入了窗口概念。而窗口大小就是指无需等待确认应答,而可以继续发送数据的最大值。
窗口的实现是操作系统开辟的一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。

4.3 流量控制

4.3.1 窗口大小

TCP头部中有窗口大小字段,就是告诉发送端自己还有多少缓冲区可以接收数据
发送端就可以根据这个接收端的处理能力来发送数据。如果发送方发送的数据大小超过接收方的窗口大小,接收方就无法正常接收到数据,会导致触发重发机制。
所以发送方的窗口的大小也是由接收方的窗口大小来决定的。如果接收方Window变化了,由于报文有时延,返回的Windows字段要等会儿才会反应到发送方的滑动窗口。所以发送窗口大小约等于接收窗口大小。

4.3.2 滑动窗口流控细节

发送方滑动窗口细节。

每次发送数据后,SND.NXT移动对应长度;收到ACK包确认的序号及窗口大小后,SND.UNA再移动至对应序号及调整窗口大小SND.WND。这样便控制了发送端的发送速率。

4.3.3 窗口关闭

接收方向发送方通告窗口大小时,是通过 ACK 报文来通告的。当发生窗口关闭时,接收方处理完数据后,会向发送方通告一个窗口非 0 的 ACK 报文,告知窗口已开启。

为了避免窗口开启的ACK报文丢失,发送方也有窗口关闭超时机制。超时后,会发送多次窗口探测 ( Window probe ) 报文。如果连续3次窗口依然为0,有的TCP实现会发送RST报文中断连接。

4.3.4 糊涂窗口综合征

糊涂窗口综合征:接口方只有少量字节窗口,发送方依然发送的情况。

避免这种情况,TCP制定了两个策略:

接收方

  • 当「窗口大小」小于 min(MSS,缓存空间/2) ,也就是小于MSS与1/2缓存大小中的最小值时,就会向发送方通告窗口为 0

发送方

Nagle 算法:

  • 窗口大小>=MSS 并且 数据大小>MSS;
  • 等待超时(一般为200ms)

4.4 拥塞控制

流量控制只能控制TCP双方的流量,对于整个网络的拥堵情况却无法感知。

4.4.1 网络拥塞的危害

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

4.4.2 拥塞窗口算法

拥塞窗口cwnd

控制发送方的发送窗口大小,根据网络的拥塞程度动态变化的,发送窗口=min(拥塞窗口, 接收窗口)

拥塞判断原理

没有在规定时间内接收到 ACK 应答报文,发生了超时重传,就会认为网络出现了拥塞。

拥塞控制算法

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

TCP 在刚建立连接完成后,首先是有个慢启动的过程。

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

比如连接刚建立吧开始1个,然后2个,然后4个...,,窗口大小呈指数级增长

当拥塞窗口达到慢启动门限(ssthresh,slow start threshold,默认65535字节)时,会切换为拥塞避免算法。

4.4.2.2 拥塞避免算法

当拥塞窗口达到慢启动门限(ssthresh,slow start threshold)时,会切换为拥塞避免算法。

拥塞避免规则:每当收到一个新ACK时,拥塞窗口增加1/拥塞窗口。实际上,就是每秒增加1个,窗口大小呈线性级增长

4.4.2.3 拥塞发生算法

当出现数据包重传的情况,便认为拥塞发生了,切换为拥塞发生算法。
针对于超时重传和快速重传,有不同的拥塞发生算法。

  • 针对超时重传的拥塞发生算法
  1. 慢启动门限=当前拥塞窗口的1/2,ssthresh=cwnd/2
  2. 拥塞窗口=1,cwnd=1

效果就是从慢启动算法重新开始,达到之前拥塞窗口的1/2时便切换为拥塞避免算法了。

  • 针对快速重传的拥塞发生算法
  1. 拥塞窗口=当前拥塞窗口的1/2,cwnd=cwnd/2
  2. 慢启动门限=更新后的拥塞窗口,ssthresh=cwnd
  3. 进入快速恢复算法
4.4.2.4 快速恢复算法

只有快速重传触发的拥塞发生算法,才会启动快速恢复算法。

  1. 拥塞窗口=慢启动门限+3, cwnd=ssthresh+3
  2. 重传丢失数据包
  3. 收到重复的ACK,拥塞窗口=拥塞窗口+1,cwnd=cwnd+1
    收到不重复ACK,拥塞窗口=慢启动门限,cwnd=ssthres

快速恢复算法第3步的算法为什么是这样?

  • 收到重复的ACK,拥塞窗口=拥塞窗口+1
    收到重复的ACK表示接收方还没拿到新的数据,网络依然拥塞。
    可以看看发送方滑动窗口的图,由于发送方不断的重传丢失的数据包,导致已发送未ACK的范围越来越大,那么可用窗口就会越来越少甚至无法发送。所以拥塞窗口持续+1是一种临时补偿机制。

  • 收到不重复ACK,拥塞窗口=慢启动门限
    收到重复的ACK表示接收方已成功接收,网络已正常。
    此时将拥塞门限重置为慢启动门限,其实是撤销了阻塞期间拥塞窗口持续+1的补偿措施。直接切换到拥塞避免算法。

5. MSS和MTU

既然IP包会按照MTU大小分片,为什么TCP还要提前分片成MSS大小?
MTU=IP头部长度+TCP头部长度+MSS
因为IP层是不可靠的,不支持超时和重传机制。
假设没有MSS分片,一个TCP数据包被分成n个IP包,其中一个IP包丢失,那么整个TCP包便不能回复ACK给发送端。超时后,发送端会重新发送整个TCP包,相当于又要重发n个IP包,就算之前收到了n-1个IP包也要重发,效率很低下。
所以,TCP直接根据MTU大小事先按照MSS划分后,就算丢失了一个IP包,也最多重传一个IP包的数据,大大增加了重传的效率。

6. TCP Keepalive 和 HTTP Keep-Alive区别

6.1 TCP Keepalive

TCP Keepalive就是TCP保活机制。

如果两端的 TCP 连接一直没有数据交互,达到了触发 TCP 保活机制的条件,那么内核里的 TCP 协议栈就会发送探测报文。

  • 如果对端程序是正常工作的。当 TCP 保活的探测报文发送给对端, 对端会正常响应,这样 TCP 保活时间会被重置,等待下一个 TCP 保活时间的到来。
  • 如果对端主机宕机(注意不是进程崩溃,进程崩溃后操作系统在回收进程资源的时候,会发送 FIN 报文,而主机宕机则是无法感知的,所以需要 TCP 保活机制来探测对方是不是发生了主机宕机),或对端由于其他原因导致报文不可达。当 TCP 保活的探测报文发送给对端后,石沉大海,没有响应,连续几次,达到保活探测次数后,TCP 会报告该 TCP 连接已经死亡。
    所以,TCP 保活机制可以在双方没有数据交互的情况,通过探测报文,来确定对方的 TCP 连接是否存活,这个工作是在内核完成的。

6.2 HTTP Keep-Alive

HTTP Keep-Alive就是HTTP长连接,使用同一个 TCP 连接来发送和接收多个 HTTP 请求/应答,避免了连接建立和释放的开销。

HTTP请求头和响应头中,都有Connection: Keep-Alive便可开启长连接。

HTTP 长连接有默认超时时间。如果客户端在长连接超时期间没有再发起新的请求,服务器的定时器的超时时间一到,就会触发回调函数来释放该连接。

6.3 总结

HTTP 的 Keep-Alive 也叫 HTTP 长连接,该功能是由应用程序实现的,可以使得用同一个 TCP 连接来发送和接收多个 HTTP 请求/应答,减少了 HTTP 短连接带来的多次 TCP 连接建立和释放的开销。

TCP 的 Keepalive 也叫 TCP 保活机制,该功能是由内核实现的,当客户端和服务端长达一定时间没有进行数据交互时,内核为了确保该连接是否还有效,就会发送探测报文,来检测对方是否还在线,然后来决定是否要关闭该连接。

posted @ 2023-12-13 16:50  kiper  阅读(12)  评论(0编辑  收藏  举报