计算机网络八股3
计算机网络八股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 状态。
总结:
- 客户端 -> 服务器:
SYN, seq = x
- 服务器 ->
客户端:
SYN, ACK, seq = y, ack = x + 1
- 客户端 -> 服务器:
ACK, seq = x + 1, ack = y + 1
连接建立后,客户端和服务器可以开始数据传输。
📚 大白话解释:
TCP 三次握手的过程类似于两个人进行确认后才开始对话:
- 第一次握手:发起连接 假设你和某人要开始谈话,但由于嘈杂的环境,你想确认对方是否能听到你。你首先大声问:“嗨,你听得到我说话吗?”这就像客户端发送 SYN 请求,表明你想开始交流,并提供了自己的“起点”(初始序列号)。
- 第二次握手:对方回应 对方听到你的声音后,回应:“是的,我听到了。我也准备好了。”这个回应就像服务器发送 SYN + ACK 报文,确认双方都准备好开始对话,并告诉你它的“起点”(服务器序列号)。
- 第三次握手:最终确认 为了确保对方的回应听清楚,你再回应:“太好了,我确认了你准备好了,我们可以开始交流了!”这个回应就是客户端发送的 ACK 确认,双方都确认了对方的状态。
- 开始交流 在双方都确认准备好之后,你们可以开始正式的交流了。
📚 SYN 的概念与作用:
SYN 标志位
- 定义:SYN 是 TCP 协议中用来建立连接的一个标志位,全称为 Synchronize Sequence Numbers(同步序列号)。它用于告诉对方当前连接的同步请求,是三次握手过程中的关键。
作用:
- 同步序列号:SYN 用来标记初次请求建立连接时的同步信息。它告诉对方,连接请求的序列号需要同步,为之后的可靠数据传输做准备。
- 防止旧报文干扰:SYN 位的使用能够有效防止旧的报文段误被当作新连接的请求。例如,在网络中,若以前的连接报文仍未消失,SYN 位可确保新的连接请求与旧的报文段区分开来,避免数据混乱。
示意图:
1 | 客户端 服务器 |
📚 实际例子:
假设你是客户(客户端),想和某个企业代表(服务器)讨论合作事宜。为了确认彼此都准备好了并且愿意交流,按照以下步骤进行:
- 第一次握手: 你向代表问候:“您好,我是客户小张,我们可以谈谈合作吗?”(这是你发出的连接请求)
- 第二次握手: 代表听到你的话后,确认:“你好,我是企业代表小李,我也准备好了。”(这是代表同意建立连接,并告知自己的序列号)
- 第三次握手: 为了确认对方的回应,你说:“太好了,我确认你准备好了,咱们可以开始讨论合作事宜。”(你确认对方的回应并准备好开始对话)
- 开始讨论: 一旦确认完毕,双方就可以开始正式讨论合作事宜,数据交流正式开始。
小结:
- 三次握手 是为了确保双方都能接收到对方的连接请求,并且都准备好开始数据传输。
- SYN 标志位 确保了序列号同步,防止无效或重复连接请求干扰数据传输。
TCP 三次握手为什么是三次,为什么不能是两次或四次?
TCP 三次握手(Three-Way Handshake)是用于建立可靠连接的机制。其目的是确保双方能够同步序列号并确认双方都准备好通信。三次握手的设计有其特定的原因和背景,下面详细解析三次握手的必要性及为什么不能是两次或四次。
为什么是三次握手?
TCP 三次握手的设计确保了以下几个重要目标:
- 确保客户端和服务器都准备好建立连接:
- 第一次握手,客户端通过发送 SYN 包告诉服务器它希望建立连接。服务器收到这个请求后知道客户端准备好了。
- 第二次握手,服务器确认收到连接请求,并告诉客户端它也准备好了。
- 第三次握手,客户端确认接收到服务器的回应,确保双发双方都可以进行通信。
- 序列号同步:
- 在 TCP 中,每个数据包都有一个序列号,用来保证数据的顺序传输。
- 第一次握手中,客户端通过 SYN 包提供自己的序列号;第二次握手中,服务器提供自己的序列号并确认客户端的序列号;第三次握手中,客户端再次确认服务器的序列号,确保双方都同步了序列号。
为什么不能是两次握手?
如果 TCP 握手只进行两次,会存在以下潜在问题:
- 防止丢包引起的连接问题:
- 假设客户端发送 SYN 包给服务器后,服务器立即响应 SYN-ACK 包。如果这个 ACK 包丢失了,服务器就无法知道客户端是否已经收到了它的回应,服务器就会继续等待客户端发送 ACK 包。
- 如果没有第三次握手,服务器可能会一直保持等待状态,即使客户端实际上没有收到确认。这样,客户端和服务器之间就没有可靠的同步信息,连接就可能无法正常建立。
- 防止无效连接的建立:
- 如果没有第三次握手,客户端在没有确认服务器响应的情况下就开始数据传输,可能会导致错误的连接被建立。特别是在网络延迟较大的情况下,丢失的报文可能导致错误连接的生成。
- 丢失请求后的重传机制:
- 通过三次握手,客户端和服务器都有机会确认数据包的发送和接收。如果是两次握手,客户端和服务器只能在第一次和第二次握手后假设连接建立完毕,没有进一步的确认,这样可能会出现连接没有实际建立的情况。
为什么不能是四次握手?
三次握手已经足够建立可靠连接,而四次握手并不会带来额外的好处,反而增加了复杂度和不必要的开销。具体原因如下:
- 四次握手没有额外的实际用途:
- 三次握手已经确保了客户端和服务器能够同步其初始序列号,并且双方都能确认彼此准备好建立连接。第四次握手会造成不必要的延迟,并且不会增加协议的可靠性。
- 因此,增加第四次握手不仅浪费资源,还会导致连接建立的延迟增大,不符合高效传输的需求。
- 三次握手已能确认连接的双方准备好通信:
- 第三次握手完成时,双方已经知道对方准备好,且同步了序列号。增加第四次握手不会改善连接质量,反而增加了协议的复杂性。
总结:为什么是三次握手?
- 三次握手 确保了双方都已经准备好,并且在数据传输前成功同步了序列号。它通过防止丢包、重传、以及未确认的连接请求等问题,确保了可靠的连接建立。
- 两次握手 不足以保证连接的可靠性,可能会导致丢包或连接错误。
- 四次握手 不必要,三次握手已足够,增加额外的握手次数只会增加延迟和开销。
TCP 握手与“泛洪攻击”
在某些情况下,TCP 三次握手机制可能会被恶意攻击者利用,进行 SYN Flood(泛洪攻击),即发送大量伪造的 SYN 包,导致服务器资源耗尽,无法处理正常的连接请求。
SYN Flood 攻击:
- 攻击者伪造大量 SYN 包发送到目标服务器,导致服务器响应并为每个 SYN 包分配资源(为每个半开连接预留资源)。
- 由于攻击者不会完成第三次握手,服务器会在半连接队列中保持这些无效的连接,浪费服务器资源。
- 这种攻击会导致服务器无法处理正常的请求,最终导致 拒绝服务(DoS)。
解决方案:
- SYN Cookies:使用 SYN Cookies 技术,通过对 SYN-ACK 包编码连接信息,服务器不再为每个连接分配资源,直到客户端完成第三次握手,从而防止资源被消耗掉。
- 半连接队列长度限制:控制半连接队列的长度,防止半连接被过度占用。
重新设计 TCP 连接建立过程:
如果重新设计 TCP 连接建立过程,以下几点可以考虑:
- SYN Cookies:如上所述,SYN Cookies 技术能够减少资源占用,防止 SYN Flood 攻击,保证服务器在不增加额外开销的情况下能处理大量连接请求。
- 增加验证机制:可以设计更高级的验证机制,在握手过程中使用更复杂的加密方式来防止伪造 SYN 请求。
- 连接预确认:考虑在三次握手之前,进行一个简单的双向确认,以确保双方都能可靠地接收到对方的信息。
结论:
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 包作用:
- ACK(确认):
- 服务器发送的 ACK 包是为了确认客户端发送的 SYN
包(连接请求)已经成功接收。ACK
通过设置确认号字段(
ACK=客户端发送的序列号+1
)告知客户端,服务端已接收到客户端的请求。
- 服务器发送的 ACK 包是为了确认客户端发送的 SYN
包(连接请求)已经成功接收。ACK
通过设置确认号字段(
- SYN(同步):
- 同时,服务器还会发送 SYN 包,表示服务端也准备好建立连接。由于服务端需要同步自己的初始序列号,它必须发送一个 SYN 包给客户端,告知客户端服务端准备就绪,并且发送自己的初始序列号。
因此,第二次握手需要传回 SYN 和 ACK:
- SYN:表示服务端希望建立连接。
- ACK:确认收到客户端的 SYN 包。
这两者合在一起,标志着双方都已准备好建立连接,并开始数据传输。
第三次握手可以携带数据吗?
第三次握手是否可以携带数据?
- 可以携带数据:第三次握手是可以携带数据的。当第三次握手完成时,连接已经建立,客户端和服务器可以交换数据。
- 客户端的状态:客户端此时已经进入 ESTABLISHED(已建立)状态,连接建立完成,确认双方的接收和发送能力都是正常的,可以开始正常的数据传输。
- 第一次握手不携带数据的原因:
- 第一次握手使用 SYN 包进行连接请求时,不能携带数据。主要是出于 安全考虑,因为如果允许在 SYN 包中携带数据,恶意攻击者可能利用此特性发送大量数据,导致服务器在处理时消耗大量的 CPU 和内存资源,影响服务器的正常运行。
结论:第三次握手可以携带数据,因为此时连接已经建立,并且双方已经准备好交换数据。
总结:
- 第一次握手:客户端发送 SYN 包,服务端收到后进行响应。如果服务端未收到 SYN 包,客户端会重试,直到最大重试次数或连接失败。
- 第二次握手:服务端回应 SYN-ACK 包,客户端收到后发送 ACK 确认。如果客户端未收到 ACK 包,会继续重传。
- 第三次握手:客户端确认服务端的响应后发送 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 队列)被填满,无法处理正常的连接请求,从而导致服务不可用。
- 攻击过程:
- 攻击者伪造源 IP 地址,向目标服务器发送大量的 SYN 包。
- 服务器收到 SYN 包后,回复 SYN-ACK 包(即第三次握手的第二步)。
- 攻击者并不回复 ACK,导致服务器的半连接队列中的连接无法完成三次握手。
- 服务器资源消耗殆尽,无法再处理来自合法客户端的连接请求,导致服务器崩溃或拒绝服务。
- 攻击影响:这种攻击会造成服务器资源被耗尽,使得无法为正常用户提供服务。
SYN Flood 攻击的应对方案
- SYN Cookies:
- 原理:SYN Cookies 是一种防止 SYN Flood 攻击的技术。当服务器接收到 SYN 包时,它不会立即分配资源,而是根据客户端的 IP 地址、端口号等信息计算出一个唯一的 cookie 值,并将该值作为序列号发送 SYN-ACK 包。
- 过程:
- 服务器收到 SYN 包后,回复 SYN-ACK 包并携带计算出的 cookie 值。
- 只有当客户端回复 ACK 时,服务器才能根据该 ACK 包的序列号验证是否与计算出的 cookie 一致,若一致才真正建立连接。
- 如果不一致,服务器会丢弃该连接请求。
- 优势:通过 SYN Cookies,可以避免攻击者占用过多的资源,因为服务器没有为每个连接分配资源,只有在验证 ACK 包时才会实际分配资源。
- 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)来确保连接的正确终止。在这个过程中,客户端和服务器必须相互确认各自的关闭请求,以保证双方都知道连接已经完全断开。
详细过程
- 第一次挥手:客户端发送 FIN 报文
- 客户端发送 FIN(Finish)报文,表示客户端没有数据要发送了,但仍然可以接收数据。客户端进入 FIN-WAIT-1 状态,等待服务器的确认。
- 解释:这时客户端像服务器宣布,它已经没有数据要发了,但还没完全结束连接,它仍然会接收服务器的数据。
- 第二次挥手:服务器确认接收 FIN 报文
- 服务器收到 FIN 报文后,发送 ACK(Acknowledgment)报文来确认客户端的关闭请求,进入 CLOSE-WAIT 状态,表示已经准备好断开连接。
- 解释:服务器确认收到了客户端发来的断开请求,服务器暂时还没有完全关闭连接,它可能需要先发送一些剩余的数据。
- 第三次挥手:服务器发送 FIN 报文
- 当服务器确认所有的数据都已发送完后,服务器会向客户端发送 FIN 报文,表示它也没有数据要发送了,准备关闭连接。此时服务器进入 LAST-ACK 状态,等待客户端的确认。
- 解释:服务器发送 FIN 包,通知客户端它已经完成数据的发送,并请求关闭连接。
- 第四次挥手:客户端确认接收 FIN 报文
- 客户端接收到服务器发送的 FIN 报文后,回复一个 ACK 报文,表示客户端确认服务器的关闭请求。此时客户端进入 TIME-WAIT 状态,等待一段时间以确保服务器接收到 ACK 报文。客户端之后进入 CLOSED 状态。
- 服务器接收到 ACK 报文后,进入 CLOSED 状态,连接完全断开。
总结
- 四次挥手的目的是保证双方都知道连接已经完全关闭,防止数据丢失或者在连接未完全关闭的情况下就开始新的连接。
- 在整个过程中,客户端和服务器都会经历不同的状态,确保在数据传输和关闭连接时没有遗漏。
四次挥手在实际应用中的意义
- 防止数据丢失:通过确保每一方都发送和确认了断开请求,可以防止在连接未完全关闭时发送数据,从而避免数据丢失。
- 确保连接彻底关闭:每次挥手都确认了另一方的状态,确保没有一方在还未完全准备好的情况下关闭连接。
- 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 | 客户端 服务端 |
🧾 举个大白话例子(自己版本)
你和你的朋友在打语音电话聊天:
- 你说:“我没什么好说的了,我要挂电话了。”(第一次挥手,FIN)
- 朋友说:“好的,我知道了,但我还没说完,你听我说完。”(第二次挥手,ACK)
- 朋友说完后说:“我现在也说完了,我们可以挂电话了。”(第三次挥手,FIN)
- 你说:“收到,我确认你也说完了,我们正式挂电话。”(第四次挥手,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 来断开连接。
- 此时服务器并不知道客户端已经不可用,还会一直保留这个连接,占用资源。
🧠 解决方案: 使用 保活计时器,主动检测对方是否“还活着”,否则主动关闭连接,避免资源浪费。
⚙️ 三、保活计时器的工作机制
- 每当收到一次对方的数据,就重置保活计时器。
- 如果设定时间(如 2 小时)内没有收到数据,就触发保活机制。
- 发送 探测报文(KeepAlive Probe) 给对方 —— 通常是一个带 ACK 标志、无数据负载的报文。
- 如果对方响应,说明还活着,重新启动计时器。
- 如果对方不响应,继续间隔发送探测包 —— 默认间隔:75 秒。
- 连续发送 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 | ESTABLISHED(连接建立) |
🔹 4. 意义和作用:
- 让本端有时间完成剩余数据的发送或清理工作,再主动关闭连接(即发送 FIN)。
- 如果本端忘记调用
close()
,可能长时间停留在 CLOSE-WAIT,造成资源泄露(连接泄漏)。
🧠 重点记忆:
CLOSE-WAIT 是“对方说要断,我说等一下,我还有事没做”。
🧩 二、TIME-WAIT 状态
🔹 1. 定义:
当 TCP 连接关闭时,本端发送了最后一个 ACK(确认对方
FIN 报文),就进入 TIME-WAIT
状态。
🔹 2. 出现场景:
- 常出现在主动关闭连接的一方(一般是客户端)。
🔹 3. 状态转换路径:
1 | LAST-ACK ← 被动关闭方等待 ACK |
🔹 4. 意义和作用:
✅ TIME-WAIT 的两个主要目的:
- 保证被动关闭方收到最终 ACK 报文:
- 若 ACK 丢失,被动关闭方会重发 FIN。
- 此时 TIME-WAIT 状态能重新发送 ACK。
- 防止旧连接的重复报文干扰新连接:
- 若新连接重复使用旧的 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 | int optval = 1; |
🚫 但不能完全避免 TIME_WAIT 的出现,只是允许在一定条件下重用端口。
✅ 方法 2:使用长连接(Keep-Alive)
- 🧲 通过使用持久连接减少连接创建/销毁频率,显著降低 TIME_WAIT 数量。
- 常用于 HTTP/1.1 默认启用的
Connection: keep-alive
。 - 适用于高频交互场景(如 RPC、数据库连接池等)。
✅ 方法 3:内核参数优化(Linux)
通过修改系统内核参数,调控 TIME_WAIT 状态行为:
1 | # 允许 TIME_WAIT 连接被快速重用 |
⚠️
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 | 0 4 8 12 16 20 |
🧩 三、字段解释(按顺序)
字段 | 位数 | 作用 |
---|---|---|
源端口号 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 |