目录

一、TCP 是面向连接的:为何需要“连接”?

可靠性机制依赖于连接

二、操作系统如何管理连接?

1、“先描述,再组织”的管理哲学

2、连接的生命周期与资源成本

连接是有成本的

三、TCP协议三次握手与四次挥手全局过程分析

1、全局过程图

2、服务端状态转换流程

3、客户端状态转换流程

4、TCP状态转换汇总图

四、三次握手:建立连接的艺术

1、握手过程详解(客户端 → 服务器)

三次握手的具体过程

2、为什么必须是三次?三大核心理由

(1)最小验证双向通信能力

(2)防止历史连接污染(防旧 SYN 攻击)

(3)风险转移:异常连接由客户端承担

3、三次握手的状态变迁

4、套接字 API 与三次握手的关联机制

五、四次挥手:优雅断开的艺术

1、挥手过程详解

2、为什么是四次?

3、四次挥手的状态变化

4、套接字 API 与四次挥手

六、连接管理中的两大陷阱

1、CLOSE_WAIT:服务器的“隐形杀手”

1. 问题根源

2. 后果

3. 诊断与解决

2、TIME_WAIT:主动关闭方的“等待代价”

1. 为何需要 TIME_WAIT?

2. TIME_WAIT 时长

3. 优化策略(针对高并发短连接场景)

七、总结:连接管理的核心思想


一、TCP 是面向连接的:为何需要“连接”?

可靠性机制依赖于连接

TCP 的所有可靠性保障(确认应答、超时重传、流量控制、拥塞控制等)并非作用于主机之间,而是作用于“连接”之上。这是因为:

  • 一台服务器可能同时与成百上千个客户端通信;

  • 若没有“连接”的概念,所有客户端的数据将混入同一个接收缓冲区,造成数据交叉、混乱甚至安全漏洞;

  • 每个 TCP 连接拥有独立的发送/接收缓冲区、序列号空间、窗口大小和状态机,从而实现会话隔离。

核心结论:“建立连接”是启用 TCP 可靠性机制的前提。只有在连接建立成功后,双方才能基于该连接进行有序、可靠、无重复的数据传输。


二、操作系统如何管理连接?

1、“先描述,再组织”的管理哲学

操作系统通过内核中的 TCP 控制块(TCB, TCP Control Block) 来描述每一个连接。TCB 中包含:

  • 本地与远程的 IP 地址和端口号(四元组)

  • 当前序列号(Seq)、确认号(Ack)

  • 接收/发送窗口大小

  • 连接状态(ESTABLISHED、TIME_WAIT 等)

  • 定时器(用于重传、保活等)

  • 发送/接收缓冲区指针

所有 TCB 被组织在高效的数据结构中(如哈希表 + 链表),使得连接的查找、插入、删除操作尽可能高效。

2、连接的生命周期与资源成本

  • 建立连接 = 分配 TCB + 初始化字段 + 插入连接表

  • 断开连接 = 从连接表移除 + 释放缓冲区 + 回收 TCB 内存

连接是有成本的

  • 时间成本:内核需维护状态、处理定时器、执行握手/挥手

  • 空间成本:每个连接通常占用 3–5 KB 内存(含缓冲区)

因此,在高并发系统中,连接数成为关键性能瓶颈,这也是为何要使用连接池、长连接等优化手段。


三、TCP协议三次握手与四次挥手全局过程分析

在正常情况下,TCP协议通过三次握手建立连接,通过四次挥手断开连接。

1、全局过程图

