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

把粗体小标题改为正式标题,补充 CUBIC 和 BBR 的说明以及参考资料,更新 RTO 的描述。
This commit is contained in:
CyC2018
2026-10-28 03:19:09 +08:00
parent 10cf2566d3
commit 8a6ebf414d
+42 -39
View File
@@ -5,7 +5,11 @@
* [UDP 首部格式](#udp-首部格式)
* [TCP 首部格式](#tcp-首部格式)
* [TCP 的三次握手](#tcp-的三次握手)
* [三次握手的原因](#三次握手的原因)
* [TCP 的四次挥手](#tcp-的四次挥手)
* [四次挥手的原因](#四次挥手的原因)
* [TIME_WAIT](#time_wait)
* [大量 TIME_WAIT 与 CLOSE_WAIT](#大量-time_wait-与-close_wait)
* [TCP 可靠传输](#tcp-可靠传输)
* [TCP 滑动窗口](#tcp-滑动窗口)
* [TCP 流量控制](#tcp-流量控制)
@@ -13,6 +17,7 @@
* [1. 慢开始与拥塞避免](#1-慢开始与拥塞避免)
* [2. 快重传与快恢复](#2-快重传与快恢复)
* [TCP 粘包与拆包](#tcp-粘包与拆包)
* [参考资料](#参考资料)
<!-- GFM-TOC -->
@@ -20,8 +25,7 @@
## UDP 和 TCP 的特点
- 用户数据报协议 UDP(User Datagram Protocol)是无连接的,尽最大可能交付,没有拥塞控制,面向报文(对于应用程序传下来的报文不合并也不拆分,只是添加 UDP 首部),支持一对一、一对多、多对一和多对多的交互通信。
- 用户数据报协议 UDP(User Datagram Protocol)是无连接的,尽最大可能交付,没有拥塞控制,面向报文(对于应用程序传下来的报文不合并也不拆分,只是添加 UDP 首部),支持一对一、一对多、多对一和多对多的交互通信;
- 传输控制协议 TCP(Transmission Control Protocol)是面向连接的,提供可靠交付,有流量控制,拥塞控制,提供全双工通信,面向字节流(把应用层传下来的报文看成字节流,把字节流组织成大小不等的数据块),每一条 TCP 连接只能是点对点的(一对一)。
## UDP 首部格式
@@ -34,19 +38,13 @@
<div align="center"> <img src="pics/tcp-header.png" width="720px"/> </div><br>
- **序号** :用于对字节流进行编号,例如序号为 301,表示第一个字节的编号为 301,如果携带的数据长度为 100 字节,那么下一个报文段的序号应为 401。
- **确认号** :期望收到的下一个报文段的序号。例如 B 正确收到 A 发送来的一个报文段,序号为 501,携带的数据长度为 200 字节,因此 B 期望下一个报文段的序号为 701,B 发送给 A 的确认报文段中确认号就为 701。
- **数据偏移** :指的是数据部分距离报文段起始处的偏移量,实际上指的是首部的长度。
- **确认 ACK** :当 ACK=1 时确认号字段有效,否则无效。TCP 规定,在连接建立后所有传送的报文段都必须把 ACK 置 1。
- **同步 SYN** :在连接建立时用来同步序号。当 SYN=1,ACK=0 时表示这是一个连接请求报文段。若对方同意建立连接,则响应报文中 SYN=1,ACK=1。
- **终止 FIN** :用来释放一个连接,当 FIN=1 时,表示此报文段的发送方的数据已发送完毕,并要求释放连接。
- **窗口** :窗口值作为接收方让发送方设置其发送窗口的依据。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。
- 序号:用于对字节流进行编号,例如序号为 301,表示第一个字节的编号为 301,如果携带的数据长度为 100 字节,那么下一个报文段的序号应为 401;
- 确认号:期望收到的下一个报文段的序号。例如 B 正确收到 A 发送来的一个报文段,序号为 501,携带的数据长度为 200 字节,因此 B 期望下一个报文段的序号为 701,B 发送给 A 的确认报文段中确认号就为 701;
- 数据偏移:指的是数据部分距离报文段起始处的偏移量,实际上指的是首部的长度;
- 确认 ACK:当 ACK=1 时确认号字段有效,否则无效。TCP 规定,在连接建立后所有传送的报文段都必须把 ACK 置 1;
- 同步 SYN:在连接建立时用来同步序号。当 SYN=1,ACK=0 时表示这是一个连接请求报文段。若对方同意建立连接,则响应报文中 SYN=1,ACK=1;
- 终止 FIN:用来释放一个连接,当 FIN=1 时,表示此报文段的发送方的数据已发送完毕,并要求释放连接;
- 窗口:窗口值作为接收方让发送方设置其发送窗口的依据。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。
## TCP 的三次握手
@@ -54,17 +52,13 @@
假设 A 为客户端,B 为服务器端。
- 首先 B 处于 LISTEN(监听)状态,等待客户的连接请求。
- A 向 B 发送连接请求报文,SYN=1,ACK=0,选择一个初始的序号 x。
- B 收到连接请求报文,如果同意建立连接,则向 A 发送连接确认报文,SYN=1,ACK=1,确认号为 x+1,同时也选择一个初始的序号 y。
- A 收到 B 的连接确认报文后,还要向 B 发出确认,确认号为 y+1,序号为 x+1。
- 首先 B 处于 LISTEN(监听)状态,等待客户的连接请求;
- A 向 B 发送连接请求报文,SYN=1,ACK=0,选择一个初始的序号 x;
- B 收到连接请求报文,如果同意建立连接,则向 A 发送连接确认报文,SYN=1,ACK=1,确认号为 x+1,同时也选择一个初始的序号 y;
- A 收到 B 的连接确认报文后,还要向 B 发出确认,确认号为 y+1,序号为 x+1;
- B 收到 A 的确认后,连接建立。
**三次握手的原因**
### 三次握手的原因
第三次握手是为了防止失效的连接请求到达服务器,让服务器错误打开连接。
@@ -76,31 +70,26 @@
以下描述不讨论序号和确认号,因为序号和确认号的规则比较简单。并且不讨论 ACK,因为 ACK 在连接建立之后都为 1。
- A 发送连接释放报文,FIN=1。
- B 收到之后发出确认,此时 TCP 属于半关闭状态,B 能向 A 发送数据但是 A 不能向 B 发送数据。
- 当 B 不再需要连接时,发送连接释放报文,FIN=1。
- A 收到后发出确认,进入 TIME-WAIT 状态,等待 2 MSL(最大报文存活时间)后释放连接。
- A 发送连接释放报文,FIN=1;
- B 收到之后发出确认,此时 TCP 属于半关闭状态,B 能向 A 发送数据但是 A 不能向 B 发送数据;
- 当 B 不再需要连接时,发送连接释放报文,FIN=1;
- A 收到后发出确认,进入 TIME_WAIT 状态,等待 2 MSL(Maximum Segment Lifetime,最大报文段寿命)后释放连接;
- B 收到 A 的确认后释放连接。
**四次挥手的原因**
### 四次挥手的原因
客户端发送了 FIN 连接释放报文之后,服务器收到了这个报文,就进入了 CLOSE-WAIT 状态。这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕之后,服务器会发送 FIN 连接释放报文。
客户端发送了 FIN 连接释放报文之后,服务器收到了这个报文,就进入了 CLOSE_WAIT 状态。这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕之后,服务器会发送 FIN 连接释放报文。
**TIME_WAIT**
### TIME_WAIT
客户端接收到服务器端的 FIN 报文后进入此状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器设置的时间 2MSL。这么做有两个理由:
- 确保最后一个确认报文能够到达。如果 B 没收到 A 发送来的确认报文,那么就会重新发送连接释放请求报文,A 等待一段时间就是为了处理这种情况的发生。
- 确保最后一个确认报文能够到达。如果 B 没收到 A 发送来的确认报文,那么就会重新发送连接释放请求报文,A 等待一段时间就是为了处理这种情况的发生;
- 等待一段时间是为了让本连接持续时间内所产生的所有报文都从网络中消失,使得下一个新的连接不会出现旧的连接请求报文。
2MSL 正好覆盖一个来回:A 的确认最多在网络中存活 1 MSL,B 超时重传的 FIN 最多再存活 1 MSL。Linux 中 TIME_WAIT 固定为 60 秒(TCP_TIMEWAIT_LEN)。
**大量 TIME_WAIT 与 CLOSE_WAIT**
### 大量 TIME_WAIT 与 CLOSE_WAIT
TIME_WAIT 出现在主动关闭的一方,使用短连接的机器会积累大量 TIME_WAIT,例如每次请求都新建连接去访问下游服务。每个 TIME_WAIT 连接占用一个本地端口 60 秒,Linux 默认的本地端口范围是 32768~60999,共 28232 个,连接同一个目标地址和端口时,每秒新建超过约 470 个连接就会耗尽端口。
@@ -132,7 +121,7 @@ RTTs = (1 - a) × RTTs + a × RTT
RTO = RTTs + 4 × RTTd
```
其中 RTT<sub>d</sub> 为偏差的加权平均值。RFC 6298 建议 a 取 1/8,计算 RTT<sub>d</sub> 时的权重取 1/4,并要求 RTO 不小于 1 秒;Linux 的下限为 200 ms。
其中 RTT<sub>d</sub> 为偏差的加权平均值。RFC 6298 建议 a 取 1/8,计算 RTT<sub>d</sub> 时的权重取 1/4,并建议 RTO 不小于 1 秒;Linux 的下限为 200 ms。
## TCP 滑动窗口
@@ -187,6 +176,8 @@ TCP 主要通过四个算法来进行拥塞控制:慢开始、拥塞避免、
慢开始和快恢复的快慢指的是 cwnd 的设定值,而不是 cwnd 的增长速率。慢开始 cwnd 设定为 1,而快恢复 cwnd 设定为 ssthresh。
以上是经典的 Reno 算法(RFC 5681)。Linux、Windows 和 Apple 的协议栈默认使用的是 CUBIC(RFC 9438),它保留了慢开始和快重传,区别在拥塞避免阶段:cwnd 按距离上次丢包的时间的三次函数增长,先快速回到丢包前的大小,在附近放慢,然后再加速探测更大的带宽,在带宽大、延迟高的网络中比每轮加 1 增长得快。Google 的 BBR 则不再把丢包作为主要的拥塞信号,而是根据测得的瓶颈带宽和最小 RTT 来控制发送速率。
<div align="center"> <img src="pics/tcp-fast-retransmit.png" width="620px"/> </div><br>
## TCP 粘包与拆包
@@ -237,3 +228,15 @@ static String receive(DataInputStream in) throws IOException {
```
需要注意的是,长度字段来自网络,必须限制上限,否则对方发来一个 2 GB 的长度,服务器就会为它分配 2 GB 内存。
## 参考资料
- 谢希仁. 计算机网络 [M]. 电子工业出版社, 2017.
- [RFC 768: User Datagram Protocol](https://www.rfc-editor.org/rfc/rfc768)
- [RFC 9293: Transmission Control Protocol (TCP)](https://www.rfc-editor.org/rfc/rfc9293)
- [RFC 5681: TCP Congestion Control](https://www.rfc-editor.org/rfc/rfc5681)
- [RFC 6298: Computing TCP's Retransmission Timer](https://www.rfc-editor.org/rfc/rfc6298)
- [RFC 6928: Increasing TCP's Initial Window](https://www.rfc-editor.org/rfc/rfc6928)
- [RFC 9438: CUBIC for Fast and Long-Distance Networks](https://www.rfc-editor.org/rfc/rfc9438)
- [BBR: Congestion-Based Congestion Control](https://queue.acm.org/detail.cfm?id=3022184)
- [Linux ip-sysctl: tcp_tw_reuse、ip_local_port_range](https://www.kernel.org/doc/html/latest/networking/ip-sysctl.html)