计算机网络八股3


📚 TCP 三次握手(Three-Way Handshake)

TCP 三次握手是建立可靠连接的过程,它确保了客户端与服务器之间的通信可以同步进行。每一次握手的作用是确认双方都已准备好进行数据传输。

三次握手流程

1. 第一次握手:客户端发送 SYN 请求

  • 动作:客户端向服务器发送一个包含 SYN 标志的 TCP 报文段,表示希望建立连接。报文段中还会包含一个初始序列号 x,用于后续的数据传输。
  • 目的:客户端通知服务器它希望建立连接,并告知服务器自己的初始序列号。
  • 客户端状态:进入 SYN_SENT 状态。

2. 第二次握手:服务器回应 SYN + ACK

  • 动作:服务器收到客户端的 SYN 请求后,若同意建立连接,服务器会回复一个带有 SYN 和 ACK 标志的报文段。服务器会选择一个随机序列号 y,并将客户端的序列号 x 加 1 作为确认号(x + 1),返回给客户端。
  • 目的:服务器确认收到客户端的连接请求,并通知客户端服务器自己的初始序列号。
  • 服务器状态:进入 SYN_RCVD 状态。

3. 第三次握手:客户端发送 ACK 确认

  • 动作:客户端收到服务器的 SYN + ACK 后,向服务器发送一个确认报文段。这个报文段的 ACK 位被设置为 1,确认号设置为服务器的序列号 y + 1,并且自己的序列号设置为 x + 1
  • 目的:客户端确认服务器的响应,完成连接建立。
  • 客户端状态:进入 ESTABLISHED 状态;此时服务器收到客户端的 ACK 后也进入 ESTABLISHED 状态。

总结:

  1. 客户端 -> 服务器:SYN, seq = x
  2. 服务器 -> 客户端:SYN, ACK, seq = y, ack = x + 1
  3. 客户端 -> 服务器:ACK, seq = x + 1, ack = y + 1

连接建立后,客户端和服务器可以开始数据传输。


📚 大白话解释:

TCP 三次握手的过程类似于两个人进行确认后才开始对话:

  1. 第一次握手:发起连接 假设你和某人要开始谈话,但由于嘈杂的环境,你想确认对方是否能听到你。你首先大声问:“嗨,你听得到我说话吗?”这就像客户端发送 SYN 请求,表明你想开始交流,并提供了自己的“起点”(初始序列号)。
  2. 第二次握手:对方回应 对方听到你的声音后,回应:“是的,我听到了。我也准备好了。”这个回应就像服务器发送 SYN + ACK 报文,确认双方都准备好开始对话,并告诉你它的“起点”(服务器序列号)。
  3. 第三次握手:最终确认 为了确保对方的回应听清楚,你再回应:“太好了,我确认了你准备好了,我们可以开始交流了!”这个回应就是客户端发送的 ACK 确认,双方都确认了对方的状态。
  4. 开始交流 在双方都确认准备好之后,你们可以开始正式的交流了。

📚 SYN 的概念与作用:

SYN 标志位

  • 定义:SYN 是 TCP 协议中用来建立连接的一个标志位,全称为 Synchronize Sequence Numbers(同步序列号)。它用于告诉对方当前连接的同步请求,是三次握手过程中的关键。

作用:

  • 同步序列号:SYN 用来标记初次请求建立连接时的同步信息。它告诉对方,连接请求的序列号需要同步,为之后的可靠数据传输做准备。
  • 防止旧报文干扰:SYN 位的使用能够有效防止旧的报文段误被当作新连接的请求。例如,在网络中,若以前的连接报文仍未消失,SYN 位可确保新的连接请求与旧的报文段区分开来,避免数据混乱。

示意图

1
2
3
4
5
6
7
8
9
客户端               服务器
| |
|---- SYN (x) -------->|
| |
|<---- SYN + ACK (y, x+1) |
| |
|---- ACK (x+1, y+1) --->|
| |
| 连接建立 |

📚 实际例子:

假设你是客户(客户端),想和某个企业代表(服务器)讨论合作事宜。为了确认彼此都准备好了并且愿意交流,按照以下步骤进行:

  1. 第一次握手: 你向代表问候:“您好,我是客户小张,我们可以谈谈合作吗?”(这是你发出的连接请求)
  2. 第二次握手: 代表听到你的话后,确认:“你好,我是企业代表小李,我也准备好了。”(这是代表同意建立连接,并告知自己的序列号)
  3. 第三次握手: 为了确认对方的回应,你说:“太好了,我确认你准备好了,咱们可以开始讨论合作事宜。”(你确认对方的回应并准备好开始对话)
  4. 开始讨论: 一旦确认完毕,双方就可以开始正式讨论合作事宜,数据交流正式开始。

小结:

  • 三次握手 是为了确保双方都能接收到对方的连接请求,并且都准备好开始数据传输。
  • SYN 标志位 确保了序列号同步,防止无效或重复连接请求干扰数据传输。

TCP 三次握手为什么是三次,为什么不能是两次或四次?

TCP 三次握手(Three-Way Handshake)是用于建立可靠连接的机制。其目的是确保双方能够同步序列号并确认双方都准备好通信。三次握手的设计有其特定的原因和背景,下面详细解析三次握手的必要性及为什么不能是两次或四次。


为什么是三次握手?

