diff --git a/notes/pics/tcp-cwnd.png b/notes/pics/tcp-cwnd.png new file mode 100644 index 00000000..90dcb13a Binary files /dev/null and b/notes/pics/tcp-cwnd.png differ diff --git a/notes/pics/tcp-fast-retransmit.png b/notes/pics/tcp-fast-retransmit.png new file mode 100644 index 00000000..0007f464 Binary files /dev/null and b/notes/pics/tcp-fast-retransmit.png differ diff --git a/notes/pics/tcp-flow-vs-congestion.png b/notes/pics/tcp-flow-vs-congestion.png new file mode 100644 index 00000000..7d82af7d Binary files /dev/null and b/notes/pics/tcp-flow-vs-congestion.png differ diff --git a/notes/pics/tcp-handshake.png b/notes/pics/tcp-handshake.png new file mode 100644 index 00000000..3a608d65 Binary files /dev/null and b/notes/pics/tcp-handshake.png differ diff --git a/notes/pics/tcp-header.png b/notes/pics/tcp-header.png new file mode 100644 index 00000000..82244d03 Binary files /dev/null and b/notes/pics/tcp-header.png differ diff --git a/notes/pics/tcp-sliding-window.png b/notes/pics/tcp-sliding-window.png new file mode 100644 index 00000000..10c7c973 Binary files /dev/null and b/notes/pics/tcp-sliding-window.png differ diff --git a/notes/pics/tcp-teardown.png b/notes/pics/tcp-teardown.png new file mode 100644 index 00000000..897070b2 Binary files /dev/null and b/notes/pics/tcp-teardown.png differ diff --git a/notes/pics/udp-header.png b/notes/pics/udp-header.png new file mode 100644 index 00000000..a8a18882 Binary files /dev/null and b/notes/pics/udp-header.png differ diff --git a/notes/计算机网络 - 传输层.md b/notes/计算机网络 - 传输层.md index 935d5c16..21305f18 100644 --- a/notes/计算机网络 - 传输层.md +++ b/notes/计算机网络 - 传输层.md @@ -25,13 +25,13 @@ ## UDP 首部格式 -

+

首部字段只有 8 个字节,包括源端口、目的端口、长度、检验和。12 字节的伪首部是为了计算检验和临时添加的。 ## TCP 首部格式 -

+

- **序号** :用于对字节流进行编号,例如序号为 301,表示第一个字节的编号为 301,如果携带的数据长度为 100 字节,那么下一个报文段的序号应为 401。 @@ -49,7 +49,7 @@ ## TCP 的三次握手 -

+

假设 A 为客户端,B 为服务器端。 @@ -71,7 +71,7 @@ ## TCP 的四次挥手 -

+

以下描述不讨论序号和确认号,因为序号和确认号的规则比较简单。并且不讨论 ACK,因为 ACK 在连接建立之后都为 1。 @@ -103,12 +103,18 @@ TCP 使用超时重传来实现可靠传输:如果一个已经发送的报文 一个报文段从发送再到接收到确认所经过的时间称为往返时间 RTT,加权平均往返时间 RTTs 计算如下: -

+```text +RTTs = (1 - a) × RTTs + a × RTT +``` + 其中,0 ≤ a < 1,RTTs 随着 a 的增加更容易受到 RTT 的影响。 超时时间 RTO 应该略大于 RTTs,TCP 使用的超时时间计算如下: -

