5
5
- 计算机网络
6
6
---
7
7
8
- 为了准确无误地把数据送达目标处 ,TCP 协议采用了三次握手策略 。
8
+ TCP 是一种面向连接的、可靠的传输层协议。为了在两个不可靠的端点之间建立一个可靠的连接 ,TCP 采用了三次握手(Three-way Handshake)的策略 。
9
9
10
10
## 建立连接-TCP 三次握手
11
11
12
12
![ TCP 三次握手图解] ( https://oss.javaguide.cn/github/javaguide/cs-basics/network/tcp-shakes-hands-three-times.png )
13
13
14
14
建立一个 TCP 连接需要“三次握手”,缺一不可:
15
15
16
- - ** 一次握手** :客户端发送带有 SYN(SEQ=x) 标志的数据包 -> 服务端,然后客户端进入 ** SYN_SEND** 状态,等待服务端的确认;
17
- - ** 二次握手** :服务端发送带有 SYN+ACK(SEQ=y,ACK=x+1) 标志的数据包 –> 客户端,然后服务端进入 ** SYN_RECV** 状态;
18
- - ** 三次握手** :客户端发送带有 ACK(ACK=y+1) 标志的数据包 –> 服务端,然后客户端和服务端都进入** ESTABLISHED** 状态,完成 TCP 三次握手。
16
+ 1 . ** 第一次握手 (SYN)** : 客户端向服务端发送一个 SYN(Synchronize Sequence Numbers)报文段,其中包含一个由客户端随机生成的初始序列号(Initial Sequence Number, ISN),例如 seq=x。发送后,客户端进入 ** SYN_SEND** 状态,等待服务端的确认。
17
+ 2 . ** 第二次握手 (SYN+ACK)** : 服务端收到 SYN 报文段后,如果同意建立连接,会向客户端回复一个确认报文段。该报文段包含两个关键信息:
18
+ - ** SYN** :服务端也需要同步自己的初始序列号,因此报文段中也包含一个由服务端随机生成的初始序列号,例如 seq=y。
19
+ - ** ACK** (Acknowledgement):用于确认收到了客户端的请求。其确认号被设置为客户端初始序列号加一,即 ack=x+1。
20
+ - 发送该报文段后,服务端进入 ** SYN_RECV** 状态。
21
+ 3 . ** 第三次握手 (ACK)** : 客户端收到服务端的 SYN+ACK 报文段后,会向服务端发送一个最终的确认报文段。该报文段包含确认号 ack=y+1。发送后,客户端进入 ** ESTABLISHED** 状态。服务端收到这个 ACK 报文段后,也进入 ** ESTABLISHED** 状态。
19
22
20
- 当建立了 3 次握手之后,客户端和服务端就可以传输数据啦!
23
+ 至此,双方都确认了连接的建立,TCP 连接成功创建,可以开始进行双向数据传输。
21
24
22
25
### 什么是半连接队列和全连接队列?
23
26
24
- 在 TCP 三次握手过程中,Linux 内核会维护两个队列来管理连接请求 :
27
+ 在 TCP 三次握手过程中,服务端内核会使用两个队列来管理连接请求 :
25
28
26
- 1 . ** 半连接队列** (也称 SYN Queue):当服务端收到客户端的 SYN 请求时,此时双方还没有完全建立连接,它会把半连接状态的连接放在半连接队列 。
29
+ 1 . ** 半连接队列** (也称 SYN Queue):当服务端收到客户端的 SYN 请求并回复 SYN+ACK 后,连接会处于 SYN_RECV 状态。此时,这个连接信息会被放入半连接队列。这个队列存储的是尚未完成三次握手的连接 。
27
30
2 . ** 全连接队列** (也称 Accept Queue):当服务端收到客户端对 ACK 响应时,意味着三次握手成功完成,服务端会将该连接从半连接队列移动到全连接队列。如果未收到客户端的 ACK 响应,会进行重传,重传的等待时间通常是指数增长的。如果重传次数超过系统规定的最大重传次数,系统将从半连接队列中删除该连接信息。
28
31
29
- 这两个队列的存在是为了处理并发连接请求,确保服务端能够有效地管理新的连接请求。另外,新的连接请求被拒绝或忽略除了和每个队列的大小限制有关系之外,还和很多其他因素有关系,这里就不详细介绍了,整体逻辑比较复杂。
32
+ 这两个队列的存在是为了处理并发连接请求,确保服务端能够有效地管理新的连接请求。
33
+
34
+ 如果全连接队列满了,新的已完成握手的连接可能会被丢弃,或者触发其他策略。这两个队列的大小都受系统参数控制,它们的容量限制是影响服务器处理高并发连接能力的重要因素,也是 SYN 泛洪攻击(SYN Flood)所针对的目标。
30
35
31
36
### 为什么要三次握手?
32
37
33
- 三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。
38
+ TCP 三次握手的核心目的是为了在客户端和服务器之间建立一个** 可靠的** 、** 全双工的** 通信信道。这需要实现两个主要目标:
39
+
40
+ ** 1. 确认双方的收发能力,并同步初始序列号 (ISN)**
41
+
42
+ TCP 通信依赖序列号来保证数据的有序和可靠。三次握手是双方交换和确认彼此初始序列号(ISN)的过程,通过这个过程,双方也间接验证了各自的收发能力。
43
+
44
+ - ** 第一次握手 (客户端 → 服务器)** :客户端发送 SYN 包。
45
+ - 服务器:能确认客户端的发送能力正常,自己的接收能力正常。
46
+ - 客户端:无法确认任何事。
47
+ - ** 第二次握手 (服务器 → 客户端)** :服务器回复 SYN+ACK 包。
48
+ - 客户端:能确认自己的发送和接收能力正常,服务器的接收和发送能力正常。
49
+ - 服务端:能确认对方发送能力正常,自己接收能力正常
50
+ - ** 第三次握手 (客户端 → 服务器)** :客户端发送 ACK 包。
51
+ - 客户端:能确认双方发送和接收能力正常。
52
+ - 服务端:能确认双方发送和接收能力正常。
53
+
54
+ 经过这三次交互,双方都确认了彼此的收发功能完好,并完成了初始序列号的同步,为后续可靠的数据传输奠定了基础。
55
+
56
+ ** 2. 防止已失效的连接请求被错误地建立**
57
+
58
+ 这是“为什么不能是两次握手”的关键原因。
34
59
35
- 1 . ** 第一次握手** :Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常
36
- 2 . ** 第二次握手** :Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:对方发送正常,自己接收正常
37
- 3 . ** 第三次握手** :Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常
60
+ 设想一个场景:客户端发送的第一个连接请求(SYN1)因网络延迟而滞留,于是客户端重发了第二个请求(SYN2)并成功建立了连接,数据传输完毕后连接被释放。此时,延迟的 SYN1 才到达服务端。
38
61
39
- 三次握手就能确认双方收发功能都正常,缺一不可。
62
+ - ** 如果是两次握手** :服务端收到这个失效的 SYN1 后,会误认为是一个新的连接请求,并立即分配资源、建立连接。但这将导致服务端单方面维持一个无效连接,白白浪费系统资源,因为客户端并不会有任何响应。
63
+ - ** 有了第三次握手** :服务端收到失效的 SYN1 并回复 SYN+ACK 后,会等待客户端的最终确认(ACK)。由于客户端当前并没有发起连接的意图,它会忽略这个 SYN+ACK 或者发送一个 RST (Reset) 报文。这样,服务端就无法收到第三次握手的 ACK,最终会超时关闭这个错误的连接,从而避免了资源浪费。
40
64
41
- 更详细的解答可以看这个: [ TCP 为什么是三次握手,而不是两次或四次? - 车小胖的回答 - 知乎 ] ( https://www.zhihu.com/question/24853633/answer/115173386 ) 。
65
+ 因此,三次握手是确保 TCP 连接可靠性的 ** 最小且必需 ** 的步骤。它不仅确认了双方的通信能力,更重要的是增加了一个最终确认环节,以防止网络中延迟、重复的历史请求对连接建立造成干扰 。
42
66
43
67
### 第 2 次握手传回了 ACK,为什么还要传回 SYN?
44
68
58
82
59
83
断开一个 TCP 连接则需要“四次挥手”,缺一不可:
60
84
61
- 1 . ** 第一次挥手** :客户端发送一个 FIN(SEQ=x) 标志的数据包->服务端,用来关闭客户端到服务端的数据传送。然后客户端进入 ** FIN-WAIT-1** 状态。
62
- 2 . ** 第二次挥手** :服务端收到这个 FIN(SEQ=X) 标志的数据包,它发送一个 ACK (ACK=x+1)标志的数据包->客户端 。然后服务端进入 ** CLOSE-WAIT** 状态,客户端进入 ** FIN-WAIT-2** 状态。
63
- 3 . ** 第三次挥手** :服务端发送一个 FIN (SEQ=y)标志的数据包->客户端,请求关闭连接,然后服务端进入 ** LAST-ACK** 状态。
64
- 4 . ** 第四次挥手** :客户端发送 ACK ( ACK=y+1)标志的数据包->服务端,然后客户端进入 ** TIME-WAIT** 状态,服务端在收到 ACK (ACK=y+1)标志的数据包后进入 CLOSE 状态。此时如果客户端等待 ** 2MSL** 后依然没有收到回复,就证明服务端已正常关闭,随后客户端也可以关闭连接了 。
85
+ 1 . ** 第一次挥手 (FIN) ** :当客户端(或任何一方)决定关闭连接时,它会向服务端发送一个 ** FIN** (Finish)标志的报文段,表示自己已经没有数据要发送了。该报文段包含一个序列号 seq=u。发送后,客户端进入 ** FIN-WAIT-1** 状态。
86
+ 2 . ** 第二次挥手 (ACK) ** :服务端收到 FIN 报文段后,会立即回复一个 ** ACK** 确认报文段。其确认号为 ack=u+1。发送后,服务端进入 ** CLOSE-WAIT** 状态。客户端收到这个 ACK 后,进入 ** FIN-WAIT-2** 状态。此时,TCP 连接处于 ** 半关闭(Half-Close) ** 状态:客户端到服务端的发送通道已关闭,但服务端到客户端的发送通道仍然可以传输数据 。
87
+ 3 . ** 第三次挥手 (FIN) ** :当服务端确认所有待发送的数据都已发送完毕后,它也会向客户端发送一个 ** FIN** 报文段,表示自己也准备关闭连接。该报文段同样包含一个序列号 seq=y。发送后,服务端进入 ** LAST-ACK** 状态,等待客户端的最终确认 。
88
+ 4 . ** 第四次挥手** :客户端收到服务端的 FIN 报文段后,会回复一个最终的 ** ACK** 确认报文段,确认号为 ack =y+1。发送后,客户端进入 ** TIME-WAIT** 状态。服务端在收到这个 ACK 后,立即进入 ** CLOSED ** 状态,完成连接关闭。客户端则会在 ** TIME-WAIT ** 状态下等待 ** 2MSL** (Maximum Segment Lifetime,报文段最大生存时间)后,才最终进入 ** CLOSED ** 状态 。
65
89
66
90
** 只要四次挥手没有结束,客户端和服务端就可以继续传输数据!**
67
91
@@ -82,7 +106,7 @@ TCP 是全双工通信,可以双向传输数据。任何一方都可以在数
82
106
83
107
### 如果第二次挥手时服务端的 ACK 没有送达客户端,会怎样?
84
108
85
- 客户端没有收到 ACK 确认,会重新发送 FIN 请求 。
109
+ 客户端在发送 FIN 后会启动一个重传计时器。如果在计时器超时之前没有收到服务端的 ACK,客户端会认为 FIN 报文丢失,并重新发送 FIN 报文 。
86
110
87
111
### 为什么第四次挥手客户端需要等待 2\* MSL(报文段最长寿命)时间后才进入 CLOSED 状态?
88
112
0 commit comments