TCP 三次握手的设计确保了以下几个重要目标:

  1. 确保客户端和服务器都准备好建立连接
    • 第一次握手,客户端通过发送 SYN 包告诉服务器它希望建立连接。服务器收到这个请求后知道客户端准备好了。
    • 第二次握手,服务器确认收到连接请求,并告诉客户端它也准备好了。
    • 第三次握手,客户端确认接收到服务器的回应,确保双发双方都可以进行通信。
  2. 序列号同步
    • 在 TCP 中,每个数据包都有一个序列号,用来保证数据的顺序传输。
    • 第一次握手中,客户端通过 SYN 包提供自己的序列号;第二次握手中,服务器提供自己的序列号并确认客户端的序列号;第三次握手中,客户端再次确认服务器的序列号,确保双方都同步了序列号。

为什么不能是两次握手?

如果 TCP 握手只进行两次,会存在以下潜在问题:

  1. 防止丢包引起的连接问题
    • 假设客户端发送 SYN 包给服务器后,服务器立即响应 SYN-ACK 包。如果这个 ACK 包丢失了,服务器就无法知道客户端是否已经收到了它的回应,服务器就会继续等待客户端发送 ACK 包。
    • 如果没有第三次握手,服务器可能会一直保持等待状态,即使客户端实际上没有收到确认。这样,客户端和服务器之间就没有可靠的同步信息,连接就可能无法正常建立。
  2. 防止无效连接的建立
    • 如果没有第三次握手,客户端在没有确认服务器响应的情况下就开始数据传输,可能会导致错误的连接被建立。特别是在网络延迟较大的情况下,丢失的报文可能导致错误连接的生成。
  3. 丢失请求后的重传机制
    • 通过三次握手,客户端和服务器都有机会确认数据包的发送和接收。如果是两次握手,客户端和服务器只能在第一次和第二次握手后假设连接建立完毕,没有进一步的确认,这样可能会出现连接没有实际建立的情况。

为什么不能是四次握手?

三次握手已经足够建立可靠连接,而四次握手并不会带来额外的好处,反而增加了复杂度和不必要的开销。具体原因如下:

  1. 四次握手没有额外的实际用途
    • 三次握手已经确保了客户端和服务器能够同步其初始序列号,并且双方都能确认彼此准备好建立连接。第四次握手会造成不必要的延迟,并且不会增加协议的可靠性。
    • 因此,增加第四次握手不仅浪费资源,还会导致连接建立的延迟增大,不符合高效传输的需求。
  2. 三次握手已能确认连接的双方准备好通信
    • 第三次握手完成时,双方已经知道对方准备好,且同步了序列号。增加第四次握手不会改善连接质量,反而增加了协议的复杂性。

总结:为什么是三次握手?

  • 三次握手 确保了双方都已经准备好,并且在数据传输前成功同步了序列号。它通过防止丢包、重传、以及未确认的连接请求等问题,确保了可靠的连接建立。
  • 两次握手 不足以保证连接的可靠性,可能会导致丢包或连接错误。
  • 四次握手 不必要,三次握手已足够,增加额外的握手次数只会增加延迟和开销。

TCP 握手与“泛洪攻击”

在某些情况下,TCP 三次握手机制可能会被恶意攻击者利用,进行 SYN Flood(泛洪攻击),即发送大量伪造的 SYN 包,导致服务器资源耗尽,无法处理正常的连接请求。

SYN Flood 攻击

  1. 攻击者伪造大量 SYN 包发送到目标服务器,导致服务器响应并为每个 SYN 包分配资源(为每个半开连接预留资源)。
  2. 由于攻击者不会完成第三次握手,服务器会在半连接队列中保持这些无效的连接,浪费服务器资源。
  3. 这种攻击会导致服务器无法处理正常的请求,最终导致 拒绝服务(DoS)

解决方案

  • SYN Cookies:使用 SYN Cookies 技术,通过对 SYN-ACK 包编码连接信息,服务器不再为每个连接分配资源,直到客户端完成第三次握手,从而防止资源被消耗掉。
  • 半连接队列长度限制:控制半连接队列的长度,防止半连接被过度占用。

重新设计 TCP 连接建立过程:

如果重新设计 TCP 连接建立过程,以下几点可以考虑:

  1. SYN Cookies:如上所述,SYN Cookies 技术能够减少资源占用,防止 SYN Flood 攻击,保证服务器在不增加额外开销的情况下能处理大量连接请求。
  2. 增加验证机制:可以设计更高级的验证机制,在握手过程中使用更复杂的加密方式来防止伪造 SYN 请求。
  3. 连接预确认:考虑在三次握手之前,进行一个简单的双向确认,以确保双方都能可靠地接收到对方的信息。

结论

TCP 三次握手是为了建立可靠连接,防止丢包和冗余连接请求的产生。两次握手无法保证双方都准备好,四次握手则没有必要。因此,三次握手既高效又确保了连接的可靠性。

TCP 三次握手过程中的超时与重传机制

在 TCP 三次握手过程中,每一步如果没有收到对方的报文,都会导致重传机制的启动,直到超时或达到最大重试次数。以下是每次握手中未收到报文时发生的情况:


