第一部分:TCP 三次握手 (建立连接)
核心目的: 为了保证客户端和服务器双方都具备发送和接收数据的能力。
想象一下打电话的过程:
-
你(客户端)给朋友(服务器)打电话。
-
朋友听到铃声,接起电话说“喂,你好?”。
-
你听到朋友的“喂”,回一句“喂,是我啊!”。
-
这时,你们双方都确认了对方能听到自己,也能让自己听到,通话正式建立。
在 TCP 协议中,这个过程被精确地抽象为三次握手。
流程详解:
-
第一次握手 (SYN)
-
客户端 -> 服务器:发送一个 TCP 数据包。
-
这个包的关键标志位:SYN=1 (Synchronize Sequence Numbers,同步序列号)。
-
同时,客户端会随机生成一个自己的初始序列号 seq = x。
-
客户端状态:进入
SYN_SENT状态。 -
测试视角:如果客户端卡在这个状态,可能是网络防火墙阻断了、服务器端口没开放、或者服务器挂了。
-
-
第二次握手 (SYN + ACK)
-
服务器 -> 客户端:收到 SYN 包后,必须进行回应。
-
回应的包包含两个关键标志位:SYN=1 和 ACK=1 (Acknowledgment,确认)。
-
服务器也会随机生成一个自己的初始序列号 seq = y。
-
同时,确认号 ack = x + 1 (表示“我收到了你的序列号 x,我期望你下次从 x+1 开始发”)。
-
服务器状态:进入
SYN_RCVD状态。 -
测试视角:服务器如果收到大量
SYN_RCVD状态的连接,可能是遭受了 SYN Flood 洪水攻击(一种拒绝服务攻击)。
-
-
第三次握手 (ACK)
-
客户端 -> 服务器:收到服务器的 SYN-ACK 包后,再次发送一个确认包。
-
这个包的标志位:ACK=1。
-
自己的序列号 seq = x + 1 (因为第一次握手消耗了一个序列号)。
-
确认号 ack = y + 1 (表示“我收到了你的序列号 y,我期望你下次从 y+1 开始发”)。
-
双方状态:完成这次握手后,客户端和服务器都进入
ESTABLISHED状态,连接建立成功,可以开始传输数据了。
-
图解三次握手:
客户端(Client) 服务器(Server)
| |
|-------- SYN (seq=x) ----------->| (CLOSED -> LISTEN)
| | (状态: SYN_RCVD)
|<---- SYN+ACK (seq=y, ack=x+1) --|
| | (状态: ESTABLISHED)
|------ ACK (seq=x+1, ack=y+1) -->|
(状态: ESTABLISHED) |
第二部分:TCP 四次挥手 (终止连接)
核心目的: 为了保证双方的数据都已经传输完毕,才能安全地断开连接。
同样用打电话举例:
-
你说:“我说完了,要挂电话了。”
-
朋友说:“好的,我知道你说完了。” (但朋友可能还有话没说完)
-
朋友把最后几句话说完,然后说:“我也说完了,可以挂了。”
-
你说:“好的,拜拜。” 然后挂断。
TCP 是全双工的,即数据可以双向独立传输。因此断开连接需要两个方向分别断开。
流程详解:
-
第一次挥手 (FIN)
-
主动关闭方 (假设是客户端) -> 被动关闭方 (服务器):发送一个 TCP 数据包。
-
标志位:FIN=1 (Finish,结束),表示我这边的数据发完了,要关闭我到你方向的连接。
-
序列号 seq = u (一个随机值)。
-
客户端状态:进入
FIN_WAIT_1状态。
-
-
第二次挥手 (ACK)
-
服务器 -> 客户端:收到 FIN 包后,立即发送一个确认包。
-
标志位:ACK=1。
-
确认号 ack = u + 1。
-
服务器状态:进入
CLOSE_WAIT状态。此时,从客户端到服务器的连接断了,客户端不能再发数据,但服务器可能还有数据要发给客户端。 -
客户端状态:收到这个 ACK 后,状态变为
FIN_WAIT_2。
-
-
第三次挥手 (FIN)
-
服务器 -> 客户端:当服务器也把剩下的数据发送完毕后,它会发送一个 FIN 包。
-
标志位:FIN=1 和 ACK=1。
-
序列号 seq = w。
-
确认号 ack 依然为 u + 1 (因为中间没有数据交互,确认号不变)。
-
服务器状态:进入
LAST_ACK状态,等待最后一个确认。
-
-
第四次挥手 (ACK)
-
客户端 -> 服务器:收到服务器的 FIN 包后,发送一个确认包。
-
标志位:ACK=1。
-
序列号 seq = u + 1。
-
确认号 ack = w + 1。
-
客户端状态:发送完 ACK 后,进入
TIME_WAIT状态。等待 2MSL (Maximum Segment Lifetime,最大报文生存时间,通常为 2分钟) 后,状态变为CLOSED,彻底关闭。 -
服务器状态:收到这个 ACK 后,状态立即变为
CLOSED。
-
为什么客户端需要 TIME_WAIT 等待?
-
可靠性:确保服务器收到了最后的 ACK。如果这个 ACK 丢失,服务器在超时后会重发 FIN,客户端在
TIME_WAIT状态下还能再次回应 ACK。 -
防旧连接数据混淆:让本次连接的所有报文都在网络中消散,避免被之后新建的、相同四元组(源IP、源端口、目标IP、目标端口)的连接错误接收。
图解四次挥手:
客户端(主动关闭) 服务器(被动关闭)
| (状态: ESTABLISHED) | (状态: ESTABLISHED)
|--------- FIN (seq=u) ------->|
| | (状态: CLOSE_WAIT)
|<------- ACK (ack=u+1) ------|
| (状态: FIN_WAIT_1 -> FIN_WAIT_2) |
| | ... 发送剩余数据 ...
|<------ FIN+ACK (seq=w, ack=u+1) --|
| | (状态: LAST_ACK)
|-------- ACK (ack=w+1) ----->|
| (状态: TIME_WAIT) | (状态: CLOSED)
|(等待 2MSL 后 -> CLOSED) |
对软件测试工程师的意义
-
性能测试与监控:
-
在压力测试中,你可以使用
netstat,ss等命令观察服务器上的连接状态。如果发现大量TIME_WAIT或CLOSE_WAIT状态的连接,可能就是性能瓶颈或 bug 的信号。 -
TIME_WAIT过多:通常是因为主动关闭连接的次数太多(例如,由服务器端主动关闭),可能会耗尽端口资源。这在压力测试中很常见。 -
CLOSE_WAIT过多:这是个大坑! 表示你的应用程序(服务器程序)没有及时调用close()方法响应对方的 FIN 请求,很可能代码里有资源泄露(如 socket 未关闭)。
-
-
故障分析与定位:
-
测试时遇到“连接超时”、“连接被重置 (RST)”等问题,你可以用 Wireshark、tcpdump 等抓包工具直接捕获 TCP 握手和挥手过程。
-
看看是第一次握手没响应(网络问题?端口关闭?),还是第三次握手没过去(防火墙规则?),或者是挥手过程不完整(程序bug?)。抓包是定位这类问题的终极利器。
-
-
设计测试用例:
-
你可以模拟异常情况来测试软件的健壮性。例如:
-
模拟只发送 SYN 不回复 ACK(半连接攻击)。
-
模拟在握手过程中发送 RST 复位包。
-
模拟异常断开(直接拔网线、杀进程),看程序能否正常处理。
-
-

被折叠的 条评论
为什么被折叠?