+```text +RTO = RTTs + 4 × RTTd +``` + 其中 RTTd 为偏差的加权平均值。 ## TCP 滑动窗口 @@ -117,9 +123,9 @@ TCP 使用超时重传来实现可靠传输:如果一个已经发送的报文 发送窗口内的字节都允许被发送,接收窗口内的字节都允许被接收。如果发送窗口左部的字节已经发送并且收到了确认,那么就将发送窗口向右滑动一定距离,直到左部第一个字节不是已发送并且已确认的状态;接收窗口的滑动类似,接收窗口左部字节已经发送确认并交付主机,就向右滑动接收窗口。 -接收窗口只会对窗口内最后一个按序到达的字节进行确认,例如接收窗口已经收到的字节为 {31, 34, 35},其中 {31} 按序到达,而 {34, 35} 就不是,因此只对字节 31 进行确认。发送方得到一个字节的确认之后,就知道这个字节之前的所有字节都已经被接收。 +接收窗口只会对窗口内最后一个按序到达的字节进行确认,例如接收窗口已经收到的字节为 {31, 34, 35},其中 {31} 按序到达,而 {34, 35} 就不是,因此只对字节 31 进行确认,确认号为 32。发送方得到一个字节的确认之后,就知道这个字节之前的所有字节都已经被接收。 -

+

## TCP 流量控制 @@ -131,18 +137,18 @@ TCP 使用超时重传来实现可靠传输:如果一个已经发送的报文 如果网络出现拥塞,分组将会丢失,此时发送方会继续重传,从而导致网络拥塞程度更高。因此当出现拥塞时,应当控制发送方的速率。这一点和流量控制很像,但是出发点不同。流量控制是为了让接收方能来得及接收,而拥塞控制是为了降低整个网络的拥塞程度。 -

+

TCP 主要通过四个算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。 -发送方需要维护一个叫做拥塞窗口(cwnd)的状态变量,注意拥塞窗口与发送方窗口的区别:拥塞窗口只是一个状态变量,实际决定发送方能发送多少数据的是发送方窗口。 +发送方需要维护一个叫做拥塞窗口(cwnd)的状态变量,注意拥塞窗口与发送方窗口的区别:拥塞窗口只是一个状态变量,实际决定发送方能发送多少数据的是发送方窗口,它取拥塞窗口和接收方通告的窗口中的较小值。 为了便于讨论,做如下假设: - 接收方有足够大的接收缓存,因此不会发生流量控制; - 虽然 TCP 的窗口基于字节,但是这里设窗口的大小单位为报文段。 -

+

### 1. 慢开始与拥塞避免 @@ -164,4 +170,4 @@ TCP 主要通过四个算法来进行拥塞控制:慢开始、拥塞避免、 慢开始和快恢复的快慢指的是 cwnd 的设定值,而不是 cwnd 的增长速率。慢开始 cwnd 设定为 1,而快恢复 cwnd 设定为 ssthresh。 -

+