第一次握手:服务端未收到 SYN 报文

  • 客户端发送 SYN 包:客户端发起连接请求,发送 SYN 包给服务端,等待服务端的响应。
  • 服务端未收到 SYN 包
    • 如果服务端没有收到客户端的 SYN 包(可能因为网络丢包等原因),它不会做出任何响应。
    • 客户端的反应:客户端会在一定的超时时间后重传 SYN 包。这个过程会重复,直到客户端收到服务端的 SYN-ACK 包或者超过最大重传次数。超时后,客户端会返回连接失败的错误。

结论:第一次握手没有成功时,客户端会重传 SYN 包,直到连接超时或达到最大重试次数。


第二次握手:客户端未收到 ACK 报文

  • 服务端响应客户端的 SYN 包:服务端收到客户端的 SYN 包后,发送 SYN-ACK 包确认客户端的请求,并告知自己也准备好建立连接。
  • 客户端未收到 ACK 包
    • 如果客户端没有收到服务端的 ACK 包(可能是因为 ACK 包丢失),它会继续重传 SYN 包,直到接收到 ACK 或超时。
    • 服务端的反应:此时,服务端会在 accept() 系统调用处阻塞,等待客户端发来的 ACK 包,直到连接建立完成。
    • 客户端的超时机制:如果超过最大重试次数,客户端会放弃连接,并返回连接建立失败的错误。

结论:第二次握手未收到 ACK 时,客户端会继续重传,服务端在 accept() 阻塞,等待客户端完成连接确认。


第三次握手:服务端未收到 ACK 报文

  • 客户端发送 ACK 包:客户端收到服务端的 SYN-ACK 后,发送最终的 ACK 包,完成连接建立的最后一步。
  • 服务端未收到客户端的 ACK 包
    • 如果服务端未收到客户端发送的 ACK 包(可能因为 ACK 包丢失),服务端会使用超时重传机制,重试发送 SYN-ACK 包。
    • 客户端的反应:此时客户端已经认为自己已经建立了连接,并开始尝试向服务端发送数据。
    • 服务端的处理:如果服务端在 accept() 阻塞状态下未收到客户端的 ACK 包,会继续等待,直到超时。如果重试超过最大次数,服务端会返回错误并关闭连接。
    • 客户端的异常行为:由于客户端没有收到服务端的确认,客户端仍认为连接已成功建立,会向服务端发送数据。此时,服务端可能已不在监听状态,接收到数据时会发送 RST(重置)包给客户端,拒绝数据传输并终止连接。

结论:如果第三次握手失败,客户端和服务端之间会出现连接失败的情况,服务端会拒绝客户端的请求并发送 RST 包。


关于第二次握手:为什么需要同时传回 ACK 和 SYN?

第二次握手的 SYN 和 ACK 包作用:

  1. ACK(确认)
    • 服务器发送的 ACK 包是为了确认客户端发送的 SYN 包(连接请求)已经成功接收。ACK 通过设置确认号字段(ACK=客户端发送的序列号+1)告知客户端,服务端已接收到客户端的请求。
  2. SYN(同步)
    • 同时,服务器还会发送 SYN 包,表示服务端也准备好建立连接。由于服务端需要同步自己的初始序列号,它必须发送一个 SYN 包给客户端,告知客户端服务端准备就绪,并且发送自己的初始序列号。

因此,第二次握手需要传回 SYN 和 ACK:

  • SYN:表示服务端希望建立连接。
  • ACK:确认收到客户端的 SYN 包。

这两者合在一起,标志着双方都已准备好建立连接,并开始数据传输。


第三次握手可以携带数据吗?

第三次握手是否可以携带数据?

  • 可以携带数据:第三次握手是可以携带数据的。当第三次握手完成时,连接已经建立,客户端和服务器可以交换数据。
    • 客户端的状态:客户端此时已经进入 ESTABLISHED(已建立)状态,连接建立完成,确认双方的接收和发送能力都是正常的,可以开始正常的数据传输。
  • 第一次握手不携带数据的原因
    • 第一次握手使用 SYN 包进行连接请求时,不能携带数据。主要是出于 安全考虑,因为如果允许在 SYN 包中携带数据,恶意攻击者可能利用此特性发送大量数据,导致服务器在处理时消耗大量的 CPU 和内存资源,影响服务器的正常运行。

结论:第三次握手可以携带数据,因为此时连接已经建立,并且双方已经准备好交换数据。


总结:

  1. 第一次握手:客户端发送 SYN 包,服务端收到后进行响应。如果服务端未收到 SYN 包,客户端会重试,直到最大重试次数或连接失败。
  2. 第二次握手:服务端回应 SYN-ACK 包,客户端收到后发送 ACK 确认。如果客户端未收到 ACK 包,会继续重传。
  3. 第三次握手:客户端确认服务端的响应后发送 ACK,连接建立成功。如果第三次握手失败,客户端和服务端之间会出现连接错误,服务端会发送 RST 包。

SYN 和 ACK 的双重作用在每一步确保了双方正确同步并确认连接的建立,而第三次握手在连接建立后可以携带数据。

TCP 半连接状态(Half-Open Connection)

TCP 半连接指的是在 TCP 三次握手的过程中,服务端已接收到客户端的 SYN 包并发送了 SYN-ACK 包,但客户端尚未发送确认的 ACK 包,连接处于“半开”状态。此时的连接还没有完全建立,虽然服务端已经做出了响应,但还没有收到客户端的最终确认。

半连接队列(SYN 队列)

在三次握手过程中,服务端在收到客户端的 SYN 包后,会将连接放入半连接队列(SYN 队列)。此时,服务端处于 SYN_RCVD 状态,表示它已经收到客户端的连接请求并回复了 SYN-ACK 包。

  • 半连接队列 存放那些已经发送 SYN-ACK 但还没有收到最终 ACK 的连接。
  • 在收到客户端的 ACK 包后,连接才会被移至 全连接队列(ACCEPT 队列),并完成三次握手。

三次握手过程中的队列作用

  • LISTEN 状态:服务端准备好接受连接,此时不会有任何连接建立。
  • SYN 队列(半连接队列):当服务端收到客户端的 SYN 包并回复 SYN-ACK 时,连接处于半连接状态,放入半连接队列。
  • ACCEPT 队列(全连接队列):当客户端收到服务端的 SYN-ACK 后,回复 ACK,完成三次握手,连接进入全连接队列,等待应用程序处理。

为什么半连接队列很重要?

  • 资源分配:半连接队列用于暂存那些正在建立中的连接。在此队列中,服务端为连接分配了资源,但这些连接尚未完全建立,无法用于数据交换。
  • 限制队列大小:系统通常对半连接队列大小有一定限制,若队列满,新的连接请求将被拒绝。这样会防止服务器在处理正常请求的同时,耗尽资源。

SYN Flood 攻击

SYN Flood 是一种典型的拒绝服务(DoS)攻击,攻击者发送大量伪造的 SYN 请求到目标服务器,但不响应服务器的 SYN-ACK 包,导致服务器的半连接队列(SYN 队列)被填满,无法处理正常的连接请求,从而导致服务不可用。

  • 攻击过程
    1. 攻击者伪造源 IP 地址,向目标服务器发送大量的 SYN 包。
    2. 服务器收到 SYN 包后,回复 SYN-ACK 包(即第三次握手的第二步)。
    3. 攻击者并不回复 ACK,导致服务器的半连接队列中的连接无法完成三次握手。
    4. 服务器资源消耗殆尽,无法再处理来自合法客户端的连接请求,导致服务器崩溃或拒绝服务。
  • 攻击影响:这种攻击会造成服务器资源被耗尽,使得无法为正常用户提供服务。

SYN Flood 攻击的应对方案

  1. SYN Cookies
    • 原理:SYN Cookies 是一种防止 SYN Flood 攻击的技术。当服务器接收到 SYN 包时,它不会立即分配资源,而是根据客户端的 IP 地址、端口号等信息计算出一个唯一的 cookie 值,并将该值作为序列号发送 SYN-ACK 包。
    • 过程
      • 服务器收到 SYN 包后,回复 SYN-ACK 包并携带计算出的 cookie 值。
      • 只有当客户端回复 ACK 时,服务器才能根据该 ACK 包的序列号验证是否与计算出的 cookie 一致,若一致才真正建立连接。
      • 如果不一致,服务器会丢弃该连接请求。
    • 优势:通过 SYN Cookies,可以避免攻击者占用过多的资源,因为服务器没有为每个连接分配资源,只有在验证 ACK 包时才会实际分配资源。
  2. SYN Proxy 防火墙
    • 原理:SYN Proxy 是一种防火墙技术,能够代理和拦截 SYN 包,伪造响应包并缓存连接状态,直到客户端回复正确的 ACK 包,才真正建立连接。
    • 过程
      • 防火墙接收到 SYN 请求后,不直接将其转发给目标服务器,而是与客户端建立自己的连接,并伪造一个 SYN-ACK 包返回给客户端。
      • 客户端完成三次握手后,防火墙将连接信息传递给目标服务器,完成真正的 TCP 连接建立。
    • 优势:通过 SYN Proxy,防火墙能有效地过滤掉恶意的 SYN Flood 攻击,只允许合法的连接请求通过。

Java 面试中的 TCP 半连接问题

在 Java 面试中,关于 TCP 半连接状态的问题,可能会询问服务器如何管理连接、半连接队列的作用、以及如何防止 SYN Flood 攻击等。可以根据以下思路回答:

  • 半连接状态:在三次握手的第二步,服务器收到客户端的 SYN 包并回复 SYN-ACK,连接处于半连接状态,等待客户端的 ACK。此时连接被存储在半连接队列中。
  • 半连接队列大小限制:半连接队列的大小通常是有限制的,若队列已满,后续的连接请求会被拒绝或丢弃。因此,服务器需要合理配置半连接队列大小。
  • SYN Flood 防御:为了防止 SYN Flood 攻击,服务器可以使用 SYN Cookies 或者 SYN Proxy 防火墙技术。这些技术能有效保护服务器免受 DoS 攻击,同时确保正常用户的连接请求能够被处理。

总结

  • TCP 半连接 是指在三次握手过程中,服务端已经收到客户端的 SYN 包并回复 SYN-ACK,但还没有收到客户端的 ACK 包,连接处于半连接队列中。
  • 半连接队列(SYN 队列) 存放这些未完成的连接,直到客户端完成最后的 ACK 才能转移到全连接队列。
  • SYN Flood 攻击 通过伪造大量的 SYN 请求,占用服务器的半连接队列资源,导致合法请求无法得到处理。
  • 应对方案:SYN Cookies 和 SYN Proxy 防火墙能够有效防止 SYN Flood 攻击,保护服务器的资源。

这种情况的理解和应对方案是 TCP 网络编程和安全领域的重要知识点,尤其是在大规模分布式系统中非常关键。

TCP 四次挥手过程(Four-Way Handshake)

TCP 连接的断开过程使用了四次挥手(Four-Way Handshake)来确保连接的正确终止。在这个过程中,客户端和服务器必须相互确认各自的关闭请求,以保证双方都知道连接已经完全断开。

详细过程

  1. 第一次挥手:客户端发送 FIN 报文
    • 客户端发送 FIN(Finish)报文,表示客户端没有数据要发送了,但仍然可以接收数据。客户端进入 FIN-WAIT-1 状态,等待服务器的确认。
    • 解释:这时客户端像服务器宣布,它已经没有数据要发了,但还没完全结束连接,它仍然会接收服务器的数据。
  2. 第二次挥手:服务器确认接收 FIN 报文
    • 服务器收到 FIN 报文后,发送 ACK(Acknowledgment)报文来确认客户端的关闭请求,进入 CLOSE-WAIT 状态,表示已经准备好断开连接。
    • 解释:服务器确认收到了客户端发来的断开请求,服务器暂时还没有完全关闭连接,它可能需要先发送一些剩余的数据。
  3. 第三次挥手:服务器发送 FIN 报文
    • 当服务器确认所有的数据都已发送完后,服务器会向客户端发送 FIN 报文,表示它也没有数据要发送了,准备关闭连接。此时服务器进入 LAST-ACK 状态,等待客户端的确认。
    • 解释:服务器发送 FIN 包,通知客户端它已经完成数据的发送,并请求关闭连接。
  4. 第四次挥手:客户端确认接收 FIN 报文
    • 客户端接收到服务器发送的 FIN 报文后,回复一个 ACK 报文,表示客户端确认服务器的关闭请求。此时客户端进入 TIME-WAIT 状态,等待一段时间以确保服务器接收到 ACK 报文。客户端之后进入 CLOSED 状态。
    • 服务器接收到 ACK 报文后,进入 CLOSED 状态,连接完全断开。

总结

  • 四次挥手的目的是保证双方都知道连接已经完全关闭,防止数据丢失或者在连接未完全关闭的情况下就开始新的连接。
  • 在整个过程中,客户端和服务器都会经历不同的状态,确保在数据传输和关闭连接时没有遗漏。

四次挥手在实际应用中的意义

  1. 防止数据丢失:通过确保每一方都发送和确认了断开请求,可以防止在连接未完全关闭时发送数据,从而避免数据丢失。
  2. 确保连接彻底关闭:每次挥手都确认了另一方的状态,确保没有一方在还未完全准备好的情况下关闭连接。
  3. TIME-WAIT 状态:客户端的 TIME-WAIT 状态保证客户端能够确认服务器是否已经收到最后的 ACK 报文,防止旧的重复数据包影响新的连接。

TCP 四次挥手的总结

  • TCP 连接的断开并不是瞬间的,它需要四次挥手的过程,每一次都需要确认,确保双方都知道连接即将关闭。
  • 这个过程包括了客户端的发起、服务器的确认,双方分别发送和确认关闭请求,最后确保没有数据丢失,并彻底关闭连接。

这四次挥手的过程在网络编程和服务端开发中非常重要,它保证了数据连接的安全关闭,并防止资源浪费。


为什么 TCP 挥手需要四次?

🎯 核心答案:

因为 TCP 是全双工通信协议full-duplex),双方的关闭是 独立进行的,发送和接收都必须分别关闭,因此需要 “你关你的,我关我的” —— 所以需要 四次挥手来完成连接的彻底断开。


🧠 分析 TCP 四次挥手的原因

🔁 全双工通信的含义:

TCP 连接是双向的(你能发我也能发),所以:

  • 一端“说我不发了”(用 FIN 报文)只能关闭自己发送数据的通道;
  • 另一端还可能有数据要发,它得“自己说”自己也不发了。

所以:

每个方向都需要发送一个 FIN 和接收一个 ACK,加起来就是四次挥手。


📶 握手与挥手对比

项目 三次握手 四次挥手
目的 建立连接 断开连接
传输方向 双向一次完成 每个方向单独关闭
是否可以合并 客户端 SYN 和 ACK 可合并(2次变3次) 两个 FIN 必须分开发出(不能合并)

📝 四次挥手流程详解

1️⃣ 第一次挥手:客户端发送 FIN

  • 客户端告诉服务端:我没有数据要发了,但还能收。
  • 客户端进入:FIN-WAIT-1 状态。

2️⃣ 第二次挥手:服务端回复 ACK

  • 服务端确认收到 FIN,说:“我知道你要关闭了”。
  • 服务端进入:CLOSE-WAIT,客户端进入 FIN-WAIT-2

此时:

  • 客户端不再发送数据,但仍接收服务端的数据。
  • 服务端继续处理剩下的事,并可能继续发数据。

3️⃣ 第三次挥手:服务端发送 FIN

  • 服务端也发完数据,发送 FIN 报文,表示也要关闭连接。
  • 服务端进入:LAST-ACK 状态。

4️⃣ 第四次挥手:客户端回复 ACK

  • 客户端接收 FIN,回复 ACK。
  • 客户端进入 TIME-WAIT,等待一段时间(2MSL),确保服务端收到 ACK 后,最终关闭连接。

