Update 计算机网络 - 传输层.md

慢开始改为每收到一个确认 cwnd 加 1、每个轮次翻倍;“在 UDP 上实现可靠传输”移到“TCP 粘包与拆包”之后。
This commit is contained in:
CyC2018
2026-10-28 22:51:26 +08:00
parent 0da4c192b6
commit 4ca59d4aeb
+23 -19
View File
@@ -3,7 +3,6 @@
* [计算机网络 - 传输层](#计算机网络---传输层)
* [UDP 和 TCP 的特点](#udp-和-tcp-的特点)
* [选择 TCP 还是 UDP](#选择-tcp-还是-udp)
* [在 UDP 上实现可靠传输](#在-udp-上实现可靠传输)
* [UDP 首部格式](#udp-首部格式)
* [TCP 首部格式](#tcp-首部格式)
* [TCP 的三次握手](#tcp-的三次握手)
@@ -20,6 +19,7 @@
* [1. 慢开始与拥塞避免](#1-慢开始与拥塞避免)
* [2. 快重传与快恢复](#2-快重传与快恢复)
* [TCP 粘包与拆包](#tcp-粘包与拆包)
* [在 UDP 上实现可靠传输](#在-udp-上实现可靠传输)
* [附录:实测](#附录实测)
* [A1 TCP 与 UDP 发送位置更新](#a1-tcp-与-udp-发送位置更新)
* [A2 握手报文丢失](#a2-握手报文丢失)
@@ -52,23 +52,7 @@ TCP 的可靠和按序是有代价的:一个报文段丢失后,后面已经
5 次实测中,TCP 的 600 条更新全部到达,但有 24~53 条的延迟超过 100 ms,最长 307~939 ms,到达时位置早已过时;UDP 丢失了 23~30 条,到达的延迟都不超过 44 ms,丢失的那一条由 50 ms 后的下一次更新补上。
### 在 UDP 上实现可靠传输
游戏中的位置可以丢,购买道具、释放技能这类消息却必须送达。既要 UDP 的低延迟、又有一部分数据必须可靠时,就要在应用层按 TCP 的思路自己实现:
- 序号:给每个数据包编号,接收方据此去重和排序;
- 确认和重传:接收方回复确认,发送方超时没有收到确认就重传,超时时间根据测得的 RTT 计算;
- 流量控制和拥塞控制:限制已发送但未确认的数据量,避免压垮接收方和网络。
与直接使用 TCP 相比,好处在于可以按需取舍:只对需要的消息重传,不相关的消息互不阻塞,重传和拥塞控制的参数也可以按业务调整。
HTTP/3 使用的 QUIC 就是在 UDP 上实现的可靠传输,并改进了 TCP 的几处设计:
- 包号只增不减,丢失的数据放进一个新编号的包中重传。收到确认时能分清确认的是哪一次发送,RTT 测得更准;TCP 重传时沿用原来的序号,无法区分;
- 数据按流(Stream)组织,每个流按帧中的偏移量单独排序,一个流丢包不影响其它流;
- 一个确认帧可以携带很多个已收到的区间,TCP 的选择确认(SACK)受首部长度限制,通常最多携带 3 个区间。
QUIC 选择在 UDP 上重新实现,而不是改进 TCP,是因为 TCP 实现在操作系统内核中,改动要等操作系统升级才能普及;防火墙、NAT 等中间设备还会检查甚至改写 TCP 首部,不认识的新选项可能被丢弃。QUIC 运行在用户态,随浏览器和 App 一起更新,并且加密了大部分首部,中间设备无法干预。QUIC 的握手和连接迁移见 [HTTP/3](HTTP.md#http3)。
有一部分数据必须送达时,可以在 UDP 之上自己实现可靠传输,见[在 UDP 上实现可靠传输](#在-udp-上实现可靠传输)。
## UDP 首部格式
@@ -222,7 +206,7 @@ TCP 主要通过四个算法来进行拥塞控制:慢开始、拥塞避免、
### 1. 慢开始与拥塞避免
发送的最初执行慢开始,令 cwnd = 1,发送方只能发送 1 个报文段;当收到确认后,将 cwnd 加倍,因此之后发送方能够发送的报文段数量为:2、4、8 ...
发送的最初执行慢开始,令 cwnd = 1,发送方只能发送 1 个报文段;每收到一个对新数据的确认,cwnd 加 1,一个轮次发出的报文段都被确认后 cwnd 就翻了一倍,因此之后每个轮次能够发送的报文段数量为:2、4、8 ...
实际实现中初始拥塞窗口通常更大,RFC 6928 建议初始值为 10 个报文段。
@@ -293,6 +277,26 @@ static String receive(DataInputStream in) throws IOException {
需要注意的是,长度字段来自网络,必须限制上限,否则对方发来一个 2 GB 的长度,服务器就会为它分配 2 GB 内存。
## 在 UDP 上实现可靠传输
[选择 TCP 还是 UDP](#选择-tcp-还是-udp) 中比较了游戏位置更新用 TCP 和 UDP 发送的效果。
游戏中的位置可以丢,购买道具、释放技能这类消息却必须送达。既要 UDP 的低延迟、又有一部分数据必须可靠时,就要在应用层按 TCP 的思路自己实现:
- 序号:给每个数据包编号,接收方据此去重和排序;
- 确认和重传:接收方回复确认,发送方超时没有收到确认就重传,超时时间根据测得的 RTT 计算;
- 流量控制和拥塞控制:限制已发送但未确认的数据量,避免压垮接收方和网络。
与直接使用 TCP 相比,好处在于可以按需取舍:只对需要的消息重传,不相关的消息互不阻塞,重传和拥塞控制的参数也可以按业务调整。
HTTP/3 使用的 QUIC 就是在 UDP 上实现的可靠传输,并改进了 TCP 的几处设计:
- 包号只增不减,丢失的数据放进一个新编号的包中重传。收到确认时能分清确认的是哪一次发送,RTT 测得更准;TCP 重传时沿用原来的序号,无法区分;
- 数据按流(Stream)组织,每个流按帧中的偏移量单独排序,一个流丢包不影响其它流;
- 一个确认帧可以携带很多个已收到的区间,TCP 的选择确认(SACK)受首部长度限制,通常最多携带 3 个区间。
QUIC 选择在 UDP 上重新实现,而不是改进 TCP,是因为 TCP 实现在操作系统内核中,改动要等操作系统升级才能普及;防火墙、NAT 等中间设备还会检查甚至改写 TCP 首部,不认识的新选项可能被丢弃。QUIC 运行在用户态,随浏览器和 App 一起更新,并且加密了大部分首部,中间设备无法干预。QUIC 的握手和连接迁移见 [HTTP/3](HTTP.md#http3)。
## 附录:实测
以下实验都在 Docker Desktop(Apple M1 Pro)中的 Ubuntu 24.04 容器里进行,内核为 6.10.14-linuxkit,客户端和服务器都在 127.0.0.1 上,用 iptables 丢弃指定的报文,用 tc netem 模拟时延和丢包,tcpdump 的时间换算为相对第一个报文的秒数。容器中的默认参数: