欢迎光临
我们一直在努力

【计算机网络】TCP三握四挥

可以这样理解: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 是确认号字段。

赞(0)
未经允许不得转载:171主机测评 » 【计算机网络】TCP三握四挥
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址