diff --git a/tools/diagram/src/tcp-cwnd.svg b/tools/diagram/src/tcp-cwnd.svg new file mode 100644 index 00000000..5efec880 --- /dev/null +++ b/tools/diagram/src/tcp-cwnd.svg @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + 拥塞窗口的变化:初始 ssthresh = 8,窗口单位为报文段 + + + cwnd + 0 + 2 + + 4 + + 6 + + 8 + + 10 + + 12 + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 传输轮次 + + + + ssthresh = 8 + ssthresh = 6 + ssthresh = 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + ① cwnd 达到 ssthresh, + 改为每轮加 1 + + + ② 超时:ssthresh = 12 / 2 = 6, + cwnd 从 1 重新慢开始 + + + ③ 4 翻倍会超过 6, + 只增大到 ssthresh + + + ④ 3 个重复确认: + ssthresh = 10 / 2 = 5,cwnd = 5 + 慢开始 + 慢开始 + 拥塞避免 + 拥塞避免 + 快恢复后拥塞避免 + 浅青色点处于慢开始(cwnd < ssthresh),每轮翻倍;浅蓝色点处于拥塞避免,每轮加 1。 + diff --git a/tools/diagram/src/tcp-fast-retransmit.svg b/tools/diagram/src/tcp-fast-retransmit.svg new file mode 100644 index 00000000..500c362f --- /dev/null +++ b/tools/diagram/src/tcp-fast-retransmit.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + 快重传:M3 丢失 + + 发送方 + + + 接收方 + + 发送 M1 + + + 确认 M1 + 发送 M2 + + + 确认 M2 + 发送 M3 + + + + 发送 M4 + + + 重复确认 M2 + 发送 M5 + + + 重复确认 M2 + 发送 M6 + + + 重复确认 M2 + 丢失 + + 收到 3 个重复确认, + 不等超时,立即重传 M3 + + 重传 M3 + + 确认 M6 + M4~M6 已缓存 + 接收方每收到一个失序的报文段,就重复确认最后一个按序到达的报文段(这里是 M2)。 + 重复确认说明后面的报文段还能到达,网络没有严重拥塞,所以只重传丢失的 M3。 + diff --git a/tools/diagram/src/tcp-flow-vs-congestion.svg b/tools/diagram/src/tcp-flow-vs-congestion.svg new file mode 100644 index 00000000..52019c99 --- /dev/null +++ b/tools/diagram/src/tcp-flow-vs-congestion.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + 流量控制与拥塞控制 + + + + + + + + + + 发送方 A + + 发送窗口 = min(cwnd, rwnd) + = min(8, 20) = 8 个报文段 + + 网络 + + + + + + + + + + 路由器缓存已满 + + + 丢包 + + + + + + + + + + 拥塞控制:A 根据超时和重复确认 + 推测网络拥塞,自己维护 cwnd + + + + + + + + + + + 接收方 B + 还能接收 20 个报文段 + + + ACK,窗口 rwnd = 20 + + + 流量控制:rwnd 由接收方在确认中告知,保证接收方来得及接收 + diff --git a/tools/diagram/src/tcp-handshake.svg b/tools/diagram/src/tcp-handshake.svg new file mode 100644 index 00000000..04a3ceef --- /dev/null +++ b/tools/diagram/src/tcp-handshake.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + TCP 三次握手 + + 客户端 A + + + 服务器 B + + + CLOSED + + LISTEN + + SYN=1, seq=1000 + + SYN-SENT + + SYN-RCVD + + SYN=1, ACK=1, seq=5000, ack=1001 + + ESTABLISHED + + ACK=1, seq=1001, ack=5001 + + ESTABLISHED + + 开始传输数据 + A 的初始序号 x = 1000,B 的初始序号 y = 5000。 + SYN 不携带数据也要占用一个序号,所以确认号分别是 x + 1 = 1001 和 y + 1 = 5001。 + diff --git a/tools/diagram/src/tcp-header.svg b/tools/diagram/src/tcp-header.svg new file mode 100644 index 00000000..0089a92a --- /dev/null +++ b/tools/diagram/src/tcp-header.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + TCP 首部 + 0 + 15 + 16 + 31 + + 源端口 + 16 位 + + 目的端口 + 16 位 + + 序号 + 本报文段数据第一个字节的编号 + + 确认号 + 期望收到的下一个字节的编号 + + 数据偏移 + 4 位 + + 保留 + 4 位 + + C + + E + + U + + A + + P + + R + + S + + F + + 窗口 + 16 位 + + 检验和 + 16 位 + + 紧急指针 + 16 位 + + 选项 + 长度可变 + + 填充 + 补齐 4 字节 + + 数据(可以没有) + + 固定 20 字节 + + 0~40 字节,例如 MSS、 + 窗口扩大、SACK、时间戳 + 标志位:URG 紧急,ACK 确认,PSH 推送,RST 复位连接,SYN 建立连接,FIN 释放连接; + CWR、ECE 用于显式拥塞通知(ECN)。淡紫色是正文介绍的 ACK、SYN、FIN。 + 数据偏移以 4 字节为单位:5 表示首部 20 字节,最大值 15 表示 60 字节。 + diff --git a/tools/diagram/src/tcp-sliding-window.svg b/tools/diagram/src/tcp-sliding-window.svg new file mode 100644 index 00000000..d233bcca --- /dev/null +++ b/tools/diagram/src/tcp-sliding-window.svg @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + 滑动窗口:A 发送了 31~36,B 收到 31、34、35 + ① A 发送 + 31~36 + + 28 + + 29 + + 30 + + 31 + + 32 + + 33 + + 34 + + 35 + + 36 + + 37 + + 38 + + 39 + + 40 + + 41 + + 42 + + 43 + + 44 + + 45 + + A 的发送窗口 10 字节:31~40 + 31~36 已发送,等待确认;37~40 还可以发送 + ② B 收到 + 31、34、35 + + 28 + + 29 + + 30 + + 31 + + 32 + + 33 + + 34 + + 35 + + 36 + + 37 + + 38 + + 39 + + 40 + + 41 + + 42 + + 43 + + 44 + + 45 + + B 的接收窗口:31 已按序收到,窗口移到 32~41 + 32、33、36 未到,34、35 先缓存;只能确认到 31,回复确认号 32、窗口 10 + ③ A 收到 + 确认号 32 + + 28 + + 29 + + 30 + + 31 + + 32 + + 33 + + 34 + + 35 + + 36 + + 37 + + 38 + + 39 + + 40 + + 41 + + 42 + + 43 + + 44 + + 45 + + A 的窗口右移 1 字节:32~41,可以再发 37~41 + + 已发送并收到确认 + + 已发送未确认 + + 窗口内,还可以发送或接收 + + 按序收到 + + 未按序收到,先缓存 + 之后 32、33 到达,B 一次确认到 35(确认号 36),A 的窗口随之右移 4 字节;36 要等它到达后才能确认。 + diff --git a/tools/diagram/src/tcp-teardown.svg b/tools/diagram/src/tcp-teardown.svg new file mode 100644 index 00000000..270628fb --- /dev/null +++ b/tools/diagram/src/tcp-teardown.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + TCP 四次挥手 + + 客户端 A + + + 服务器 B + + + 半关闭:只剩 B → A 方向 + + ESTABLISHED + + ESTABLISHED + + FIN=1, seq=3000 + + FIN-WAIT-1 + + CLOSE-WAIT + 通知应用程序, + 等它发完数据后调用 close() + + ACK=1, ack=3001 + + FIN-WAIT-2 + + B 继续发送剩余的数据 + + FIN=1, ACK=1, seq=9000, ack=3001 + + LAST-ACK + + ACK=1, seq=3001, ack=9001 + + TIME-WAIT + + + + 等待 2MSL + Linux 为 60 秒 + + CLOSED + + CLOSED + 主动关闭的一方最后进入 TIME-WAIT;被动关闭的一方在应用调用 close() 之前一直处于 CLOSE-WAIT。 + 如果最后的 ACK 丢失,B 会重传 FIN,A 还在 TIME-WAIT 中,可以再确认一次。 + diff --git a/tools/diagram/src/udp-header.svg b/tools/diagram/src/udp-header.svg new file mode 100644 index 00000000..dfd8cc90 --- /dev/null +++ b/tools/diagram/src/udp-header.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + UDP 首部与伪首部:一次 DNS 查询 + 0 + 15 + 16 + 31 + + 源 IP 地址 + 192.168.1.10 + + 目的 IP 地址 + 8.8.8.8 + + 全 0 + 0 + + 协议 + 17 + + UDP 长度 + 37 + + 伪首部 12 字节 + 从 IP 首部取来, + 只参与检验和计算, + 不随报文发送 + + 源端口 + 51000 + + 目的端口 + 53 + + 长度 + 37 + + 检验和 + 0x7CDB + UDP 首部 8 字节 + 每个字段 16 位 + + 数据 29 字节 + DNS 查询:example.com 的 A 记录 + UDP 数据 + 长度 = 首部 8 + 数据 29 = 37 字节。 + 发送方把伪首部、首部和数据按 16 位相加后取反,填入检验和;接收方重新计算,不一致就丢弃。 +