🔄 四次挥手状态图简要:

1
2
3
4
5
6
客户端                      服务端
FIN-WAIT-1 --> CLOSE-WAIT
FIN-WAIT-2 <-- ACK
(接收服务端数据)
TIME-WAIT <-- FIN LAST-ACK
CLOSED --> ACK CLOSED

🧾 举个大白话例子(自己版本)

你和你的朋友在打语音电话聊天:

  1. 你说:“我没什么好说的了,我要挂电话了。”(第一次挥手,FIN)
  2. 朋友说:“好的,我知道了,但我还没说完,你听我说完。”(第二次挥手,ACK)
  3. 朋友说完后说:“我现在也说完了,我们可以挂电话了。”(第三次挥手,FIN)
  4. 你说:“收到,我确认你也说完了,我们正式挂电话。”(第四次挥手,ACK,进入 TIME-WAIT)

⏲️ TIME-WAIT 为什么存在?

客户端需要等待一段时间(通常是 2 倍 MSL)是为了防止:

  • 服务端没收到最后一个 ACK,再次重发 FIN;
  • 客户端能重新发出 ACK,确保对方彻底关闭连接。

✅ TCP 四次挥手为什么要等待 2MSL 才进入 CLOSED?


📌 什么是 MSL?

MSL(Maximum Segment Lifetime):报文段在网络中最大生存时间,超过这个时间还没被接收,报文将被丢弃。

  • 由操作系统决定,常见系统中 MSL 约为 30 秒或 60 秒;
  • 所以 2MSL 通常是 60s 或 120s

🧠 为什么要等待 2MSL?——两个核心原因:


🟡 1. 确保对方收到了最后的 ACK

  • 在第四次挥手中,客户端(主动关闭方)发出 ACK 后,状态变为 TIME_WAIT
  • 但万一这个 ACK 在网络中丢失服务端(被动关闭方)就会重发它的 FIN 报文
  • 如果客户端立即关闭连接,就 无法响应这个重传的 FIN —— 这会导致服务端 无法正常进入 CLOSED 状态,连接无法正确释放。
  • 因此,客户端必须保留一段时间(2MSL)来 等待可能的 FIN 重传,并 能及时重发 ACK 报文

📌 一句话总结:为让服务端安心下线,客户端等一等,确保 ACK 不会丢!


🟡 2. 避免旧连接的报文污染新连接

  • 假设你立即关闭连接并新建了一个相同四元组(源 IP、源端口、目的 IP、目的端口)的连接。
  • 如果旧连接中的报文(如旧的 FIN 或数据包)还滞留在网络中,就可能被误当作新连接的有效数据处理。
  • 这种“脏数据”进入新连接,会引起数据错乱或安全问题。

📌 等待 2MSL 就能确保旧连接的所有报文都“过期”,不再污染新的连接。


🔁 为什么是“2 倍” MSL 而不是 1 倍?

1 来 + 1 回 = 2MSL

  • 第一次:服务器重发的 FIN 从服务端到客户端,最多 MSL。
  • 第二次:客户端再次确认 ACK,从客户端到服务端,再用 MSL。
  • 所以一来一回:需要 2MSL 才最安全

🔚 TIME_WAIT 状态的关键意义

问题 解决方案
ACK 丢失,服务端重发 FIN? TIME_WAIT 中客户端还能重发 ACK
脏数据污染新连接? 等待 2MSL 让旧连接数据自然“过期”

✅ TCP 保活计时器(KeepAlive Timer)


📌 一、什么是 TCP 保活计时器?

TCP 保活计时器是一种用于检测对端是否仍然存活的机制。

  • 它不是 TCP 协议强制要求的部分,但大多数操作系统都实现了这一机制。
  • 如果长时间没有收到对方的数据,系统会通过发送 探测报文(keepalive probe) 来判断连接是否还有效。

📦 二、为什么需要保活计时器?

🔸 场景举例:

  • 客户端和服务器建立了 TCP 连接。
  • 客户端突然掉电或崩溃,无法主动发送 FIN 来断开连接
  • 此时服务器并不知道客户端已经不可用,还会一直保留这个连接,占用资源。

🧠 解决方案: 使用 保活计时器,主动检测对方是否“还活着”,否则主动关闭连接,避免资源浪费。


⚙️ 三、保活计时器的工作机制

  1. 每当收到一次对方的数据,就重置保活计时器。
  2. 如果设定时间(如 2 小时)内没有收到数据,就触发保活机制。
  3. 发送 探测报文(KeepAlive Probe) 给对方 —— 通常是一个带 ACK 标志、无数据负载的报文。
  4. 如果对方响应,说明还活着,重新启动计时器。
  5. 如果对方不响应,继续间隔发送探测包 —— 默认间隔:75 秒
  6. 连续发送 10 次探测报文都没有响应,则认定连接已断,主动关闭。

⏱️ 四、默认时间参数(以 Linux 为例)

参数 默认值 含义说明
tcp_keepalive_time 7200 秒(2 小时) 空闲多久开始发送保活探测包
tcp_keepalive_intvl 75 秒 探测包之间的间隔
tcp_keepalive_probes 10 次 最多发多少次探测包无响应就断开连接

💡 即最多 2 小时 + 9 × 75 秒 ≈ 2 小时 11 分钟后断开无响应连接


