TCP(2)
第二次握手丢失是最浪费资源的情况,因为它迫使通信双方都在不断尝试重发。阶段现象结果客户端 (Client)状态变为FIN_WAIT_1,没收到 ACK超时重传FIN 包;若多次重传失败,直接强制关闭。服务端 (Server)没收到 FIN 包状态保持;直到保活探测超时或收到 RST 后才关闭。第一次挥手丢失并不可怕,客户端会通过“反复横跳”(重传)来尝试修复;如果实在连不上,客户端会自己先“撒手不
一、第一次握手丢失会怎么样?
1. 触发超时重传
当客户端发送 SYN 报文后,会进入 SYN_SENT 状态。此时客户端会启动一个重传定时器。如果在规定时间内没有收到服务端的确认(SYN-ACK),客户端就会认为报文丢失,从而重新发送 SYN 报文。
2. 重传的时间间隔(指数退避)
TCP 不会无限制地立即重传,而是采用指数退避算法。在大多数操作系统(如 Linux)中,重传的策略通常如下:
-
第一次重传: 在 1 秒后(通常初始 RTO 为 1s)。
-
第二次重传: 如果还没收到,等待 2 秒后重传。
-
后续重传: 等待时间翻倍(4s, 8s, 16s...)。
3. 最大重传次数
重传并不会永远持续下去。在 Linux 系统中,受内核参数 tcp_syn_retries 的限制。
-
默认值通常为 5 次或 6 次。
-
如果达到最大次数后依然没有收到响应,客户端就会放弃连接,并向应用层返回“连接超时”的错误。
4. 整体等待时间计算
假设 tcp_syn_retries 为 5 次,重传序列通常是: 1s + 2s + 4s + 8s + 16s + 32s = 63秒。 这意味着,如果第一次握手丢失且服务端一直没响应,用户可能需要等待一分钟左右才会看到最终的报错。
二、第二次握手丢失会怎么样?
1. 双重重传机制
由于第二次握手(SYN-ACK)具有“承上启下”的作用,它既是对客户端 SYN 的确认,也是服务端发起的连接请求。如果它丢失了:
-
客户端(认为自己的 SYN 没传到): 客户端迟迟收不到确认,会触发重传,再次发送 SYN 报文。
-
服务端(认为自己的 SYN-ACK 没传到): 服务端发送 SYN-ACK 后会进入
SYN_RCVD状态。由于没有收到客户端的 ACK(第三次握手),服务端也会触发超时重传,再次发送 SYN-ACK 报文。
2. 重传次数与参数控制
两端的重传逻辑由不同的内核参数控制:
| 角色 | 动作 | 控制参数 | 默认值 (Linux) |
| 客户端 | 重传 SYN | tcp_syn_retries |
通常为 5-6 次 |
| 服务端 | 重传 SYN-ACK | tcp_syn_retries_2 (或 tcp_synack_retries) |
通常为 5 次 |
3. 指数退避与超时
和第一次握手类似,两端都会遵循指数退避算法(1s, 2s, 4s...)。
-
最终结果: 如果网络一直不通,两端都会在达到最大重传次数后放弃。
-
状态清理: 服务端在放弃前,会维持一个半连接(Half-open connection)。如果丢失过多,可能会填满服务端的 SYN 队列,导致所谓的“SYN Flood”攻击效果,使正常用户无法建立连接。
4. 关键区别:谁先“放弃”?
在实际环境中,客户端和服务端的重传次数设置可能不同。
-
如果服务端先达到上限,它会关闭半连接并释放资源。
-
如果客户端先达到上限,它会直接报错给应用程序(如 Browser 报错连接超时)。
总结
第二次握手丢失是最浪费资源的情况,因为它迫使通信双方都在不断尝试重发。
三、第三次握手丢失会怎么样?
1. 双方状态的“不对等”
当第三次握手丢失时,客户端和服务端处于完全不同的认知状态:
-
客户端(Client): 在发送完 ACK 后,状态立即变为
ESTABLISHED。它认为连接已经建立,可以开始发送数据了。 -
服务端(Server): 没收到 ACK,状态依然维持在
SYN_RECV。它认为握手还没完成。
2. 服务端的反应:超时重传
服务端因为没有收到最后的确认包,会触发超时重传机制。
-
重发 SYN+ACK: 服务端会认为自己之前的第二次握手包丢了,于是重新发送
SYN+ACK给客户端。 -
次数限制: 服务端会尝试多次(通常是 5 次,取决于系统设置),每次间隔时间倍增(1s, 2s, 4s...)。
-
关闭连接: 如果重传达到上限依然没有收到客户端的 ACK,服务端会自动关闭这个连接并回收资源。
3. 客户端的反应:视情况而定
客户端的后续表现取决于它是否立即发送数据:
-
场景 A:客户端只握手,不发数据 客户端处于
ESTABLISHED,但在收到服务端重传的SYN+ACK时,会意识到之前的 ACK 可能丢了,于是再次发送 ACK 给服务端。如果这次送达了,双方都进入正常状态。 -
场景 B:客户端立即发送数据包
-
客户端认为连接已通,直接发送携带载荷(Payload)的数据包。
-
关键点: 在 TCP 协议中,数据包本身也带有 ACK 确认信息。
-
当服务端收到这个“带数据的包”时,它能从包头发现 ACK 号是正确的,于是直接从
SYN_RECV转为ESTABLISHED状态,并正常处理数据。这种情况下,丢失的第三次握手被后续的数据包“补偿”了。
-
-
场景 C:连接被服务端关闭后,客户端才发数据 如果服务端重传多次失败已经关闭了连接,而客户端此时才发送数据。服务端会因为找不到对应的连接记录,直接回执一个 RST(重置)包,告诉客户端:“我这没这个连接,请重连。”
四、第一次挥手丢失了会怎么样?
1. 客户端:进入超时重传
当客户端发送 FIN 包后,状态会由 ESTABLISHED 变为 FIN_WAIT_1。
-
等待 ACK: 客户端会等待服务端的确认(ACK)。如果由于网络原因 FIN 包丢失,客户端迟迟收不到 ACK。
-
超时重传: 客户端会触发 超时重传(Retransmission) 机制,重新发送这个 FIN 包。
-
退避算法: 重传的时间间隔通常会翻倍(例如 1s, 2s, 4s...),以减轻网络拥堵。
-
重传次数限制: 客户端会重传若干次(由内核参数
tcp_orphan_retries控制,通常为 5-8 次)。
2. 服务端:毫无察觉,保持连接
由于第一次挥手(FIN)压根没到服务端,服务端并不知道对方想要断开连接:
-
状态不变: 服务端依然维持在
ESTABLISHED状态。 -
继续等待: 服务端会认为连接依然正常,继续等待接收数据或发送数据。
3. 最终结局:静默关闭 或 RST
如果客户端重传多次 FIN 后依然没有任何回应(服务端宕机或网络彻底中断),客户端将不再尝试:
-
客户端直接关闭: 客户端达到最大重传次数后,会强制释放连接资源,直接进入
CLOSED状态。 -
服务端超时释放(Keepalive): * 此时服务端还傻傻地开着连接。
-
如果服务端开启了 TCP Keepalive(保活机制),在一段很长的时间(默认通常是 2 小时)没有数据往来后,服务端会发送探测包。
-
因为客户端已经关闭了连接,客户端可能会回一个 RST(重置)包。
-
服务端收到 RST 后,也会意识到连接已断开,从而释放资源。
-
总结
| 阶段 | 现象 | 结果 |
| 客户端 (Client) | 状态变为 FIN_WAIT_1,没收到 ACK |
超时重传 FIN 包;若多次重传失败,直接 强制关闭。 |
| 服务端 (Server) | 没收到 FIN 包 | 状态保持 ESTABLISHED;直到保活探测超时或收到 RST 后才关闭。 |
简单来说: 第一次挥手丢失并不可怕,客户端会通过“反复横跳”(重传)来尝试修复;如果实在连不上,客户端会自己先“撒手不管”(直接关闭),而服务端最终也会因为超时而释放资源。
五、第二次挥手丢失了会怎么样?
1. 双方状态的“信息差”
-
客户端(主动关闭方): 发送了 FIN,正在
FIN_WAIT_1状态死等 ACK。 -
服务端(被动关闭方): 收到了 FIN,已经回了 ACK,状态变为
CLOSE_WAIT。它认为自己已经告诉客户端“我知道你要断开了”,正在处理剩下的任务(比如发完最后的缓冲区数据)。
2. 会发生什么?(双重重传机制)
由于 ACK 报文本身是没有确认机制的(即 ACK 包丢了,接收方不会回一个 ACK 来确认收到了 ACK),系统只能靠“超时”来解决问题:
A. 客户端:重传 FIN
客户端在 FIN_WAIT_1 状态下,如果收不到 ACK,它会认为自己的 第一次挥手(FIN) 丢了。
-
动作: 客户端会再次发送 FIN 包。
-
后果: 只要服务端还活着,它每收到一个重传的 FIN,就会再次触发一个第二次挥手(ACK)。
B. 服务端:等待应用层指令
服务端此时处于 CLOSE_WAIT 状态。这个状态很特殊,它在等待应用程序调用 close() 函数来发送第三次挥手(FIN)。
-
如果服务端应用程序处理得很快,在客户端重传 FIN 之前就发出了第三次挥手(FIN),那么这个新的 FIN 包其实兼具了 ACK 的功能。
-
客户端收到这个 FIN 后,会意识到连接已经可以进入下一阶段。
3. 最极端的有趣情况:如果客户端一直收不到 ACK
如果网络环境极差,导致所有的 ACK 都丢了,或者服务端处理极慢:
-
客户端心灰意冷: 客户端在重传多次 FIN 失败后,会直接认为连接已死,强行关闭(进入
CLOSED)。 -
服务端“守活寡”: 服务端可能还停留在
CLOSE_WAIT状态,等待它自己的应用程序关闭。-
如果应用程序不调用
close(),这个CLOSE_WAIT会永久存在。 -
这就是为什么在排查服务器问题时,如果看到大量
CLOSE_WAIT,通常意味着程序代码有 Bug,忘记关闭连接了。
-
4. 总结对比
| 视角 | 认知 | 反应 |
| 客户端 | “我的 FIN 丢了吧?怎么没回音?” | 重传 FIN,直到收到 ACK 或达到重传上限。 |
| 服务端 | “我已经答应要关了,等我忙完。” | 每次收到重传的 FIN,就补发一个 ACK。 |
有趣的一点是:
如果第二次挥手(ACK)丢失,但紧接着第三次挥手(FIN)发过来了,客户端可以直接从 FIN_WAIT_1 跳过中间状态,直接进入确认阶段。TCP 协议的设计非常“务实”,只要目的达到了,过程中的小波折可以通过重传来修补。
六、第三次挥手丢失了会怎么样?
1. 服务端的反应:它是真的“着急”
在第三次挥手(服务端发送 FIN)时,服务端的状态由 CLOSE_WAIT 转为 LAST_ACK。
-
重传责任: 因为这是一个带有
FIN标志的报文,它必须得到客户端的确认(第四次挥手 ACK)。 -
动作: 服务端如果收不到第四次挥手的 ACK,它会认为自己的 FIN 包丢了。
-
后果: 服务端会开启超时重传,反复发送这个 FIN 包。
-
结局: 如果重传多次(由
tcp_orphan_retries参数决定)依然无果,服务端会彻底死心,强制进入CLOSED状态并释放内存资源。
2. 客户端的反应:它是真的“无助”
客户端在收到第二次挥手(ACK)后,状态进入了 FIN_WAIT_2。
-
死等的困境: 在这个状态下,客户端已经完成了它的关闭任务,它在等待服务端的 FIN 包。
-
没有重传权: 客户端此时是“接收方”,它没法主动发什么东西来催促服务端,只能干等。
-
超时机制: * 如果客户端是通过调用
close()关闭的(这种连接叫孤儿连接,Orphan Socket),内核会给FIN_WAIT_2设置一个定时器(通常由tcp_fin_timeout参数决定,默认 60s)。-
如果 60s 内没收到 FIN 包,客户端会直接断开连接。
-
3. 谁先“着急”?(场景推演)
通常情况下,服务端会先表现出“着急”的状态,因为它在不断地重传 FIN 包:
-
如果网络恢复: 服务端重传的 FIN 包终于到了客户端。客户端回一个 ACK(第四次挥手),双方圆满结束。
-
如果网络彻底断掉:
-
客户端:盯着表看,60 秒一到,准时走人(进入
CLOSED),释放资源。 -
服务端:还在那儿一次又一次地重传 FIN,直到达到最大重传次数(可能需要几分钟),才最终放弃。
-
4. 总结对比
| 角色 | 状态 | 心理活动 | 最终结局 |
| 服务端 (Server) | LAST_ACK |
“我把最后的话 (FIN) 发了,对方怎么不理我?重发一次试试!” | 多次重传,最后实在没反应就强制关闭。 |
| 客户端 (Client) | FIN_WAIT_2 |
“我等着听他最后一句告别,等不到我可就走了。” | 静默等待,超时(通常 60s)后直接闪人。 |
七、第四次挥手丢失会怎么样?
1. 服务端的反应:我认为你没收到
服务端发送了第三次挥手(FIN)后,进入了 LAST_ACK 状态。
-
没收到回应: 服务端在一段时间内没收到客户端回传的 ACK,它会认为:“糟了,我的 FIN 包可能丢了,或者客户端没收到。”
-
动作: 服务端会重传第三次挥手(FIN)。
-
循环: 只要没收到 ACK,服务端就会一直重传 FIN,直到达到最大重传次数后强行关闭。
2. 客户端的反应:我得等一会儿再走
客户端在发送完第四次挥手(ACK)后,并没有立即进入 CLOSED 状态,而是进入了著名的 TIME_WAIT 状态。
-
收到重传的 FIN: 客户端在
TIME_WAIT期间,如果收到了服务端重传的 FIN 包,它会意识到:“看来我刚才发的 ACK 丢了。” -
动作: 客户端会再次发送 ACK,并重新启动
TIME_WAIT定时器。
3. 核心机制:为什么要等待 2MSL?
客户端在 TIME_WAIT 状态下需要等待 2MSL(Maximum Segment Lifetime,报文最大生存时间,通常是 1 到 2 分钟)才会进入 CLOSED。这样做有两个核心目的:
原因一:确保可靠地关闭连接
如上所述,为了保证服务端能收到最后的 ACK。如果客户端发完 ACK 就直接闪人(进入 CLOSED),而 ACK 恰好丢了,服务端重传 FIN 时,客户端已经不在了。此时客户端的内核会回一个 RST 包,服务端收到后会认为连接出了异常(报错),而不是正常的“优雅关闭”。
原因二:防止“已过时”的数据包捣乱
网络中可能存在因为绕路而“迟到”的旧数据包。
-
等待 2MSL 可以保证在这个连接中产生的所有报文都在网络中消失。
-
如果没有这个等待时间,客户端立即用相同的端口号开启一个新连接,那些迟到的旧报文可能会被误认为是新连接的数据,导致数据乱序或逻辑错误。
4. 总结:如果第四次挥手真的丢了
| 角色 | 状态变化 | 结果 |
| 服务端 (Server) | LAST_ACK 重传 FIN |
只有收到 ACK 后才会进入 CLOSED。 |
| 客户端 (Client) | TIME_WAIT |
只要收到重传的 FIN,就不断补发 ACK,直到 2MSL 定时器耗尽。 |
趣味思考:
虽然 TIME_WAIT 保证了可靠性,但在高并发服务器(如爬虫或大量短连接服务)上,大量的 TIME_WAIT 会占用掉所有可用的端口号,导致无法建立新连接。
更多推荐

所有评论(0)