一、为什么是三次握手?而不是两次呢?
有三个原因:
1)第一个原因也是主要原因:防止旧的重复连接建立造成混乱。网络拥堵的情况下,客户端可能发送多次连接请求,而旧的连接请求可能先到达服务端,这时如果是两次握手,服务端返回 ACK 报文就建立了连接,等到新的连接请求到达后,此时旧连接已经建立,所以会造成混乱;三次握手就可以避免这种情况,当服务端返回 ACK 报文时,客户端发现 ACKnum 与预期不符,就会发送 RST(Reset) 报文给服务端表示重置连接,服务端回退到 LISTEN 状态;
接收方收到 RST 报文时将采取以下三种措施:
- 接收方处于非同步状态(
SYN_SENT, SYN-RECEIVED
),则返回到 LISTEN 状态;- 接收方处于同步状态(
ESTABLISHED, FIN-WAIT-1, FIN-WAIT-2, CLOSE-WAIT, CLOSING, LAST-ACK, TIME-WAIT
),则终止连接,回到 CLOSED 状态;
2)为了保证双方都具有接收和发送能力:如果只有两次握手,那么服务端不知道自己的发送能力和客户端的接收能力是否正常;如果三次握手,客户端返回确认消息,服务端收到消息,就可以确认自己的发送能力和客户端的接受能力正常;
3)防止已经失效的连接请求到达服务端导致建立连接浪费资源:当客户端发送的 SYN 报文被阻塞,客户端又重发了 SYN 报文,成功与服务端建立连接,然后传输完数据后关闭了连接,此后失效的 SYN 报文到达服务端,如果是两次握手,会再次建立一个连接,服务端会一直等待客户端发送数据从而浪费了资源。
二、 为什么建立连接握手三次,关闭连接时需要是四次呢?
因为建立连接的时候服务端发送的报文中同时设置了 SYN 和 ACK 两个控制位,所以减少了一次报文的发送;而关闭连接时,主动关闭方发送控制位 FIN 置 1 的报文,表示数据已经发送完毕,可以关闭连接,但此时接收方可能还在发送数据,不能立刻关闭数据传输通道,所以不能把 FIN 包和 ACK 包同时发送,接收方先发送 ACK 包确认,然后等待应用进程发来通知说数据发送完毕了,再发送 FIN 包。
三、 为什么 TIME_WAIT 状态需要经过 2MSL 才能返回到 CLOSED 状态?
MSL(maximum segment lifetime)
:TCP 定义 MSL 为 120s,同时允许修改这个值;
两个原因:
1)保证客户端发送的最后一个 ACK 包能到达服务端,当这个包丢失时,服务端会再次发送 FIN 包,所以 2MSL=ACK 包到达服务端 + 服务端重发 FIN 包,接着客户端发送 ACK 包,并重启 2MSL 计时器;
2)保证关闭连接前,本次连接中产生的所有数据报文都从网络中消失,防止与新的连接混淆。
四、 为什么 FIN 和 SYN 要额外消耗一个序号?
因为 FIN 包和 SYN 包都需要确认,如果不占用序号的话,就无法辨别 ACK 包确认的是哪一个报文。
五、 双方同时发送请求建立 TCP 连接,会发生什么?
TCP A TCP B
1. CLOSED CLOSED
2. SYN-SENT --> <SEQ=100><CTL=SYN> ...
3. SYN-RECEIVED <-- <SEQ=300><CTL=SYN> <-- SYN-SENT
4. ... <SEQ=100><CTL=SYN> --> SYN-RECEIVED
5. SYN-RECEIVED --> <SEQ=100><ACK=301><CTL=SYN,ACK> ...
6. ESTABLISHED <-- <SEQ=300><ACK=101><CTL=SYN,ACK> <-- SYN-RECEIVED
7. ... <SEQ=101><ACK=301><CTL=ACK> --> ESTABLISHED
Simultaneous Connection Synchronization
首先解释一下图中符号的含义:
- 右箭头 (-->) :从 A 发送到 B 的 TCP 报文段,或者 B 接收到了 A 的报文;
- 左箭头 (<--) :从 B 发送到 A 的 TCP 报文段,或者 A 接收到了 B 的报文;
- 省略号 (…) :TCP 报文段仍滞留在网络中(delayed);
- 丢失 (XXX) :TCP 报文段丢失或者被拒绝;
- 注释会放在括号中;
- TCP 状态代表了处于中间的报文段发出或者到达之后的状态(AFTER);
先说结论:双方同时发送请求建立 TCP 连接,最终只会建立一个 TCP 连接。
接着我们逐行来分析这个连接过程发生了什么:
- 双方都处于
CLOSED
状态; - 此时 A 发送 SYN 报文请求建立 TCP 连接;
- A 发送的连接报文还未到达 B 时,B 也发送一个报文请求建立连接,A 收到后就进入
SYN-RECEIVED
状态; - A 发送的连接报文到达 B,B 也进入
SYN-RECEIVED
状态; - A 发送 SYN+ACK 报文,表示请求建立连接同时确认应答 B 发送的 SYN 报文,注意,这里的序列号 SEQ 是初始序列号,所以与第 1 次发送的序列号一致;
- B 也同时发送 SYN+ACK 报文,A 收到后就进入
ESTABLISHED
状态,表示成功建立连接; - 最后 A 发送的 SYN+ACK 报文到达 B 端,我们注意到,第 7 行 B 收到的报文与第 5 行 A 发送的报文不一样,这是为什么呢?这是因为前面 B 已经收到 A 发送的 SEQ=100 的报文了(见第 4 行),所以实际上 B 只接收 A 发送的从序列号 101 开始的 ACK 报文,这就是第 7 行和第 5 行的报文不一样的原因。B 收到报文后也进入
ESTABLISHED
状态,最终只建立了一个 TCP 连接。
5. SYN-RECEIVED --> <SEQ=100><ACK=301><CTL=SYN,ACK> ...
7. ... <SEQ=101><ACK=301><CTL=ACK> --> ESTABLISHED
六、 在建立 TCP 连接时,一个旧的 SYN 报文到达服务端,会发生什么?
TCP A TCP B
1. CLOSED LISTEN
2. SYN-SENT --> <SEQ=100><CTL=SYN> ...
3. (duplicate) ... <SEQ=90><CTL=SYN> --> SYN-RECEIVED
4. SYN-SENT <-- <SEQ=300><ACK=91><CTL=SYN,ACK> <-- SYN-RECEIVED
5. SYN-SENT --> <SEQ=91><CTL=RST> --> LISTEN
6. ... <SEQ=100><CTL=SYN> --> SYN-RECEIVED
7. SYN-SENT <-- <SEQ=400><ACK=101><CTL=SYN,ACK> <-- SYN-RECEIVED
8. ESTABLISHED --> <SEQ=101><ACK=401><CTL=ACK> --> ESTABLISHED
Recovery from Old Duplicate SYN
当服务端 B 处于 SYN-RECEIVED 状态时,来自客户端 A 的一个旧 SYN 报文到达:
- 服务端无法判断这个报文是否是旧的,所以正常返回应答报文;
- 客户端收到 ACK 报文后发现 ACKnum(应答号) 不正确,所以返回一个 RST 报文表示重置;
- 服务器收到 RST 报文后,回到 LISTEN 监听状态;
七、 在一个正常通信的 TCP 连接中,某一端崩溃了,另一端继续发送数据,会发生什么?
崩溃方的操作系统内核会发送一个 RST 重置报文到发送方,发送方收到 RST 报文后会终止连接。
八、 第三次握手失败了,会发生什么?
我们先看下基本的三次握手过程:
TCP A TCP B
1. CLOSED LISTEN
2. SYN-SENT --> <SEQ=100><CTL=SYN> --> SYN-RECEIVED
3. ESTABLISHED <-- <SEQ=300><ACK=101><CTL=SYN,ACK> <-- SYN-RECEIVED
4. ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK> --> ESTABLISHED
5. ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK><DATA> --> ESTABLISHED
Basic 3-Way Handshake for Connection Synchronization
在上述过程中的第 4 行,A 发送的应答报文丢失了,此时 B 处于 SYN-RECEIVED 状态,会发生什么?
- 当计时器超时了而 B 还没收到 A 的应答报文,B 就认为它之前发送的 SYN+ACK 报文已丢失,会触发超时重传,重传次数默认 5 次;
- 如果重传指定次数后还没收到应答,则 B 就会关闭连接;
此时 B 可能处于 SYN-RECEIVED 状态或者 CLOSED 状态:
- CLOSED 状态:B 发送重置报文给 A,A 收到后就关闭连接;
- SYN-RECEIVED 状态:如果 B 之后收到正常的应答报文(第 4 行),那么连接成功建立;如果收到传输数据的请求(第 5 行),那么连接也能成功建立,因为传输数据的报文包含了应答号;
参考资料:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。