2、服务端状态转换流程

  1. [CLOSED -> LISTEN] 服务器调用listen后进入LISTEN状态,开始监听客户端连接请求;

  2. [LISTEN -> SYN_RCVD] 当收到客户端SYN同步报文时,将该连接加入内核等待队列,并向客户端发送SYN+ACK确认报文;

  3. [SYN_RCVD -> ESTABLISHED] 收到客户端的ACK确认后,连接建立完成,进入可读写数据的ESTABLISHED状态;

  4. [ESTABLISHED -> CLOSE_WAIT] 当客户端发起关闭请求(FIN报文)时,服务器确认后进入CLOSE_WAIT状态;

  5. [CLOSE_WAIT -> LAST_ACK] 服务器处理完待发送数据后,调用close关闭连接,然后发送FIN报文给客户端并进入LAST_ACK状态,等待客户端最后的ACK确认(这个ACK是客户端确认收到了FIN);

  6. [LAST_ACK -> CLOSED] 收到客户端的最终ACK后,服务器完全关闭连接。

3、客户端状态转换流程

  1. [CLOSED → SYN_SENT] 客户端调用connect()发送SYN报文,发起连接请求

  2. [SYN_SENT → ESTABLISHED] connect()调用成功后进入连接就绪(ESTABLISHED)状态,可开始数据传输

  3. [ESTABLISHED → FIN_WAIT_1] 客户端主动调用close()发送FIN报文,进入终止等待状态

  4. [FIN_WAIT_1 → FIN_WAIT_2] 收到服务端对FIN报文的ACK确认后,进入二次终止等待状态,等待服务器发送结束报文

  5. [FIN_WAIT_2 → TIME_WAIT] 收到服务端FIN报文后进入时间等待状态,并发送LAST_ACK

  6. [TIME_WAIT → CLOSED] 客户端等待2MSL(Max Segment Life最大报文生存时间)后才会进入 CLOSED 状态,最终关闭连接

4、TCP状态转换汇总图

粗虚线表示服务端状态变化,粗实线表示客户端状态变化,CLOSED是虚拟起始状态,并非实际状态。


四、三次握手:建立连接的艺术

1、握手过程详解(客户端 → 服务器)

在TCP通信中,建立连接的过程被称为三次握手。以客户端和服务器为例:当客户端需要与服务器通信时,首先会发送连接请求。此时TCP协议会在底层自动完成三次握手流程。

步骤 报文标志 序列号/确认号 发送方状态 接收方动作
1 SYN=1 Seq=x SYN_SENT 服务器收到后放入“未完成连接队列”
2 SYN=1, ACK=1 Seq=y, Ack=x+1 SYN_RCVD 客户端收到后准备发送最终确认
3 ACK=1 Seq=x+1, Ack=y+1 ESTABLISHED 服务器收到后移入“已完成连接队列”

三次握手的具体过程

  1. 第一次握手:客户端发送SYN=1的报文,表示请求与服务端建立连接

  2. 第二次握手:服务器收到请求后,同时发送SYN=1和ACK=1的报文,既确认客户端的请求 又 发起自己作为服务端向客户端的连接请求(服务端完成两个操作)

  3. 第三次握手:当客户端收到服务器发送的确认报文后,确认服务器已接收其连接请求并同意建立连接,随后客户端向服务器发送最终确认响应完成握手过程(发送ACK报文进行最终确认)

关键点TCP是全双工通信协议,一次连接包含两个单向通道。第一次握手建立「客户端→服务器」通道,第二次握手中的 SYN 实际是在请求建立「服务器→客户端」通道。(客户端的连接请求仅建立客户端到服务器方向的连接,而服务器也需要独立发起从服务器到客户端方向的连接请求,这正是第二次握手中服务器发送SYN报文的原因。)

2、为什么必须是三次?三大核心理由

        我们需要明确的是,TCP连接的建立并非百分之百成功。在三次握手过程中,前两次握手的可靠性可以得到保证,因为每次握手都会触发对方的响应。然而第三次握手由于没有对应的确认报文,其可靠性无法确保。如果客户端发送的第三次ACK报文丢失,连接建立就会失败。这种情况下,虽然客户端已完成三次握手流程,但服务端因未收到确认报文而不会建立连接。这表明无论采用几次握手协议,最后一次握手的可靠性始终无法得到保证。既然连接建立本身就存在失败的可能性,因此选择握手次数的关键考量,实际上是基于不同握手方案的优势比较。

