代码随想录 | 八股-TCP机制
TCP连接如何确保可靠性
序列号与确认应答: * 序列号: 发送方为每个字节的数据分配一个唯一的序列号。TCP报文段的首部包含该报文段中第一个数据字节的序列号。 * 确认号: 接收方收到数据后,会发送一个确认报文段。该报文段中的确认号字段表示接收方期望收到的下一个字节的序列号。例如,接收方正确收到了序列号为 1001-2000 的数据,它会发送确认号 2001,表示“我已正确收到序列号 2000 及之前的所有字节,请从序列号 2001 开始发送”。 * 机制: 发送方发送数据后,会等待接收方的确认。如果收到预期的确认,说明数据已成功送达。这是可靠性的基石。
校验和: * 计算: 发送方在发送数据前,会计算报文段(首部和数据)的校验和,并将结果放入首部的校验和字段。 * 验证: 接收方收到报文段后,会使用相同的算法重新计算校验和。 * 丢弃: 如果接收方计算出的校验和与报文段首部中的校验和不匹配,说明数据在传输过程中发生了比特错误(比特翻转)。接收方会丢弃该损坏的报文段,并且不会发送任何确认。 * 触发重传: 由于发送方没有收到确认(见第1、2点),最终会触发超时重传。
流量控制: * 目的: 防止发送方发送数据过快,导致接收方缓冲区溢出而丢失数据。 * 滑动窗口: 接收方通过TCP首部中的窗口大小字段告知发送方自己当前接收缓冲区的可用空间大小。这个值称为接收窗口。 * 发送窗口限制: 发送方维护一个发送窗口,其大小不能超过接收方通告的接收窗口大小。发送窗口内的数据是允许发送但尚未被确认的数据。 * 动态调整: 随着接收方处理数据并释放缓冲区空间,它会通过后续的确认报文段更新其通告的窗口大小,发送方据此调整自己的发送窗口。这确保了发送速率不会超过接收方的处理能力。
拥塞控制: * 目的:
防止发送方发送数据过快,导致网络中间设备(如路由器)的缓冲区溢出,引发网络拥塞和数据包丢失。这是对整个网络的保护机制。
* 核心机制:
发送方维护一个拥塞窗口,它限制了在任何时候可以发送但未被确认的数据量。发送窗口的实际大小是min(接收窗口, 拥塞窗口)
。
超时重传: * 核心思想: 发送方发送一个报文段后启动一个重传计时器。如果在计时器超时之前没有收到该报文段的确认,发送方就认为该报文段已丢失或损坏,会重新发送该报文段。 * 动态计算超时时间: 超时时间是根据历史数据包往返时间动态计算出来的,称为RTO。这确保了在网络状况变化时也能有效工作。
拥塞控制是怎么实现的
- 慢启动: 连接开始时,拥塞窗口从一个很小的值开始,并随着每个成功确认的报文段而指数增长(每收到一个ACK,cwnd增加1个MSS),快速探测可用带宽。
- 拥塞避免: 当拥塞窗口增长到某个阈值时,进入拥塞避免阶段,窗口变为线性增长(每收到一个RTT内的所有ACK,cwnd增加1个MSS),增速放缓。
- 拥塞检测:
- 超时:
如果发生超时(表明有严重丢包),阈值被设置为当前拥塞窗口的一半(
ssthresh = cwnd / 2
),拥塞窗口被重置为1(或一个很小的值),重新进入慢启动。 - 快速重传与快速恢复:
如果发送方收到3个重复的ACK(表明有单个数据包丢失,但后续数据包接收方还能收到),它立即重传丢失的报文段(快速重传),并将阈值设置为当前拥塞窗口的一半(
ssthresh = cwnd / 2
),拥塞窗口设置为阈值加3(或类似算法),然后进入快速恢复阶段。在快速恢复阶段,每收到一个重复ACK,拥塞窗口增加1个MSS。当收到一个新数据的ACK时(表明重传成功),退出快速恢复,将拥塞窗口设置为阈值大小,进入拥塞避免阶段。这比超时恢复要快得多。
- 超时:
如果发生超时(表明有严重丢包),阈值被设置为当前拥塞窗口的一半(
TCP流量控制是怎么实现的
TCP流量控制的实现核心在于滑动窗口协议(Sliding Window Protocol),其目的是防止发送方发送数据过快导致接收方缓冲区溢出。这是通过接收方动态通告其接收窗口大小来实现的。
- 初始通告:
- 连接建立时(三次握手阶段),接收方在其
SYN+ACK
报文段中设置窗口大小
字段,告知发送方其初始接收缓冲区大小。
- 连接建立时(三次握手阶段),接收方在其
- 动态窗口通告:
- 接收方处理数据: 当接收方应用程序从缓冲区读取数据后,缓冲区空间被释放,可用空间增加。
- 发送更新窗口:
接收方在发送给发送方的任何报文段(包括数据报文段、纯ACK确认报文段)中,都会携带最新的
窗口大小
值。 - 即时生效: 发送方收到包含新窗口大小的报文段后,立即更新其对接收方接收窗口的理解。
- 发送方行为 - 滑动窗口:
- 维护状态: 发送方维护三个指针:
SND.UNA
:最早未确认字节的序列号。SND.NXT
:下一个要发送字节的序列号。- 发送窗口大小
(
swnd
):swnd = min(接收方通告的接收窗口, 拥塞窗口)
。流量控制关注的是接收窗口部分。
- 发送约束: 发送方只能发送序列号在
[SND.UNA, SND.UNA + swnd)
范围内的数据。 - 窗口滑动:
- 当发送方收到新的ACK确认(推进了
SND.UNA
),并且接收方通告了新的(可能更大的)窗口大小时,发送窗口会向右“滑动”。 - 滑动后,
SND.NXT
可能可以继续发送新的数据(如果可用窗口 > 0)。
- 当发送方收到新的ACK确认(推进了
- 维护状态: 发送方维护三个指针:
- 关键操作示例:
- 假设接收方初始通告
rwnd = 4000
字节。 - 发送方发送2000字节(
SND.NXT
前进2000)。 - 接收方收到这2000字节,但应用程序只读取了1000字节。此时接收缓冲区:
- 已用空间 = 1000字节(2000收到 - 1000被读走)
- 可用空间 = 3000字节(初始4000 - 1000占用)。
- 接收方在ACK中设置
rwnd = 3000
。
- 发送方收到ACK和
rwnd=3000
:SND.UNA
前进2000(假设ACK确认了前2000字节)。- 更新
swnd = min(3000, cwnd)
。 - 新发送窗口变为
[新SND.UNA, 新SND.UNA + 3000)
。 SND.NXT
可能指向新窗口内的位置,允许发送最多3000字节新数据(减去已在传输中的)。
- 假设接收方初始通告
- 处理零窗口 - 死锁预防:
- 问题:
如果接收方缓冲区满,它会通告
rwnd = 0
。发送方必须立即停止发送数据。但如果之后接收方应用程序读取数据释放了缓冲区,它需要通知发送方rwnd > 0
。如果这个通知(携带新rwnd
的ACK)丢失了怎么办?双方会陷入死锁:发送方在等待窗口更新,接收方以为发送方知道窗口已打开。 - 解决方案:零窗口探测:
- 当发送方收到
rwnd = 0
时,启动一个持续计时器。 - 计时器超时后,发送方发送一个1字节的探测报文段。
- 接收方收到探测报文段:
- 如果缓冲区仍满,再次回复
rwnd = 0
,发送方重置持续计时器。 - 如果缓冲区已有空间,回复包含当前
rwnd > 0
的ACK。
- 如果缓冲区仍满,再次回复
- 探测报文段确保即使窗口更新ACK丢失,死锁也能被打破。
- 当发送方收到
- 问题:
如果接收方缓冲区满,它会通告
UDP怎么实现可靠传输
(参考QUIC)