声明:此文章仅是作者自学整理的内容,并非通篇原创,因思否无法设置转载多篇文章只能选择原创,详细内容可见文末『参考文章』。文章引用形式不严谨,敬请见谅。
1. 三次握手
三次握手的过程和状态变迁
- 刚开始,Client(客户端,下面简称 C)和 Server(服务端,下面简称 S)都处于 CLOSED 状态。S 开启服务,处于监听状态,LISTEN
C 发出第一个 SYN 报文,该报文不带数据,初始化了一个随机序列号(client_isn),标记 SYN 为 1。发送后,C 处于 SYN-SENT
S 收到报文后,将 client_isn + 1 作为确认应答号,自己随机生成了一个序列号 server_isn,同时标记 SYN 和 ACK 为 1,传给 C 第二个报文,这个报文也不带数据。发送后,S 处于 SYN-RCVD
- C 收到第二个报文后,将 server_isn + 1 作为确认应答号,标记 ACK 为 1,传给 S 第三个报文,这个报文可以带应用层数据。发送后,C 处于 ESTABLISHED,S 收到后也处于 ESTABLISHED,双方可以交换数据了。
为什么握手三次而不是两次、四次?
- 阻止重复的历史连接被初始化(主要原因)
如下图所示👇,假设出现旧的历史连接(比如网络拥塞产生)先于新的连接到达 S,S 基于旧的连接产生 server_isn。在三次握手的情况下,C 可以通过上下文比较判断这是一个历史连接,返回 RST 报文告诉 S 中止连接。如果两次握手,旧连接到达 S 后 S 返回报文,C 不进行第三次握手,就不足以产生足够的上下文判断这是否为历史连接,不能避免历史连接被初始化。 - 同步双方初始序列号
C 和 S 根据序列号来确定发送的顺序和完整性,所以同步序列号,保证可靠非常重要。因此,C 发出的 client_isn 被 S 接收,S 必须返回一个 ACK,同理,S 发出的 server_isn 被 C 接受,C 也必须返回一个 ACK。但由于第二次握手可以合并『确认 C 的序列号』和『发送 S 自身的序列号』两个动作,所以可以将 4 次略为 3 次。 - 避免资源浪费
和前面第 1 点提到的类似,如果遇到网络拥塞,C 发出的第一次握手没有被响应,就会触发超时重传,如果两次握手,那不仅重传的连接会被 S 分配资源建立连接,之前第 1 次发出的历史连接也会被分配资源建立连接,导致资源浪费。
小结:
- 不两次握手的原因:避免重复的历史连接被初始化,避免重传的连接导致资源浪费,避免序列号不同步。(类似的理解:避免已经失效的连接请求被服务器接收,产生错误。 ——谢希仁《计算机网络》)
- 不四次握手的原因:第二次握手将『确认应答客户端的序列号』和『发送服务器自身的序列号』两个动作合并,可以简化一步。是理论上最少的握手数,四次有冗余。
2. 四次挥手
四次挥手的过程和状态变迁
- 开始 C 和 S 两者都处于 ESTABLISHED 状态。C 想要中止连接,发送了一个首部 FIN 标记为 1 的连接,进入 FIN_WAIT_1 状态。
- S 收到报文后,发送一个 ACK 标记为 1 的报文,进入 CLOSED_WAIT 状态。
- C 收到应答报文后,进入 FIN_WAIT_2 状态。S 处理完剩余的数据后,发送含 FIN 的报文,进入 LAST_ACK 状态。
- C 收到 FIN 报文后,返回一个 ACK 报文,进入 TIME_WAIT状态。S 收到 ACK 报文后,关闭连接进入 CLOSED 状态。
- C 等待 2MSL 后,关闭连接进入 CLOSED 状态。
为什么挥手四次而不是三次?
C 发送 FIN 关闭连接请求后,并非马上进入 CLOSED 状态,还在等待剩余数据。
而此时 S 得知 C 有关闭请求的意图后,发送完 ACK 报文,还得把剩余的数据发送完,然后发送 FIN 告诉 C 表示现在要关闭了。
所以,和三次握手不同,这里发送 S 发送 ACK 和发送 FIN 不能合并成一步。因为要等剩余数据发送完。
为什么 TIME_WAIT 要等 2MSL?
什么是 MSL?
Max Segment Lifetime(最大报文生存时间),和它类似的概念有 TTL(Time To Live),IP 协议头中的一个字段,表示报文能经过的最大路由数,每经过一个路由器减 1,当 TTL 为 0 时丢弃该数据报并发送 ICMP 报文给源主机。MSL 应该是大于等于使 TTL 为 0 所需要的时间。
C 在收到 S 的 FIN 报文后从 FIN_WAIT_2 转 TIME_WAIT,发送出去的 ACK 如果没有被 S 接收到,那 S 会再发一次带 FIN 的报文。所以,S 重发 FIN 报文给 C 最多花 MSL,C 再发 ACK 给 S 最多 MSL,因此只需等待 2MSL。C 收到 FIN 后就开始计时,另外如果在 TIME_WAIT 中收到重传的 FIN 报文,2MSL会被重置。
Linux 系统中默认 2MSL 为 60 second。
为什么需要 TIME_WAIT 状态?
- 防止旧连接的数据包被相同端口的新连接接收,产生数据错乱。
- 保证连接正确关闭。就算 C 的 ACK 丢失,S 也会重发 FIN。
TIME_WAIT 过多有什么危害?
一般是针对服务器发起的挥手。S 处于 TIME_WAIT。
此时,有两个问题:
- 内存资源占用。
- 端口占用。端口是有限资源,如果占用过多会无法创建新连接。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。