🧾 五、和 TIME_WAIT、FIN 等计时器的区别

名称 作用 属于哪一方主动发起
TIME_WAIT 等待报文老化、安全关闭连接 客户端最后一个 ACK 后
保活计时器 探测连接是否还活着 服务端主动探测
FIN 等 正常四次挥手中的超时重发控制 双方都可能使用

💡 六、使用保活的常见应用场景

场景 作用
长连接服务如 FTP、SSH 检测客户端是否断开
服务端资源有限时 避免僵尸连接占用 socket 资源
客户端连接无线网络或 NAT 设备 检测连接是否被 NAT 中断

✅ 七、总结一句话:

TCP 保活计时器是“主动心跳检测机制”,防止一方突然断开而不通知,确保连接不是僵尸连接。


✅ CLOSE-WAIT 和 TIME-WAIT 的状态与意义


🧩 一、CLOSE-WAIT 状态

🔹 1. 定义:

对方主动关闭连接(发送 FIN),本端接收到这个 FIN 报文,并发送 ACK 确认报文后,就会进入 CLOSE-WAIT 状态。

🔹 2. 出现场景:

  • 常出现在服务器端(服务端是被动关闭连接的一方)。
  • 表示“对方已经关闭了连接,但我还没关”。

🔹 3. 状态转换路径:

1
2
3
4
ESTABLISHED(连接建立) 
→ 对方发送 FIN
→ 本方接收并回 ACK
→ 进入 CLOSE-WAIT

🔹 4. 意义和作用:

  • 让本端有时间完成剩余数据的发送或清理工作,再主动关闭连接(即发送 FIN)。
  • 如果本端忘记调用 close(),可能长时间停留在 CLOSE-WAIT,造成资源泄露(连接泄漏)

🧠 重点记忆:

CLOSE-WAIT 是“对方说要断,我说等一下,我还有事没做”。


🧩 二、TIME-WAIT 状态

🔹 1. 定义:

当 TCP 连接关闭时,本端发送了最后一个 ACK(确认对方 FIN 报文),就进入 TIME-WAIT 状态。

🔹 2. 出现场景:

  • 常出现在主动关闭连接的一方(一般是客户端)。

🔹 3. 状态转换路径:

1
2
LAST-ACK ← 被动关闭方等待 ACK
→ 主动方收到对方 FIN,发出 ACK → 进入 TIME-WAIT

🔹 4. 意义和作用:

✅ TIME-WAIT 的两个主要目的:
  1. 保证被动关闭方收到最终 ACK 报文
    • 若 ACK 丢失,被动关闭方会重发 FIN。
    • 此时 TIME-WAIT 状态能重新发送 ACK。
  2. 防止旧连接的重复报文干扰新连接
    • 若新连接重复使用旧的 4 元组(源/目标 IP + 端口),旧报文段若未超时可能会误判为新连接数据。
    • 所以 TIME-WAIT 需等待 2×MSL 确保旧连接相关数据都消失。

🧾 三、TIME-WAIT 中的 MSL 补充知识

名称 含义
MSL(Maximum Segment Lifetime) 报文在网络中最长生存时间
通常取值 30 秒 ~ 2 分钟(Linux 默认 2 分钟)
TIME-WAIT 时间 2 × MSL,即一个来回传输

🔁 为什么是两倍?

  • 一个 MSL 是 ACK 发送出去的时间;
  • 一个 MSL 是对方重发 FIN 再次被我接收的时间。

🔧 四、CLOSE-WAIT 和 TIME-WAIT 常见问题对比

对比项 CLOSE-WAIT TIME-WAIT
出现在哪一方 被动关闭方(通常是服务端) 主动关闭方(通常是客户端)
是否已经收到对方 FIN ✅ 是 ✅ 是
是否已经发送 ACK ✅ 是 ✅ 是
是否已发送自己的 FIN ❌ 否,还没 ✅ 是
状态含义 等我处理完我再关闭连接 等我确认连接彻底关闭
持续时间 一般由程序控制 至少 2 × MSL(系统控制)
潜在风险 忘记调用 close 导致连接泄漏 太多 TIME_WAIT 占用端口

✅ TIME_WAIT 状态过多会导致什么问题?如何解决?


🧩 一、什么是 TIME_WAIT 状态?

当 TCP 连接由本地主动关闭(即发送了最后一个 ACK),会进入 TIME_WAIT 状态,保持 2 × MSL 时间,确保连接安全彻底关闭。


❗二、TIME_WAIT 状态过多带来的问题

✅ 1. 系统资源浪费(尤其是高并发下)

危害 描述
占用内核内存资源 每个 TCP 连接都要占用内核维护的连接表项(TCB 结构),TIME_WAIT 状态越多,占用越多
🎯 占用本地端口号 每个连接唯一由四元组 (源 IP+端口, 目标 IP+端口) 标识,TIME_WAIT 状态的连接仍占用本地端口
导致新连接失败 若短时间内快速建立断开连接,端口资源耗尽,报错如 “Address already in use”

🧪 三、常见场景

场景 特征
高频率的短连接 如 HTTP/1.0 每次请求一个 TCP 连接,极易出现大量 TIME_WAIT
客户端与服务端频繁通信 若服务端主动断开连接,将产生大量 TIME_WAIT
压测/调试时反复创建 TCP 连接 容易造成 TIME_WAIT 洪水

