目录
目录
三次握手
为什么TCP协议中序列号是随机的?
同时打开连接的情况
四次挥手
为什么需要四次挥手?
第三次和第四次挥手能否合并?
TCP的状态机
TCP 的流量控制和拥塞控制有什么区别?它们分别是如何实现的?
技术细节
RTO
MSL
优先选择UDP的场景
总结与建议
三次握手
模拟面试问题:1. 你能描述一下 TCP 的三次握手过程吗?为什么需要三次握手?两次握手行不行?
blabla....
可能的问题:你基本描述了三次握手的流程:客户端发送 SYN,服务端回应 SYN-ACK 并附带自己的序列号。但存在一些表述不准确的地方,如“SYN关键字”应为“SYN 标志位”,且未完整描述第三次握手(客户端发送 ACK 确认服务端的序列号)。此外,未解释为什么需要三次握手以及两次握手的潜在问题。
补充说明:
TCP 三次握手过程如下:
第一次握手:客户端发送 SYN 报文(SYN=1,序列号=x),进入 SYN_SENT 状态,等待服务器确认。
第二次握手:服务器收到 SYN 报文,确认客户端的 SYN(ACK=x+1),同时自己也发送 SYN 报文(SYN=1,序列号=y),进入 SYN_RCVD 状态。
第三次握手:客户端收到 SYN-ACK 报文,向服务器发送确认报文(ACK=y+1),双方进入 ESTABLISHED 状态,连接建立完成。
为什么需要三次握手?
三次握手的核心目的是防止历史连接初始化的请求造成资源浪费。
若使用两次握手,当客户端发送的 SYN 因网络延迟到达服务器时,服务器会误认为是新的连接请求,建立连接并分配资源,而客户端可能已经超时放弃,导致服务器资源浪费。
三次握手通过客户端的最后一次确认(ACK),确保双方都确认了对方的序列号,避免了这种问题。
两次握手的问题:
两次握手无法确保客户端已收到服务器的 SYN-ACK,可能导致服务器单方面建立连接,而客户端并未准备好,造成资源浪费和连接状态不一致。"
为什么TCP协议中序列号是随机的?
现代 TCP 实现正是要求 ISN 尽可能随机,而不是从 1 或 0 开始。
之所以这么做,核心原因有两个:
-
防止旧连接数据干扰新连接
如果每次 ISN 都从 1 开始,网络里可能还残留着上一次连接的延迟报文,这些报文的序列号会与新连接重叠,导致接收方把旧数据误当成新数据。
通过让 ISN 随时间线性增长并叠加随机偏移,可以保证新旧连接的序列号空间几乎不会重叠,旧报文一到就被当作“未来的字节”丢弃,不会造成混乱。 -
抵御序列号猜测攻击
若 ISN 可预测(比如固定从 1 开始),攻击者只需猜出当前序列号就能伪造 RST 报文断开连接,或注入恶意数据。
现代实现把 ISN 设计成“时间线性递增 + 加密哈希随机偏移”的组合:
ISN = (每 4 μs 递增 1 的计数器) + F(四元组, 密钥)
这样既保证全局唯一,又让攻击者无法通过时间差或四元组推算出当前 ISN,伪造成本极高。
一句话总结:
ISN 必须“随机”(更准确说是“不可预测”),才能同时解决“旧数据干扰”和“安全攻击”两大问题;现代 TCP 不仅允许随机,还强制要求随机。"
同时打开连接的情况
"同时打开
正常情况是“一端主动打开,另一端被动打开”,状态迁移是三次握手。
而当两端几乎同时发送 SYN 时,状态迁移变成四步:
两端都从 CLOSED 进入 SYN_SENT,发出 SYN。
每端收到对方的 SYN 后,进入 SYN_RCVD,同时再发一个 SYN+ACK 给对方。
当双方都收到 SYN 及对应的 ACK 后,直接进入 ESTABLISHED。
整个过程共交换 4 个报文段(SYN→SYN→SYN+ACK→SYN+ACK),比三次握手多一次。"
四次挥手
TCP 四次挥手的过程是怎样的?为什么需要四次挥手?第三次和第四次挥手能否合并?
你大致描述了四次挥手的过程:客户端发送 FIN,服务端回复 ACK,服务端再发送 FIN,客户端回复 ACK。你提到第三次和第四次挥手不能合并,并提到了“等待确认时间”和“防止丢失”的概念,但表述不够准确和完整。
补充说明:
TCP 四次挥手过程如下:
第一次挥手:客户端发送 FIN 报文(FIN=1,序列号=u),进入 FIN_WAIT_1 状态,表示客户端没有数据要发送了。
第二次挥手:服务器收到 FIN 报文,发送 ACK 报文(ACK=u+1,序列号=v),进入 CLOSE_WAIT 状态,客户端收到 ACK 后进入 FIN_WAIT_2 状态。
第三次挥手:服务器处理完数据后,发送 FIN 报文(FIN=1,ACK=u+1,序列号=w),进入 LAST_ACK 状态。
第四次挥手:客户端收到 FIN 报文,发送 ACK 报文(ACK=w+1,序列号=u+1),进入 TIME_WAIT 状态,服务器收到 ACK 后关闭连接,客户端等待 2MSL 时间后关闭连接。
为什么需要四次挥手?
因为 TCP 是全双工通信,双方都可以发送和接收数据。当一方(如客户端)发送 FIN 时,只是表示它没有数据要发送了,但还可以接收数据;另一方(服务器)可能还有数据要发送,所以需要等待服务器发送完数据后再发送 FIN,因此需要四次挥手。
第三次和第四次挥手能否合并?
不能合并。
因为第三次挥手是服务器发送 FIN,表示服务器也没有数据要发送了;第四次挥手是客户端确认服务器的 FIN。这两步是独立的,客户端必须等待服务器的 FIN 报文,然后才能发送 ACK 确认。如果合并,客户端无法确认服务器是否已经发送完数据,可能导致数据丢失或连接状态不一致。
TCP的状态机
TCP 状态机就是一张“连接生命周期图”,把一条 TCP 连接从诞生到消亡的每一步都用状态刻画出来,状态之间靠事件触发迁移。
典型迁移路径
- 客户端:CLOSED → SYN_SENT → ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED
- 服务器:CLOSED → LISTEN → SYN_RCVD → ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED
常见状态可归纳为 11 个,按连接阶段分三段:
- 连接建立
- CLOSED:初始空闲状态。
- SYN_SENT:客户端发出 SYN,等待对方确认。
- SYN_RCVD:服务器收到 SYN,回 SYN+ACK,等待最终确认。
- ESTABLISHED:三次握手完成,可以双向收发数据。
助记 SYN打头
-
数据传输
- ESTABLISHED:唯一的数据传输状态,持续到连接关闭。
-
连接关闭
- FIN_WAIT_1:主动关闭方发 FIN,等待对方确认或对方 FIN。
- FIN_WAIT_2:收到对方对 FIN 的确认,等待对方 FIN。
- TIME_WAIT:收到对方 FIN 并回 ACK,等待 2MSL 以确保对方收到确认。
助力 三个WAIT
- CLOSE_WAIT:被动关闭方收到 FIN,等待本地应用关闭连接。
- LAST_ACK:被动关闭方发 FIN,等待最终确认。
- CLOSING:同时关闭时,等待对方对 FIN 的确认。
- CLOSED:连接彻底释放,回到初始状态。
助记 CLOSE
典型迁移路径
- 客户端:CLOSED → SYN_SENT → ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED
- 服务器:CLOSED → LISTEN → SYN_RCVD → ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED
特殊场景
- 同时打开:两端同时从 SYN_SENT → SYN_RCVD,再完成握手,共 4 个报文段。
- 同时关闭:两端同时发 FIN,直接进入 CLOSING,再完成四次挥手。
- 超时/丢包:SYN、ACK 丢失都会触发超时重传,最终回到 CLOSED。
一句话总结:把状态机看成“连接的一生”,只要记住“三次握手进 ESTABLISHED,四次挥手回 CLOSED”,中间状态都是围绕这两个动作展开,就不会乱。"
TCP 的流量控制和拥塞控制有什么区别?它们分别是如何实现的?
打分:9/10
理由:
你准确区分了流量控制和拥塞控制的核心思想:流量控制关注点对点的接收能力(防止缓冲区溢出),拥塞控制关注网络整体的拥堵情况(防止网络过载)。你提到流量控制通过窗口大小告知对方接收能力,拥塞控制通过超时调整发送速率,表述基本正确。
补充说明:
- 流量控制:通过滑动窗口机制实现。接收方在 TCP 报头的窗口字段中告知发送方可接收的数据量,发送方据此调整发送速率,防止接收方缓冲区溢出。
- 拥塞控制:通过拥塞窗口(cwnd)和一系列算法(慢启动、拥塞避免、快速重传、快速恢复)实现。
- 慢启动:初始拥塞窗口较小,每收到一个确认,拥塞窗口指数增长,快速探测网络容量。
- 拥塞避免:当拥塞窗口达到阈值后,进入线性增长阶段,避免网络过载。
- 快速重传:当发送方收到 3 个重复确认时,立即重传丢失的报文,无需等待超时。
- 快速恢复:在快速重传后,将拥塞窗口减半,进入拥塞避免阶段,避免网络再次过载。
这些机制协同工作:流量控制确保接收方不被压垮,拥塞控制确保网络不被压垮。两者共同保障 TCP 的可靠性和高效性。
技术细节
RTO
TCP 的超时时间(RTO,Retransmission Time-Out)并不是固定值,而是动态计算出来的,核心思路是“用最近的往返时间 RTT 来预测下一次重传应该等多久”。
- 每次发送一个报文段,TCP 都会记录下发送时刻。
- 当对应的 ACK 到达时,再记录当前时刻,两者之差就是这次的 RTT 样本(SampleRTT)。
- TCP 用一个“平滑算法”把新样本和历史估计值混合,得到新的 EstimatedRTT:
EstimatedRTT = α × EstimatedRTT + (1-α) × SampleRTT
α 通常取 0.8~0.9,既保留历史趋势,又对新变化敏感。 - 最终 RTO 就在 EstimatedRTT 的基础上再放大一点,常见做法是:
RTO = EstimatedRTT × β,β 取 2 左右,确保不会因为瞬时抖动就触发重传。 - 如果网络抖动剧烈,RTT 变化范围大,TCP 还会把 RTT 的“抖动程度”也纳入计算,进一步加大 RTO 的余量。
这样,同一台机器在不同时间、不同目标之间,RTO 都能自动适应:
- 同一栋楼的局域网,RTT 短,RTO 就短,重传快;
- 跨国链路 RTT 长,RTO 也跟着拉长,避免无效重传。
一句话总结:RTO 不是写死的,而是“用最近的 RTT 样本实时算出来”的,既保证可靠,又尽量不浪费带宽。
MSL
"MSL怎么计算得出
MSL(Maximum Segment Lifetime)并不是通过公式“算”出来的,而是人为设定的一个经验值,用来约束 TCP 报文在网络里能存活的最长时间。
RFC 793 给出的建议值是 2 分钟;实际操作系统通常取 30 秒、60 秒或 120 秒,Linux 默认 60 秒,Windows 默认 120 秒。
一旦这个时间过去,路由器、交换机等设备就会把 TTL(Time to Live)字段减到 0 的报文直接丢弃,从而保证旧连接的报文不会干扰新连接。"
优先选择UDP的场景
UDP 有哪些典型的应用场景?为什么在这些场景下选择 UDP 而不是 TCP?
理由:
你正确指出了 UDP 的典型应用场景(如直播、实时视频流、HTTP/3)和选择它的核心原因——对低延迟和实时性的极致追求,以及应用层有能力实现可靠性机制。但描述稍显笼统,例如“自己手搓 TCP”不够准确(更准确的说法是“应用层实现类似可靠性机制”),且未提及其他典型场景(如 DNS 查询、在线游戏)。
补充说明:
UDP 的典型应用场景及选择原因:
-
实时音视频传输(如直播、视频会议):
- 原因:UDP 允许少量丢包(如视频帧丢失),但优先保证低延迟。TCP 的重传机制会导致延迟增加,影响实时体验。应用层可通过前向纠错(FEC)、丢帧补偿等机制弥补可靠性。
-
在线游戏(如多人射击游戏):
- 原因:游戏需要极低延迟(如毫秒级),UDP 适合传输位置、状态等高频更新数据。丢包可通过预测算法或丢弃旧数据处理,TCP 的重传会导致操作卡顿。
-
DNS 查询:
- 原因:DNS 查询通常数据量小(< 512 字节),UDP 头部更轻量,响应速度快。若数据量大,可切换到 TCP。
-
QUIC 协议(HTTP/3 底层):
- 原因:QUIC 基于 UDP 实现,但内置了类似 TCP 的可靠性机制(如重传、流量控制),同时通过多路复用、0RTT 连接减少延迟,比传统 TCP 更高效。
UDP 的核心优势:
- 低延迟:无握手、无重传、无拥塞控制,传输速度快。
- 轻量级:头部仅 8 字节,适合小数据量传输。
- 灵活:应用层可自定义可靠性机制,满足特定需求。
总结与建议
总结与建议:
对 TCP/UDP 的核心概念(如可靠性机制、三次握手、四次挥手、流量控制、应用场景等)有较好的理解,能准确回答大部分问题,但表述有时不够严谨或细节不足。
建议:
- 强化细节:深入理解协议字段(如 TCP 头部结构、窗口字段作用)和状态机(如 TIME_WAIT、CLOSE_WAIT)。
- 实践结合:通过抓包工具(如 Wireshark)观察实际网络通信,加深对机制的理解。
- 场景化学习:结合具体应用(如 HTTP/3、在线游戏)分析协议选择原因,提升知识应用能力。