(1)最小验证双向通信能力

TCP三次握手是验证通信双方信道连通性的最小必要次数:

  • 由于TCP采用全双工通信,建立连接的核心目标在于确认双方的通信信道是否畅通。

  • 三次握手恰好能够以最少的交互次数完成这一验证,确保双方都能确认彼此的收发能力是否正常。

  • 少于三次无法验证双向通信;多于三次则冗余低效。

从客户端角度:

  1. 收到第二次握手响应时,表明首次握手请求已被服务器可靠接收,(已知)证明客户端发送能力和服务器接收能力正常(自己能发(服务器收到了 SYN)服务器能发(自己收到了 SYN+ACK)、双方都能收

  2. 同时,成功接收第二次握手也证明了服务器的发送能力和客户端的接收能力正常

从服务器角度:

  1. 收到第一次握手时,确认客户端的发送能力和自身的接收能力正常

  2. 收到第三次握手时,表明第二次握手响应已被客户端可靠接收,证明自身发送能力和客户端接收能力正常

  3. 也就是说:服务器在收到第三次握手后,才确认:客户端能收(收到了自己的 SYN+ACK 并回应了 ACK)

三次握手已完全满足验证需求,虽然更多次握手也能达到相同目的,但三次已是最优解,无需增加不必要的交互。

(2)防止历史连接污染(防旧 SYN 攻击)

假设一个延迟的旧 SYN 到达服务器:

  • 若仅两次握手,服务器会误认为新连接并分配资源;

  • 三次握手要求客户端用正确的序列号回应,否则服务器会拒绝(发送 RST)。

(3)风险转移:异常连接由客户端承担

三次握手机制确保了连接建立过程中的异常连接由客户端承担:

  • 当客户端收到服务器的第二次握手响应时,即可确认通信信道畅通。此时客户端发出第三次握手后,连接在客户端侧即告建立。而服务器端需要成功接收第三次握手后,才能确认通信正常并建立对应连接。

  • 这种设计使双方建立连接的时间点存在差异。若客户端发出的第三次握手丢失,服务器端不会建立连接,而客户端需要短暂维护一个异常连接。

  • 由于维护连接需要消耗时间和空间资源,三次握手的优势在于将异常连接限制在客户端,避免影响服务器性能。

  • 虽然客户端仍需处理异常连接,但其数量通常有限。相比之下,若服务器承担异常连接,当多个客户端同时连接失败时,将消耗大量服务器资源。

  • 此外,这些异常连接不会永久存在:若服务器长时间未收到第三次握手,会重传第二次握手,给客户端重新发送的机会;或者当客户端发送数据时,服务器会要求重新建立连接。

若第三次握手丢失:

  • 客户端:认为连接已建立(进入 ESTABLISHED),但后续发数据会被服务器拒绝(因服务器未建连)

  • 服务器:未创建连接,不消耗资源

  • 这种设计保护了服务器,避免其因大量失败连接耗尽资源。

3、三次握手的状态变迁

初始状态:客户端和服务器均处于CLOSED状态

状态转换流程:

  1. 服务器启动监听,从CLOSED转为LISTEN状态

  2. 客户端发送SYN报文:状态转为SYN_SENT

  3. 服务器收到SYN后:将连接加入等待队列、发送SYN+ACK响应、状态转为SYN_RCVD

  4. 客户端收到SYN+ACK后:发送最终ACK确认、状态转为ESTABLISHED

  5. 服务器收到最终ACK后:状态转为ESTABLISHED

完成握手后,双方进入ESTABLISHED状态,即可开始数据传输。

4、套接字 API 与三次握手的关联机制

服务器需首先调用listen函数进入LISTEN状态,这是建立连接的前提条件。当服务器处于LISTEN状态时,客户端即可通过connect函数发起三次握手请求。

需要明确的是,connect函数本身并不直接执行三次握手过程,其作用仅是触发握手流程。该函数返回时,表明底层已完成三次握手(无论成功与否)。

成功完成三次握手后,服务器内核会生成一个处于等待队列的新连接。服务器需调用accept函数来获取该连接。连接建立后,双方即可通过read/recv和write/send函数进行数据通信。

操作 系统调用 内核行为
服务器准备监听 listen(sockfd, backlog) 创建未完成/已完成连接队列
客户端发起连接 connect(sockfd, addr) 发送 SYN,阻塞直到握手完成或失败
服务器接受连接 accept(listenfd, ...) 从未完成队列取出已建连的套接字

重要澄清

  • accept() 不参与握手,它只是从内核的“已完成连接队列”中取出连接。

  • backlog 参数限制未完成连接队列长度,影响抗突发连接能力。


五、四次挥手:优雅断开的艺术

1、挥手过程详解

TCP连接的维护需要资源开销,因此通信结束后必须通过"四次挥手"过程来释放连接。以客户端和服务器为例:

  1. 第一次挥手:客户端发送FIN=1的报文,主动发起断开连接请求

  2. 第二次挥手:服务器收到客户端发来的断开连接请求后对其进行响应(服务器收到FIN后立即返回确认响应)

  3. 第三次挥手:当客户端请求断开连接且服务器已无待发送数据时,服务器将主动发起断开连接请求(服务器完成数据发送后,同样发送FIN=1报文请求断开)

  4. 第四次挥手:客户端收到服务器发来的断开连接请求后对其进行响应(客户端确认服务器的断开请求)

完成这四次交互后,TCP连接才完全释放。这种双向确认机制确保了连接的可靠终止。

步骤 报文标志 序列号/确认号 发送方状态 语义
1 FIN=1 Seq=u FIN_WAIT_1 主动方请求关闭「本方→对方」通道
2 ACK=1 Ack=u+1 CLOSE_WAIT 被动方确认收到关闭请求
3 FIN=1, ACK=1 Seq=v, Ack=u+1 LAST_ACK 被动方关闭「对方→本方」通道
4 ACK=1 Ack=v+1 TIME_WAIT 主动方确认收到关闭请求

第二、三次挥手不能合并:被动方(如服务器)在收到 FIN 后,可能仍有数据要发送,必须等数据发完才能发送自己的 FIN。(这个是要注意的点!!!)

2、为什么是四次?

  • TCP采用全双工通信机制,这意味着建立连接时需要建立双向通信通道,断开连接时也同样需要分别关闭两个方向的连接。

  • 四次挥手过程中,每两次挥手对应关闭一个方向的通信信道。每个方向的关闭需“请求 + 确认”,共 2 × 2 = 4 次。

  • 特别需要注意的是,第二次和第三次挥手不能合并。

  • 这是因为:当服务器收到客户端的断开请求(第一次挥手)并响应(第二次挥手)后,可能仍需向客户端传输剩余数据。只有在完成数据传输后,服务器才会主动发起断开连接的请求(第三次挥手)。

  • 这种设计确保了数据传输的完整性,避免因强制合并挥手而导致的数据丢失问题。

3、四次挥手的状态变化

四次挥手过程的状态转换如下:

初始状态下,客户端和服务器均处于ESTABLISHED状态,表示连接已建立。

  1. 客户端为了与服务器断开连接主动向服务器发起连接断开请求,此时客户端的状态变为FIN_WAIT_1(客户端主动发起断开连接请求,发送FIN报文后进入FIN_WAIT_1状态);

  2. 服务器收到客户端发来的连接断开请求后对其进行响应,此时服务器的状态变为CLOSE_WAIT(服务器收到FIN报文后,回应ACK确认,进入CLOSE_WAIT状态);

  3. 当服务器没有数据需要发送给客户端的时,服务器会向客户端发起断开连接请求,等待最后一个ACK到来,此时服务器的状态变为LASE_ACK(当服务器完成数据发送后,发送自己的FIN报文,进入LAST_ACK状态)

  4. 客户端收到服务器发来的第三次挥手后,会向服务器发送最后一个响应报文,此时客户端进入TIME_WAIT状态(客户端收到FIN报文后,发送最终ACK确认,进入TIME_WAIT状态)

        服务器收到最终ACK后立即关闭连接,转为CLOSED状态。客户端则需等待2MSL(Maximum Segment Lifetime,报文最大生存时间)后才会完全关闭连接(进入CLOSED状态)。至此,四次挥手过程完成,双方连接成功断开。

4、套接字 API 与四次挥手

  • 客户端主动断开连接时,会调用close函数发起请求。同理,服务器主动断开连接也需要调用close函数

  • 每个 close(fd) 触发两次挥手

  • 双方都需调用 close() 才能完成四次挥手

  • 若一方不调用 close(),连接将卡在中间状态


六、连接管理中的两大陷阱

1、CLOSE_WAIT:服务器的“隐形杀手”

  • 在TCP四次挥手过程中,若仅客户端调用close函数而服务器未调用,服务器将进入CLOSE_WAIT状态,客户端则进入FIN_WAIT_2状态。

  • 只有当四次挥手完整完成后,连接才会真正断开,双方才能释放相关资源。若服务器未主动关闭闲置的文件描述符,将导致大量CLOSE_WAIT状态的连接堆积。每个这样的连接都会持续占用服务器资源,最终造成可用资源逐渐耗尽。

  • 未能及时关闭文件描述符不仅会导致文件描述符泄漏,还可能引发连接资源未完全释放的问题,这本质上也是一种内存泄漏现象。

  • 在编写网络套接字程序时,若发现服务器存在大量CLOSE_WAIT状态的连接,应立即检查是否因服务器未及时调用close函数关闭对应的文件描述符所致。

1. 问题根源

  • 客户端调用 close() 发送 FIN

  • 服务器收到后进入 CLOSE_WAIT

  • 但服务器应用未调用 close(),导致无法发送自己的 FIN

2. 后果

  • 连接长期处于 CLOSE_WAIT

  • 文件描述符和内存无法释放

  • 最终耗尽系统资源(文件描述符泄漏 + 内存泄漏)

3. 诊断与解决

# 查看 CLOSE_WAIT 数量
netstat -an | grep CLOSE_WAIT | wc -l

  • 代码层面:确保所有连接路径都有 close()

  • 架构层面:设置连接空闲超时,自动清理

  • 监控层面:对 CLOSE_WAIT 数量设置告警阈值

2、TIME_WAIT:主动关闭方的“等待代价”

四次挥手过程中丢包的处理机制:

  • 第一次挥手丢包:客户端触发超时重传机制

  • 第二次挥手丢包:客户端触发超时重传机制

  • 第三次挥手丢包:服务器触发超时重传机制

  • 第四次挥手丢包:服务器触发超时重传机制

1. 为何需要 TIME_WAIT?

1. 确保最后一个 ACK 可靠到达

  • 若 ACK 丢失,服务器会重传 FIN

  • TIME_WAIT 状态可正确响应,避免服务器长时间卡在 LAST_ACK

2. 防止旧连接数据干扰新连接

  • 等待 2MSL 确保网络中所有旧报文消失

  • 避免新连接收到上一个连接的延迟数据包

TIME_WAIT状态的作用: 若客户端在第四次挥手后立即进入CLOSED状态,当服务器重传FIN报文时将无法获得响应。此时服务器需要持续维护无效连接直至超时关闭,这对服务器资源造成浪费。

服务器在经过若干次超时重发后得不到响应,最终也一定会将对应的连接关闭,但在服务器不断进行超时重传期间还需要维护这条废弃的连接,这样对服务器是非常不友好的。

为了避免这种情况,因此客户端在四次挥手后没有立即进入CLOSED状态,而是进入到了TIME_WAIT状态进行等待,此时要是第四次挥手的报文丢包了,客户端也能收到服务器重发的报文然后进行响应。

TIME_WAIT状态存在的必要性:

  • 客户端在进行四次挥手后进入TIME_WAIT状态,如果第四次挥手的报文丢包了,客户端在一段时间内仍然能够接收服务器重发的FIN报文并对其进行响应,能够较大概率保证最后一个ACK被服务器收到。

  • 客户端发出最后一次挥手时,双方历史通信的数据可能还没有发送到对方。因此客户端四次挥手后进入TIME_WAIT状态,还可以保证双方通信信道上的数据在网络中尽可能的消散。

        实际第四次挥手丢包后,可能双方网络状态出现了问题,尽管客户端还没有关闭连接,也收不到服务器重发的连接断开请求,此时客户端TIME_WAIT等若干时间最终会关闭连接,而服务器经过多次超时重传后也会关闭连接。这种情况虽然也让服务器维持了闲置的连接,但毕竟是少数,引入TIME_WAIT状态就是争取让主动发起四次挥手的客户端维护这个成本。因此TCP并不能完全保证建立连接和断开连接的可靠性,TCP保证的是建立连接之后,以及断开连接之前双方通信数据的可靠性。

2. TIME_WAIT 时长

TIME_WAIT状态的持续时间需要合理设置,过长或过短都会产生问题。

  • 如果等待时间过长:连接方需要持续维护TIME_WAIT状态、这会消耗额外的系统资源、造成不必要的资源浪费

  • 如果等待时间过短:无法确保对方收到最后的ACK、网络中的数据包可能尚未完全消散、TIME_WAIT状态将失去其设计意义

根据TCP协议规范:主动关闭连接的一方在完成四次挥手后、必须保持TIME_WAIT状态两个MSL(报文最大生存时间)、之后才能进入CLOSED状态

MSL标准说明:RFC1122建议值为2分钟、实际实现因操作系统而异、Linux默认设置为60秒、可通过命令cat /proc/sys/net/ipv4/tcp_fin_timeout查看当前值

设置2MSL时长的原因:确保双向传输中延迟或丢失的报文完全消失、理论上保证最后一个报文能可靠到达对端

  • RFC 标准:2 × MSL(Maximum Segment Lifetime)

  • 实际值

    • RFC1122 建议 MSL = 120 秒 → TIME_WAIT = 240 秒

    • Linux 默认 MSL = 60 秒 → TIME_WAIT = 120 秒

    • 可通过 cat /proc/sys/net/ipv4/tcp_fin_timeout 查看

3. 优化策略(针对高并发短连接场景)

# 允许重用 TIME_WAIT 状态的 socket(安全)
sysctl -w net.ipv4.tcp_tw_reuse=1

# 启用快速回收(Linux 4.12+ 已废弃,慎用)
# sysctl -w net.ipv4.tcp_tw_recycle=0

更根本的方案:使用 HTTP Keep-Alive、连接池、长连接减少连接创建/关闭频率


七、总结:连接管理的核心思想

机制 设计目标 工程启示
面向连接 会话隔离 + 可靠性基础 高并发需优化连接管理
三次握手 验证双向通信 + 防历史连接 + 保护服务器 服务器应防御 SYN Flood
四次挥手 优雅关闭 + 数据完整性 应用必须正确调用 close()
CLOSE_WAIT 被动关闭的中间状态 服务器代码必须及时 close
TIME_WAIT 保证最后 ACK + 清理网络残留 主动关闭方承担清理成本

终极认知:TCP 的连接管理机制,是在不可靠网络上构建可靠通信的工程杰作。它通过精巧的状态机、超时机制和风险转移策略,在效率、可靠性与资源消耗之间取得了卓越平衡。理解这些机制,是编写高性能、高可用网络服务的基础。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