可以这样理解:TCP 三次握手是“建立连接”,四次挥手是“关闭连接”。
它们本质上都是为了确认双方的发送能力、接收能力、序列号同步、资源释放是否正常。
一、TCP 三次握手
1. 为什么 TCP 要握手?
因为 TCP 是面向连接的协议。
也就是说,真正传数据之前,客户端和服务端要先确认:
客户端能不能发?
客户端能不能收?
服务端能不能发?
服务端能不能收?
双方的初始序列号是多少?
所以需要一个建立连接的过程。
2. 三次握手过程
一开始:
客户端:CLOSED
服务端:CLOSED
服务端先监听端口:
服务端:LISTEN
第一次握手:客户端发送 SYN
客户端发送:
SYN = 1
Seq = client_isn
意思是:
服务端,我想和你建立连接,这是我的初始序列号 client_isn。
发送后客户端进入:
SYN_SENT
第二次握手:服务端回复 SYN + ACK
服务端收到后,回复:
SYN = 1
ACK = 1
Seq = server_isn
Ack = client_isn + 1
意思是:
我收到你的 SYN 了,你的序列号 client_isn 我确认了。
同时我也想和你建立连接,这是我的初始序列号 server_isn。
发送后服务端进入:
SYN_RCVD
第三次握手:客户端回复 ACK
客户端收到服务端的 SYN + ACK 后,回复:
ACK = 1
Ack = server_isn + 1
意思是:
我也收到你的 SYN 了,你的序列号 server_isn 我也确认了。
发送后客户端进入:
ESTABLISHED
服务端收到这个 ACK 后,也进入:
ESTABLISHED
连接正式建立。
二、为什么必须是三次握手?
核心有两个原因:
1. 防止历史连接造成资源浪费
假设只有两次握手:
客户端 -> SYN -> 服务端
客户端 <- SYN + ACK <- 服务端
如果服务端发完 SYN + ACK 就直接认为连接建立,那么问题来了。
假设网络里有一个很久之前的旧 SYN 报文,因为网络延迟现在才到达服务端。
服务端收到这个旧 SYN 后,以为客户端要建立新连接,于是立刻分配资源,建立连接。
但是客户端根本不想建立这个连接。
这样服务端就白白浪费资源。
如果是三次握手,服务端还要等客户端第三次 ACK。
如果客户端发现这是历史连接,就不会回复 ACK,服务端就不会真正建立连接。
2. 确保双方序列号都同步成功
TCP 传输数据依赖序列号。
客户端要知道服务端的初始序列号:
server_isn
服务端也要知道客户端的初始序列号:
client_isn
前两次握手只能说明:
服务端知道了客户端的序列号
客户端知道了服务端的序列号
但是服务端还不知道:
客户端是否真的收到了我的 server_isn
所以需要第三次 ACK 告诉服务端:
我已经收到你的序列号了。
这样双方的初始序列号才算可靠同步。
三、为什么前两次握手不能携带数据,第三次可以?
第一次握手时,服务端还没有确认客户端是不是合法的连接请求。
如果第一次 SYN 就允许携带大量数据,攻击者可以伪造很多 SYN 报文,让服务端处理大量无效数据,容易被攻击。
第二次握手时,连接也还没完全建立。
第三次握手时,客户端已经确认服务端存在,并且双方序列号已经基本同步,所以第三次 ACK 可以携带数据。
四、TCP 四次挥手
建立连接是双方确认可以通信。
关闭连接是双方分别确认:
我这边不发了
你那边也不发了
TCP 是全双工通信,也就是说:
客户端可以发给服务端
服务端也可以发给客户端
所以关闭连接时,两个方向要分别关闭。
1. 四次挥手过程
一开始双方都是:
ESTABLISHED
假设客户端主动关闭连接。
第一次挥手:客户端发送 FIN
客户端发送:
FIN = 1
意思是:
服务端,我这边数据发完了,我不再给你发数据了。
发送后客户端进入:
FIN_WAIT_1
注意:
客户端不发送数据了,但仍然可以接收服务端的数据。
第二次挥手:服务端回复 ACK
服务端收到 FIN 后,回复:
ACK = 1
意思是:
我知道你不发了。
服务端进入:
CLOSE_WAIT
客户端收到 ACK 后进入:
FIN_WAIT_2
这个时候连接还没完全关闭。
因为服务端可能还有数据没发完。
第三次挥手:服务端发送 FIN
等服务端数据也处理完、发送完之后,服务端发送:
FIN = 1
意思是:
我这边也发完了,我也不再发数据了。
服务端进入:
LAST_ACK
第四次挥手:客户端回复 ACK
客户端收到服务端 FIN 后,回复:
ACK = 1
意思是:
我知道你也不发了。
客户端进入:
TIME_WAIT
服务端收到 ACK 后进入:
CLOSE
客户端等待 2MSL 后,也进入:
CLOSE
五、为什么挥手需要四次?
因为 TCP 是双向通信。
客户端发送 FIN,只能说明:
客户端不发数据了
但不能说明:
服务端也不发数据了
服务端收到 FIN 后,可能还有数据要继续发给客户端。
所以服务端要先回一个 ACK:
我知道你不发了
等服务端自己的数据也发完后,再发送 FIN:
我也不发了
所以正常情况下 ACK 和 FIN 是分开发的。
因此是四次挥手。
当然,如果服务端收到 FIN 后刚好也没有数据要发,有时候第二次 ACK 和第三次 FIN 可以合并发送,这样看起来可能是三次报文,但逻辑上仍然是四个动作。
六、TIME_WAIT 是什么?
主动关闭连接的一方,最后会进入 TIME_WAIT。
比如图里是客户端主动关闭,所以客户端进入 TIME_WAIT。
TIME_WAIT 的作用主要有两个。
1. 保证最后一个 ACK 能被服务端收到
第四次挥手时,客户端给服务端发 ACK。
但是这个 ACK 可能丢失。
如果 ACK 丢了,服务端收不到,就会以为自己的 FIN 没有被确认,于是服务端会重发 FIN。
如果客户端此时已经直接关闭了,就无法再回复 ACK。
所以客户端不能马上关闭,而是要等一段时间。
如果在 TIME_WAIT 期间又收到了服务端重发的 FIN,客户端就再发一次 ACK。
2. 让旧连接的报文在网络中自然消失
网络中可能还有一些旧的 TCP 报文没有到达。
如果客户端马上关闭,然后立刻用相同的四元组建立新连接:
源 IP
源端口
目标 IP
目标端口
那么旧连接中的残留报文可能会被新连接误收。
所以 TIME_WAIT 等待一段时间,让旧报文在网络中消失,避免影响新连接。
七、为什么是 2MSL?
MSL 是:
Maximum Segment Lifetime
意思是:
一个 TCP 报文在网络中最多能存活的时间。
为什么是 2MSL?
因为最后一次通信可能经历两个方向:
客户端 ACK 到服务端:最多 1MSL
服务端重发 FIN 到客户端:最多 1MSL
所以等待 2MSL,大致可以保证:
旧报文都消失了
服务端如果没收到 ACK,也有足够时间重发 FIN
如果 2MSL 内客户端没有再收到 FIN,就可以认为服务端已经收到了最后的 ACK。
八、TIME_WAIT 过多怎么办?
先记住一句话:
谁主动关闭连接,谁进入 TIME_WAIT。
所以如果服务端有大量 TIME_WAIT,说明很多连接是服务端主动关闭的。
常见原因:
短连接太多
高并发
服务端主动关闭连接
HTTP/1.0 短连接
没有使用连接池
解决思路
1. 使用长连接
例如 HTTP/1.1 默认支持长连接。
多个请求复用一个 TCP 连接,不用每次请求都新建和关闭连接。
这样 TIME_WAIT 会少很多。
2. 使用连接池
比如数据库连接池、HTTP 客户端连接池。
不要频繁创建和销毁 TCP 连接。
3. 尽量让客户端主动关闭
因为主动关闭的一方进入 TIME_WAIT。
如果服务端主动关闭太多,TIME_WAIT 就堆在服务端。
可以通过协议设计,让客户端请求结束后主动关闭连接。
4. 调整内核参数
比如:
tcp_tw_reuse
tcp_max_tw_buckets
ip_local_port_range
不过这个属于系统层调优,要谨慎。
特别是所谓的 TIME_WAIT 快速回收,在新版本 Linux 中已经不推荐甚至被移除,因为在 NAT 场景下容易出问题。
九、面试版总结
你可以这样回答:
TCP 三次握手是为了建立可靠连接。第一次客户端发送 SYN,携带客户端初始序列号;第二次服务端回复 SYN + ACK,确认客户端序列号并携带服务端初始序列号;第三次客户端回复 ACK,确认服务端序列号。三次握手可以防止历史连接造成服务端资源浪费,也可以保证双方初始序列号可靠同步。
TCP 四次挥手是为了关闭双向连接。客户端发送 FIN 表示自己不再发送数据,服务端回复 ACK 表示收到;但服务端可能还有数据要发,所以等服务端数据发送完后,再发送 FIN;客户端收到后回复 ACK,进入 TIME_WAIT,等待 2MSL 后关闭。TIME_WAIT 是为了保证最后一个 ACK 能被服务端收到,同时避免旧连接报文影响新连接。主动关闭连接的一方会进入 TIME_WAIT。
这里有两个容易混的点:Seq / Ack 是 TCP 报文头里的字段,而 SYN / ACK 是标志位。
十、举个例子解惑
1. Seq 是啥?
Seq = Sequence Number,序列号。
它表示:
我这次发送的数据,从哪个编号开始。
TCP 是字节流协议,会把传输的每个字节都编号。
比如客户端初始序列号是:
client_isn = 1000
第一次握手发 SYN:
Seq = 1000
意思是:
我的初始序列号是 1000。
注意:SYN 报文本身也会占用一个序列号,所以服务端确认时要回复:
Ack = 1001
也就是:
我已经收到你的 SYN 了,下次你应该从 1001 开始发。
2. SYN + ACK 里面,哪个是确认?哪个是携带?
服务端第二次握手发的是:
SYN = 1
ACK = 1
Seq = server_isn
Ack = client_isn + 1
分开看:
SYN = 1
表示:
服务端也要发起连接请求。
因为 TCP 是双向连接,客户端要连接服务端,服务端也要确认自己这边可以建立连接。
ACK = 1
表示:
这个报文里面的确认号 Ack 字段有效。
也就是说,ACK 标志位只是一个开关。
Seq = server_isn
这个是携带服务端的初始序列号。
意思是:
这是我的初始序列号 server_isn。
Ack = client_isn + 1
这个是确认客户端的序列号。
意思是:
你的 SYN 我收到了,你的 client_isn 我也收到了。你下次应该从 client_isn + 1 开始发。
所以第二次握手可以这样理解:
SYN = 1 我也想建立连接
ACK = 1 我的确认号字段有效
Seq = server_isn 这是我的初始序列号
Ack = client_isn + 1 我确认收到了你的 SYN
其中:
确认客户端序列号的是:Ack = client_isn + 1
携带服务端初始序列号的是:Seq = server_isn
3. ACK 和 Ack 为啥一个大写一个小写?
这是为了区分两个不同概念。
ACK,大写
表示 ACK 标志位。
它只有两种状态:
ACK = 1
ACK = 0
意思是:
ACK = 1:确认号字段有效
ACK = 0:确认号字段无效
它是一个标志位。
Ack / ack,小写
表示 Acknowledgment Number,确认号字段。
比如:
Ack = client_isn + 1
Ack = server_isn + 1
它是一个具体的数字。
4. 举个完整例子
假设:
client_isn = 1000
server_isn = 8000
第一次握手
客户端发给服务端:
SYN = 1
Seq = 1000
意思是:
我要连接你,我的初始序列号是 1000。
第二次握手
服务端发给客户端:
SYN = 1
ACK = 1
Seq = 8000
Ack = 1001
意思是:
我收到你的 SYN 了,你下次从 1001 开始发。
同时我也要建立连接,我的初始序列号是 8000。
第三次握手
客户端发给服务端:
ACK = 1
Seq = 1001
Ack = 8001
意思是:
我收到你的 SYN 了,你下次从 8001 开始发。
到这里,双方都确认了对方的初始序列号。
一句话总结:
Seq 是我发出去的编号,Ack 是我期望你下次发来的编号;ACK 是确认标志位,Ack 是确认号字段。


