首页 / 资讯中心 / 文章详情

计算机网络篇17:传输层——TCP 超时重传、三次握手与四次挥手,TIME_WAIT 为什么要等 2MSL

计算机网络篇17:传输层——TCP 超时重传、三次握手与四次挥手,TIME_WAIT 为什么要等 2MSL ★ FEATURED ARTICLE
本文收录于「流浪」的系列专栏Linux系统⚙️C数据结构与算法PythonLangChain LangGraph️MySQL 数据库Git 工具计算机网络LLM大厂面试、八股学习筑基专栏 博客主页流浪 原创首发于 CSDN前言TCP 号称可靠传输但可靠不是喊出来的数据会丢ACK 也会丢连接要建、也要断。本篇从「收不到 ACK 怎么判定丢包」切进去讲清超时重传与序号去重再把三次握手的必要性、四次挥手的共识以及TIME_WAIT 为什么必须等 2MSL串成一条线。一、超时重传收不到 ACK 不等于丢包1.1 丢包分两种情况1. 数据报文丢了主机 A 把数据发给主机 B报文在途中丢了B 根本没见过这封报文自然不会回 ACK。A 等不到确认过一段时间只能重发。这种情况下丢的是数据本身B 完全不知情。2. 应答报文丢了B 收到了数据回的 ACK 在途中丢了。从 A 的角度看和上一种一模一样等不到 ACK。区别在于 B 已经收下数据A 再重发时 B 会收到一份重复的报文。两种丢失在发送方眼里长得一样都是「等不到 ACK」——发送方没法只凭「没收到」区分是哪一种。1.2 没收到 ACK 只说明「可能丢」1. ACK 没来有三种可能数据丢了B 没收到自然不会回。数据到了B 回了 ACK但 ACK 在路上丢了。数据到了ACK 也没丢只是还在路上没走到 A。2. 判定丢包要两个条件单看「没收到 ACK」判不了丢包它只能说明「可能丢」。所以 TCP 加了第二个条件等等到超时。两个条件同时成立——收不到应答、且超时——才判定丢包触发重传。只凭「没收到」就重传会把还在路上的正常确认当成丢包凭空制造重复报文加上超时才把「可能」收敛成「确定」。1.3 超时时间怎么定1. 理想的最小时间理想情况是找一个最小的时间保证「确认应答一定能在这个时间内返回」。但这个时间的长短随网络环境不同而有差异带宽、距离、拥塞程度都会影响一个报文来回要多久。2. 太长和太短的代价超时时间设得太长丢包之后要干等很久才重传整体重传效率被拉低。超时时间设得太短ACK 还在路上就被判成丢包频繁发送重复的包白占带宽。两头都不讨好所以这个值不能拍脑袋定死。3. 动态计算与指数退避TCP 会动态计算这个最大超时时间让它在不同网络环境下都能保持比较高的性能。Linux 中BSD Unix 和 Windows 也是如此超时以 500ms 为一个单位进行控制每次判定超时重发的超时时间都是 500ms 的整数倍。如果重发一次之后仍然得不到应答等待 2*500ms 后再重传。如果仍然得不到应答等待 4*500ms 重传依次类推以指数形式递增。累计到一定的重传次数TCP 认为网络或者对端主机出现异常强制关闭连接。超时时间不是一个固定值而是一组按 500ms 起步、逐次翻倍的等待窗口翻到一定次数还不通TCP 就认定这条路走不通了。1.4 序号的三个作用1. 确认应答TCP 给发送的每一个字节都编了号发送方发到哪、接收方收到哪都靠序号说清楚。接收方回 ACK 时带上确认号前 N 个字节我收齐了下一个要 N1。2. 按序到达报文可能乱序到达接收方按序号重新排列。应用程序读到的永远是排好序的字节流不用操心网络里的乱序。3. 去重重传会让同一段数据在网络里出现两份。接收方靠序号判断这段数据我收过了重复的那份直接丢掉。序号不只是「给数据编个号」它把确认应答、按序到达、去重三件事一起办了——重传敢发就是因为接收方有办法认出重复。二、连接管理每条连接先描述再组织2.1 一条连接要记的东西不少1. 连接的身份一条 TCP 连接由四元组唯一确定源 IP、源端口、目标 IP、目标端口。同一个服务端要同时服务很多客户端靠四元组才能分清哪个报文属于哪条连接。2. 连接的状态每条连接还带着序号、确认号、窗口大小以及自己当前处在哪个阶段——是在等对方确认还是已经能收发数据抑或已经关闭、在等旧报文消散。阶段决定这条连接下一步能干什么内核必须随时能查到它。连接不是一句「连上了」就完了它是一份要持续维护的状态记录内核得先把它描述清楚。2.2 先描述再组织1. 先描述一条连接一个结构体内核用一个结构体把一条连接的所有属性装进去四元组、序号、窗口、状态、收发缓冲区。这个结构体就是「描述」——把一条连接的静态属性和动态状态都落到字段上。2. 再组织一堆结构体怎么摆服务端同时有成百上千条连接就对应成百上千个结构体。收到一个报文内核要快速找到它属于哪条连接不能让所有连接排成一队挨个比对。把这些结构体按哈希表之类的结构组织起来用四元组做键才能做到快速定位。「先描述再组织」是内核管理连接的基本手法描述解决「一条连接有哪些信息」组织解决「大量连接怎么快速找到」。2.3 connect 触发握手accept 只取结果1. 握手由内核完成应用层调用 connect内核负责发出 SYN、接收 SYNACK、再回 ACK。三次握手由 C 端的 OS 自动完成应用层只看到 connect 阻塞或返回感知不到中间三次报文。2. accept 不参与握手accept 的作用是从内核的已完成连接队列里取出一条连接返回一个新的 socket 文件描述符。连接被 accept 之前三次握手早就完成了accept 拿到的是一条已经建好的连接。《计算机网络篇7传输层——TCP 网络编程常用接口与调用流程》 把 connect、listen、accept 这套接口的调用流程串过一遍这里接着看它们背后触发了什么。connect 负责「建」accept 负责「取」握手发生在 connect 这一侧的内核里accept 只是把建好的结果交到应用层手里。三、三次握手用最小成本验证全双工3.1 为什么要三次握手1. 验证全双工TCP 是全双工的双方都能发、都能收。握手要确认的不是「我能发」而是「我发出去对方能收、对方发出来我能收」——四个方向都要验证。2. 最小成本 100% 确认双方意愿次数少了有一方的接收能力没被确认连接就是半信半疑的。次数多了每多一次都是在重复已经确认过的信息纯属浪费。三次是「刚好够」的次数用最少的报文把四个方向全部确认一遍。验证全双工的本质是检验双方所处的网络环境是否通畅、能否支持双向通信三次握手用最少的报文同时把四个方向和双方的通信意愿确认一遍。3.2 逐次握手点亮了哪些能力1. 一次握手只验证了 C 到 SC 发 SYNS 收到后确认「C 能发、S 能收」。C 只知道自己发出去了不知道 S 有没有收到更不知道 S 能不能发。此时只点亮了 C→S 这一个方向。2. 二次握手C 这边齐了S 还差一个S 回 SYNACKC 收到后确认「S 能发、C 能收」。加上第一次握手C 已经能确认四件事C 能发、S 能收、S 能发、C 能收——全双工在 C 的视角里齐了。但 S 不知道 C 有没有收到自己的 SYNACK所以 S 还确认不了「C 能收」。3. 三次握手S 也齐了全双工成立C 回 ACKS 收到后确认「C 能收」。至此双方都拿到了四个方向的确认全双工验证完成。两次不够的根子就在这里S 的「C 能收」始终没被确认S 发数据可能全部打水漂。三次握手拆开看就是 C 先证明自己能发S 再证明自己能发最后 C 用 ACK 让 S 确认自己也能收——四个方向一个不落。3.3 三次握手本质是四次握手合并1. 原始的四步C 发 SYN同步请求→ S。S 发 ACK确认收到 SYN→ C。S 发 SYN同步自己的请求→ C。C 发 ACK确认收到 S 的 SYN→ S。2. 第二、三步合并成 SYNACKS 的 ACK 和 SYN 可以放进同一个报文因为这两件事之间没有要等的东西。ACK 搭 SYN 的便车一起发出去这就是捎带应答。原本的四步并成三步握手次数从四次降到三次。三次握手是四次握手「合并同类项」的结果S 的确认和请求一起发省下一封报文。四、四次挥手与 TIME_WAIT断开连接的共识4.1 四次挥手四步1. 四步流程C 发 FIN请求关闭→ S。S 回 ACK确认收到 FIN→ C。S 发 FIN请求关闭→ C。C 回 ACK确认收到 S 的 FIN→ S。2. 断开连接的本质每一步都是「请求 确认」的配对FIN 是请求ACK 是确认。四步走完两边都确认了「我不发了」和「我知道你不发了」。断开连接的本质是双方建立「断开连接」的共识不是一方说关就关而是两边都同意、都确认。4.2 为什么通常四次而不是三次1. 二、三步本可以合并如果双方都没有数据要发S 的 ACK 和 FIN 可以合成一个报文挥手就变成三次。但这需要一个前提S 手里没有要发的数据了。2. 常态是 S 还有数据没发完C 把消息发完、请求断开连接时S 端可能还有数据没发完。S 不能立刻发 FIN只能先回 ACK 稳住 C等自己的数据发完再发 FIN。ACK 和 FIN 被拆成两个报文所以通常走四次。四次是常态三次是双方都闲着时的合并特例——差别只在 S 还有没有尾数据要发。4.3 TIME_WAIT 与 2MSL1. 主动关闭方要等先发 FIN 的一方是主动关闭方它在发完最后一个 ACK 后进入 TIME_WAIT 状态。即便四次挥手已经完成它还要在 TIME_WAIT 里等 2MSL才真正关闭连接。2. 为什么等最后那个 ACK 可能丢主动关闭方发出的最后一个 ACK 有可能丢失对端没等到确认会重发 FIN。主动关闭方还在 TIME_WAIT 里就能再回一个 ACK把对端从「等确认」里捞出来。如果它立刻关闭重发的 FIN 就没人理对端会一直重发到超时。3. 为什么等旧报文不能污染新连接新旧连接的四元组可能相同旧连接滞留在网络里的报文可能被新连接误收。等 2MSL 让旧报文全部消散新连接才干净。4. 为什么是两倍 MSLMSL 是报文在网络里的最大生存时间。一来一回主动关闭方最后发的 ACK 最多活 1 MSL对端重发的 FIN 也最多活 1 MSL。所以等 2 MSL两个方向上的旧报文都能死干净。TIME_WAIT 等的不是「礼貌」是两件事让自己的最后一个 ACK 有机会重发让旧连接的报文有时间死透。4.4 TIME_WAIT 与端口复用1. 端口为什么不能立即绑TIME_WAIT 期间这条连接的四元组还被内核占着。应用退出后立刻 bind 同一个端口会失败报地址已被占用EADDRINUSE。2. 服务器崩溃重启怎么办服务器意外崩溃重启时想用原端口被 TIME_WAIT 挡住不能干等 2MSL。setsockopt 的 SO_REUSEADDR 可以让端口在 TIME_WAIT 期间也能绑定。#includesys/socket.hintsetsockopt(intsockfd,intlevel,intoptname,constvoid*optval,socklen_t optlen);3. 靠序列号区分新旧程序立即复用端口的代价是可能被旧连接的报文污染。新连接的初始序列号是新的旧报文的序列号对不上。对不上的报文会被丢弃不会污染新连接的数据。端口复用是用「可能被污染」换「立即重启」序列号就是兜底的那道筛子。五、全篇总结1. 超时重传靠两个条件判定没收到 ACK 只说明可能丢要「收不到应答 超时」才判定丢包。超时时间以 500ms 为单位动态翻倍累计到次数就强制关闭连接。序号把确认应答、按序到达、去重三件事一起办了。2. 连接由内核描述与组织一条连接有四元组、序号、窗口、状态内核先描述成结构体再组织成表。connect 触发三次握手由 C 端 OS 自动完成accept 只取已建好的连接。3. 三次握手验证全双工两次不够S 始终确认不了「C 能收」。三次刚好双方都拿到四个方向的确认。它由四次握手合并而来S 的 ACK 与 SYN 放进同一个报文。4. 四次挥手与 TIME_WAIT通常四次因为 S 的数据没发完ACK 与 FIN 拆成两个报文。主动关闭方等 2MSL保证最后的 ACK 能送达、旧报文能消散。TIME_WAIT 期间端口不能立即复用SO_REUSEADDR 可以绕过靠序列号防污染。六、文末面试题6.1 推导题1. 为什么「收不到 ACK」不能直接判定丢包两个条件缺一不可吗答推导收不到 ACK 至少有三种可能数据丢了、数据到了但 ACK 丢了、ACK 还在路上只是延迟了发送方分不清是哪一种只能判定「可能丢」。所以判定丢包要两个条件同时成立收不到应答且等到超时。少一个都不行——只凭没收到 ACK 就重传会把还在路上的正常确认当成丢包凭空制造重复报文只凭超时而不看有没有应答又没有判定依据。2. 三次握手为什么少一次不够、多一次浪费答推导两次握手后C 已经能从自己的视角确认四个方向——C 能发、S 能收、S 能发、C 能收但 S 只知道 C 能发、自己能收、自己能发唯独不知道 C 有没有收到自己的 SYNACK也就是「C 能收」没被确认S 此时发数据可能全部打水漂。第三次握手的 ACK 让 S 确认「C 能收」四个方向才算齐。第三次之后双方都齐了再加一次没有任何新信息纯属浪费。3. TIME_WAIT 为什么等 2MSL不等会怎样答推导主动关闭方发完最后一个 ACK 后这个 ACK 有可能丢失对端会重发 FIN。如果主动关闭方立刻关闭重发的 FIN 就没人回应对端会一直重发到超时。等 1 个 MSL 能覆盖「我的 ACK 在网络里死掉」但如果对端重发了 FIN这个 FIN 还要再活 1 个 MSL所以一共要等 2 个 MSL。另一层原因是让旧连接滞留在网络里的报文全部消散避免四元组相同的新连接被旧报文污染。6.2 真题1. 三次握手的过程是怎样的为什么要三次而不是两次答推导 · 已对照解析转述客户端发 SYN服务端回 SYNACK客户端再回 ACK三次握手完成。两次不够的原因是服务端无法确认客户端收到了自己的 SYNACK也就无法确认客户端有接收能力客户端的 ACK 补齐了这个确认同时避免历史连接请求造成错误建连。【真题·转述自 牛客网面经帖】2. 四次挥手的过程是怎样的答推导 · 已对照解析转述主动关闭方发 FIN被动关闭方回 ACK等被动关闭方的数据发完再发 FIN主动关闭方回 ACK连接关闭。主动关闭方在发完最后一个 ACK 后进入 TIME_WAIT等待 2MSL。【真题·转述自 牛客网面经帖】3. 为什么 TIME_WAIT 状态要等待 2MSL答推导 · 已对照解析转述一是保证最后一个 ACK 能到达对端如果 ACK 丢失对端重发的 FIN 还能被 TIME_WAIT 状态接住并再回 ACK二是让旧连接的重复报文在网络中消散防止污染四元组相同的新连接。2MSL 是一来一回的时长ACK 最多活 1 MSL重发的 FIN 也最多活 1 MSL。【真题·转述自 牛客网面经帖】4. TCP 第三次握手的 ACK 丢失会怎样答推导 · 已对照解析转述客户端发出 ACK 后即认为连接建立开始发数据服务端没收到 ACK认为握手没完成。服务端收到不属于任何已建立连接的数据报文会回 RST 要求重新建连客户端只能重新握手。【真题·转述自 牛客网面经帖】5. TCP 超时重传的时间是怎么确定的答推导 · 已对照解析转述理想情况是找一个能保证 ACK 一定返回的最小时间但网络环境不同这个时间有差异设太长影响重传效率设太短会频繁发重复包。所以 TCP 动态计算超时时间Linux 等系统以 500ms 为单位每次重传按 2 倍指数递增累计到一定次数就认为网络或对端异常强制关闭连接。【真题·转述自 牛客网面经帖】6. 四次挥手为什么通常是四次什么情况下可以变成三次答推导 · 已对照解析转述通常四次是因为被动关闭方收到 FIN 时可能还有数据没发完只能先回 ACK等数据发完再发 FINACK 与 FIN 拆成两个报文。如果双方都没有数据要发被动关闭方的 ACK 和 FIN 可以合并成一个报文捎带应答四次挥手就变成三次。【真题·转述自 牛客网面经帖】结语超时靠「没应答 超时」判定三次握手把四个方向验齐TIME_WAIT 的两倍 MSL 等的是一来一回——连接的建立与释放本质上都是拿最少的报文换双方都认的共识。评论区聊聊你抓包时见过最长的 TIME_WAIT 挂了多久。
阅读完成 · 觉得有帮助?
咨询建站