🧰 四、解决 TIME_WAIT 过多的方法(系统 + 应用层)


✅ 方法 1:设置 SO_REUSEADDR 套接字选项

  • 🔧 在程序中设置 SO_REUSEADDR,使得在 TIME_WAIT 状态下,端口可以被立即重用。
  • ✅ 适用于快速重启服务器、避免“地址已被占用”错误。
1
2
int optval = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

🚫 但不能完全避免 TIME_WAIT 的出现,只是允许在一定条件下重用端口。


✅ 方法 2:使用长连接(Keep-Alive)

  • 🧲 通过使用持久连接减少连接创建/销毁频率,显著降低 TIME_WAIT 数量。
  • 常用于 HTTP/1.1 默认启用的 Connection: keep-alive
  • 适用于高频交互场景(如 RPC、数据库连接池等)。

✅ 方法 3:内核参数优化(Linux)

通过修改系统内核参数,调控 TIME_WAIT 状态行为:

1
2
3
4
5
6
7
8
# 允许 TIME_WAIT 连接被快速重用
echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse

# 允许 TIME_WAIT 的端口快速回收
echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle # ⚠️ 已废弃,慎用

# 查看 TIME_WAIT 等状态数量
netstat -nat | grep TIME_WAIT | wc -l

⚠️ tcp_tw_recycle 在高 NAT/多客户端场景下会导致连接异常,自 Linux 4.12 起已被移除。


✅ 方法 4:使用负载均衡或代理层卸载连接

  • 🔀 通过 Nginx / LVS / HAProxy 等中间层代理,让其统一管理连接,客户端不直接连接后端服务。
  • 后端服务维持更少连接,避免过多 TIME_WAIT。

🧾 五、TIME_WAIT 产生在哪一方?

主动关闭连接的一方 会进入 TIME_WAIT
被动关闭连接的一方 进入 CLOSE-WAIT

✅ 因此,客户端通常进入 TIME_WAIT 状态,如果你看到服务端有大量 TIME_WAIT,很可能是服务端主动断开连接了!


✅ 六、总结一句话:

TIME_WAIT 是为连接安全关闭做的必要等待,但过多会带来端口耗尽和资源浪费,可通过长连接、端口重用、内核调优等手段缓解。


✅ TCP 报文头部格式详解(TCP Header)


📦 一、TCP 报文段结构

一个 TCP 报文段由两部分组成:

1
[TCP 报文头部 (Header)] + [TCP 数据部分 (Data)]

其中,头部最小 20 字节,最大 60 字节,取决于是否有“选项字段(Options)”。


📐 二、TCP 报文头部格式(标准 20 字节)

以下是标准 TCP 报文头部的字段结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  0          4          8         12         16         20
+-----------+-----------+-----------+-----------+
| 源端口号 (16 bits) | 目标端口号 (16 bits) |
+-----------+-----------+-----------+-----------+
| 序列号 Sequence Number (32 bits) |
+--------------------------------------------------------------+
| 确认号 Acknowledgment Number (32 bits) |
+-----------+-----------+-----------+-----------+
| 数据偏移 | 保留 | 控制位 Flags(6位) | 窗口大小 (16 bits) |
| 4 bits | 6 bits | 6 bits | |
+-----------+-----------+-----------+-----------+
| 校验和 (Checksum) (16 bits) | 紧急指针 (16 bits) |
+-----------+-----------+-----------+-----------+
| (可选字段 Options 及其填充 Padding,0~40 字节) |
+--------------------------------------------------------------+

🧩 三、字段解释(按顺序)

字段 位数 作用
源端口号 Source Port 16 表示发送方应用程序使用的端口号
目标端口号 Destination Port 16 表示接收方应用程序的端口号
序列号 Sequence Number 32 数据字节流的起始序号,确保有序传输
确认号 Acknowledgment Number 32 表示期望接收到的下一个字节序号(ACK 用)
数据偏移 Data Offset 4 表头长度,以 4 字节为单位(最小值为 5)
保留 Reserved 6 保留未用,值应为 0(留作扩展)
控制位 Flags 6 控制数据传输行为,详见下表
窗口大小 Window Size 16 告诉对方还能接收多少字节(流量控制)
校验和 Checksum 16 校验整个 TCP 报文是否被破坏
紧急指针 Urgent Pointer 16 指出紧急数据的位置,配合 URG 使用
选项字段 Options(可选) 0~40字节 包括如 MSS、窗口扩大、时间戳等选项

🚩 四、控制位 Flags(6 位标志位)

位名 含义 用途
URG 紧急标志 配合紧急指针使用,优先处理紧急数据
ACK 确认标志 确认号字段有效,正常通信必带
PSH 推送标志 立即推送数据到应用层
RST 重置连接 异常关闭连接,强制断开
SYN 同步序号 用于建立连接(三次握手中)
FIN 结束标志 表示数据发送完成,请求关闭连接(挥手)

三次握手:SYN,SYN+ACK,ACK四次挥手:FIN,ACK,FIN,ACK


📝 五、关键字段使用场景小结

场景 相关字段
建立连接(三次握手) SYN, ACK, Sequence Number
断开连接(四次挥手) FIN, ACK
流量控制 Window Size
错误检测 Checksum
数据有序传输 Sequence Number, Acknowledgment Number
加快数据处理 PSH
紧急中断通知 URG, Urgent Pointer