ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux网络(十八):TCP连接管理详解:从三次握手、四次挥手到CLOSE_WAIT与TIME_WAIT,深入理解2MSL与端口复用

Linux网络(十八):TCP连接管理详解:从三次握手、四次挥手到CLOSE_WAIT与TIME_WAIT,深入理解2MSL与端口复用 ◆ 博主名称 小此方-CSDN博客大家好欢迎来到小此方的博客。⭐️网络系列个人专栏 【主题曲】计算机网络⭐️此方的GitHub github_此方⭐️我们思考 (Rethink) · 我们重建 (Rebuild) · 我们记录 (Record)文章目录概要序論一、深入理解三次握手与四次挥手1.1 三次握手与系统底层的关系1.2 为什么一定要进行三次握手面试高频1.2.1 谋求共识与全双工验证1.2.2 握手的本质与“四次合并”逻辑1.3 四次挥手与半关闭状态1.3.1 拆分机制与单向信道断开1.3.2 延迟确认与谋求共识的区别1.4 CLOSE_WAIT 状态与文件描述符泄漏1.4.1 CLOSE_WAIT 的产生与危害1.4.2 系统解耦与 shutdown 接口1.5 TIME_WAIT 状态与地址复用SO_REUSEADDR1.5.1 TIME_WAIT 的困境与生产痛点1.5.2 解决方案SO_REUSEADDR 端口复用1.6 深入理解 TIME_WAIT 的本质与 2MSL 等待1.6.1 2MSL 时间的定义与作用1.6.2 网络“流弹”场景深度剖析1.6.3TIME_WAIT状态保护的保护作用1.6.4既要解决网络流弹又不想等待TIME_WAIT的辅助方案TCP随机序列号ISN机制二、连接状态2.1 服务端状态转化2.2 客户端状态转化概要序論Hello大家好我是此方。上文开始我们进入TCP的深入理解阶段本文继续讲解在前文的基础上深入理解三次握手与四次挥手的详细内容。一、深入理解三次握手与四次挥手1.1 三次握手与系统底层的关系在深入分析握手过程之前我们需要先思考一个问题连接要不要被管理要先描述在组织建立连接是有成本的包含时间成本与空间成本例如数据结构struct Link。三次握手的具体过程有客户端和服务器的操作系统自动完成而connect只是负责发起三次握手accept不参与三次握手。accept负责把已经建立好的连接拿上来1.2 为什么一定要进行三次握手面试高频1.2.1 谋求共识与全双工验证男女双方要达成夫妻的条件是什么双方谋求共识双方父母等外部条件得到满足TCP 建立连接选择三次握手主要有两个核心理由第一以最小成本100%确认双方通信意愿。第二是以最短的方式进行验证全双工本质是验证我们两个所处的网络是通畅的能够支持全双工1.2.2 握手的本质与“四次合并”逻辑三次握手真的只是三次握手吗错误的三次握手实际上是四次握手只不过对端携带应答把第一次握手的应答连同第二次握手的报文一块儿发过去了。为什么“三次握手”本质上是三次而不是“四次合并”建立连接时双方的诉求是一致的即双方都要确认对方的接收与发送能力。服务器收到客户端的 SYN 后由于自己也需要发起建立连接并且顺便确认客户端的请求发送 ACK这两件事在逻辑上是同时发生的。因此TCP 协议从底层规范上就把它们设计在同一个报头文段里SYNACK 标志位同时置1这本身就是单次原子操作而不是被迫把两次通信“拼凑”在一起。1.3 四次挥手与半关闭状态1.3.1 拆分机制与单向信道断开在断开连接时客户端和服务器断开连接需要进行四次挥手。为什么“四次挥手”通常不能合并成三次半关闭状态Half-CloseTCP 是全双工通信。当客户端发送 FIN 断开连接时只代表客户端不再发送数据了但此时服务器可能还有没发完的数据要继续传给客户端。拆分机制服务器必须先立即回复一个 ACK告诉客户端“你的断开请求我收到了”随后继续传输剩余数据。等服务器把所有数据发完后才会单独再发一个 FIN 给客户端。因此挥手过程天然被拆成了 4 个阶段FIN - ACK - FIN - ACK。1.3.2 延迟确认与谋求共识的区别只有在极极其特殊且刚好没有残留数据要发的情况下TCP 才会开启延迟确认机制将中途的 ACK 和 FIN 偶尔地合并发送但这并不是常态。总的来说为什么四次挥手不能合并因为三次握手的时候“更加容易谋求共识”而四次挥手的时候客户端和服务器断开的时间往往不能谋求一致第二次挥手和第三次挥手无法实现捎带应答。1.4 CLOSE_WAIT 状态与文件描述符泄漏1.4.1 CLOSE_WAIT 的产生与危害在四次挥手过程中如果客户端主动发起断开请求发送 FIN服务端在收到后会回应 ACK此时服务端就会进入CLOSE_WAIT状态。如果客户端已经退出或者关闭而服务器端就是不关闭代码中没有显式调用close(sockfd)服务器端就会一直处于 CLOSE_WAIT 的状态这种情况下连接并没有被彻底释放会引发文件描述符的资源泄漏。1.4.2 系统解耦与 shutdown 接口当然实际开发中并不需要这么麻烦我们的应用层和操作系统是解耦的。我们直接在两端调用close正常关闭描述符就可以了。至于这种问题交给操作系统操作系统会帮我们完成不用担心服务器的剩余发送数据被丢弃的情况。我们的write接口没有直接把报文发送到网络中的能力它是将报文从应用层拷贝给缓冲区。有操作系统将缓冲区中的内容发送到网络。于是操作系统就可以执行流量控制和超时重传等操作这也是一种解耦。此外系统还提供了一个系统调用shutdown它可以根据你传递的选项自由选择关闭文件描述符的读端、写端或者是全部关闭。例如设置为SHUT_WR时客户端可以关闭写的单向信道保留读端读取服务器发送过来的剩余消息即将全双工变成半双工。1.5 TIME_WAIT 状态与地址复用SO_REUSEADDR1.5.1 TIME_WAIT 的困境与生产痛点主动断开连接的一方在完成四次挥手后要进入一个状态叫做TIME_WAIT。即便四次挥手完成主动关闭方也不会立刻回到 CLOSED 状态。从应用角度看既要 TIME_WAIT又要让服务器立即重启但是 TIME_WAIT 解决问题的同时自身也有很大的问题在一些场景中如果我们的服务器突然挂掉了这个时候必须以“相同端口”的方式高度重构重启。比如在双11期间大量客户涌入服务器服务器炸掉了这个时候如果等待 TIME_WAIT 面而不立刻重启会引发巨大的经济损失。1.5.2 解决方案SO_REUSEADDR 端口复用我们有解决方案使用SO_REUSEADDR选项它允许套接字强制绑定并重用处于 TIME_WAIT 状态的已有 IP 和端口支持服务器挂掉后以完全相同的 IP/端口立即重启intopt1;setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,opt,sizeof(opt));1.6 深入理解 TIME_WAIT 的本质与 2MSL 等待1.6.1 2MSL 时间的定义与作用TCP 协议规定主动关闭连接的一方要处于 TIME_WAIT 状态等待两个 MSLMaximum Segment Lifetime最大报文生存时间的时间后才能回到 CLOSED 状态。MSL 是 TCP 报文的最大生存时间因此 TIME_WAIT 持续存在 2MSL 的话就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失否则服务器立刻重启可能会收到来自上一个进程的迟到数据导致数据混乱。1.6.2 网络“流弹”场景深度剖析如何理解 TIME_WAITTIME_WAIT 解决的是一种可能性非常低的 bug。我们客户端和服务器在建立通信正常交流的时候可能存在这样一个报文这个报文有以下性质没有到达 MSL依然在网络中保持存活状态。可能阻塞在网络中的任何一个路由器的阻塞队列中。超时引发发送端的超时重传机制。通信两端全部关闭。阻塞时间确实很久但是没有达到 MSL。两台主机关闭后立即重启重启使用的通信 IP 和端口号全部和上一次使用的 IP 与端口一致。就在两台主机重启后建立连接的某一次握手刚好到达了目标主机。干扰了三次握手导致三次握手失败。所以我们需要在上次连接断开之前先等待 2MSL 的时间让这种流弹保证在上一次连接断开后到达目标主机后被丢弃或者在网络中由于到达 MSL 被丢弃。这种情况发生的可能性非常低但是只要不是 0 就会在每天数以亿计的世界网络通信中天天发生。1.6.3TIME_WAIT状态保护的保护作用主动断开的一方处于 TIME_WAIT 的状态的时候是不能在以同样的端口号IP地址绑定重启的一定会发生 bind error。其实 TIME_WAIT 实际上也倒逼着我们的用户在重启的时候不使用原来的端口号。如果我们使用新的端口号重新启动“陈旧报文”到来的时候接收方会去判断它的四元组源端口 源IP 目的端口 目的IP如果有一个不一样就会直接丢弃报文。TIME_WAIT 还有一个功能这是比较容易想到的如果发起方发送的最后的那个 ACK 没有到达对端那么对端就可以发送一个 FIN。一来一回2MSL 刚好可以覆盖这个过程。——确保我发出去的 ACK 可以被正常接收到。换句话说我在 TIME_WAIT 的期间。没有收到消息就是好消息。没有收到消息就代表我发出的 ACK 到达了。1.6.4既要解决网络流弹又不想等待TIME_WAIT的辅助方案TCP随机序列号ISN机制那么先前的问题难道不解决了吗当然解决。我们采用另外一种辅助方案客户端和服务器通信的时候报文的起始序号起始时非固定的比如我这一次启动客户端发送的报文的起始序号是1000下一次起始序号就是1428。如果接收方发现接收到的报文的序号与当前通信过程中的报文序号存在巨大出入那么就会丢弃它。比如我的缓冲区大小是 5000我和对面协商好它从 1000 开始发送但是这个时候突然出来一个 7000 序号的报文于是这个时候这个报文必须丢弃连接管理机制我们正式讲完了。其实上面的各种连接状态我只讲解 了最重要的两个剩下的状态统一展出二、连接状态2.1 服务端状态转化在 TCP 通信的整个生命周期中服务端的状态演变如下[CLOSED - LISTEN]服务器端调用 listen 后进入 LISTEN 状态等待客户端连接[LISTEN - SYN_RCVD]一旦监听收到连接请求(同步报文段)就会将该连接放入内核等待队列中并向客户端发送 SYN 确认报文。[SYN_RCVD - ESTABLISHED]服务端一旦收到客户端的确认报文就进入 ESTABLISHED 状态可以进行读写数据了。[ESTABLISHED - CLOSE_WAIT]当客户端主动关闭连接(调用 close)服务器会收到结束报文段服务器返回确认报文段并进入 CLOSE_WAIT[CLOSE_WAIT - LAST_ACK]进入 CLOSE_WAIT 后说明服务器准备关闭连接(需要处理完之前的数据)当服务器真正调用 close 关闭连接时会向客户端发送 FIN此时服务器进入 LAST_ACK 状态等待最后一个 ACK 到来(这个 ACK 是客户端确认收到了 FIN)[LAST_ACK - CLOSED]服务器收到了对 FIN 的 ACK彻底关闭连接。2.2 客户端状态转化相对应地客户端的状态转化路径如下[CLOSED - SYN_SENT]客户端调用 connect发送同步报文段[SYN_SENT - ESTABLISHED]connect 调用成功则进入 ESTABLISHED 状态开始读写数据[ESTABLISHED - FIN_WAIT_1]客户端主动调用 close 时向服务器发送结束报文段同时进入 FIN_WAIT_1[FIN_WAIT_1 - FIN_WAIT_2]客户端收到服务器对结束报文段的确认则进入 FIN_WAIT_2开始等待服务器的结束报文段[FIN_WAIT_2 - TIME_WAIT]客户端收到服务器发来的结束报文段进入 TIME_WAIT并发出 LAST_ACK[TIME_WAIT - CLOSED]客户端要等待一个 2MSL(Max Segment Life, 报文最大生存时间)的时间才会进入 CLOSED 状态。以上是一张汇总图。较粗的虚线表示服务端的状态变化情况较粗的实线表示客户端的状态变化情况CLOSED是一个假想的起始点不是真实状态好的本期内容就到这里如果对你有帮助还不要忘记点赞三联支持,如果有什么疑问可以再后台私信我。我是此方我们下期再见。bye!
返回列表