diff --git a/notes/pics/arp-hops.png b/notes/pics/arp-hops.png new file mode 100644 index 000000000..a8e349156 Binary files /dev/null and b/notes/pics/arp-hops.png differ diff --git a/notes/pics/arp-request.png b/notes/pics/arp-request.png new file mode 100644 index 000000000..0d5c0d7ea Binary files /dev/null and b/notes/pics/arp-request.png differ diff --git a/notes/pics/bgp.png b/notes/pics/bgp.png new file mode 100644 index 000000000..1d6184f91 Binary files /dev/null and b/notes/pics/bgp.png differ diff --git a/notes/pics/icmp-format.png b/notes/pics/icmp-format.png new file mode 100644 index 000000000..9fb7e2987 Binary files /dev/null and b/notes/pics/icmp-format.png differ diff --git a/notes/pics/ip-classes.png b/notes/pics/ip-classes.png new file mode 100644 index 000000000..3549c71b9 Binary files /dev/null and b/notes/pics/ip-classes.png differ diff --git a/notes/pics/ip-fragment.png b/notes/pics/ip-fragment.png new file mode 100644 index 000000000..73788040d Binary files /dev/null and b/notes/pics/ip-fragment.png differ diff --git a/notes/pics/ip-header.png b/notes/pics/ip-header.png new file mode 100644 index 000000000..5a239cf49 Binary files /dev/null and b/notes/pics/ip-header.png differ diff --git a/notes/pics/nat.png b/notes/pics/nat.png new file mode 100644 index 000000000..fff6f3669 Binary files /dev/null and b/notes/pics/nat.png differ diff --git a/notes/pics/route-lookup.png b/notes/pics/route-lookup.png new file mode 100644 index 000000000..89e8f1916 Binary files /dev/null and b/notes/pics/route-lookup.png differ diff --git a/notes/pics/router-structure.png b/notes/pics/router-structure.png new file mode 100644 index 000000000..dd8ecac25 Binary files /dev/null and b/notes/pics/router-structure.png differ diff --git a/notes/pics/tcp-cwnd.png b/notes/pics/tcp-cwnd.png new file mode 100644 index 000000000..90dcb13af 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 000000000..0007f464c 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 000000000..7d82af7d4 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 000000000..3a608d65e 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 000000000..82244d030 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 000000000..10c7c973c Binary files /dev/null and b/notes/pics/tcp-sliding-window.png differ diff --git a/notes/pics/tcp-sticky.png b/notes/pics/tcp-sticky.png new file mode 100644 index 000000000..1584d5b12 Binary files /dev/null and b/notes/pics/tcp-sticky.png differ diff --git a/notes/pics/tcp-teardown.png b/notes/pics/tcp-teardown.png new file mode 100644 index 000000000..897070b20 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 000000000..a8a18882b Binary files /dev/null and b/notes/pics/udp-header.png differ diff --git a/notes/pics/vpn-tunnel.png b/notes/pics/vpn-tunnel.png new file mode 100644 index 000000000..a5252a9d8 Binary files /dev/null and b/notes/pics/vpn-tunnel.png differ diff --git "a/notes/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234 - \344\274\240\350\276\223\345\261\202.md" "b/notes/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234 - \344\274\240\350\276\223\345\261\202.md" index 935d5c164..ad3441878 100644 --- "a/notes/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234 - \344\274\240\350\276\223\345\261\202.md" +++ "b/notes/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234 - \344\274\240\350\276\223\345\261\202.md" @@ -12,6 +12,7 @@ * [TCP 拥塞控制](#tcp-拥塞控制) * [1. 慢开始与拥塞避免](#1-慢开始与拥塞避免) * [2. 快重传与快恢复](#2-快重传与快恢复) + * [TCP 粘包与拆包](#tcp-粘包与拆包) @@ -25,13 +26,13 @@ ## UDP 首部格式 -

+

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

+

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

+

假设 A 为客户端,B 为服务器端。 @@ -67,11 +68,11 @@ 第三次握手是为了防止失效的连接请求到达服务器,让服务器错误打开连接。 -客户端发送的连接请求如果在网络中滞留,那么就会隔很长一段时间才能收到服务器端发回的连接确认。客户端等待一个超时重传时间之后,就会重新请求连接。但是这个滞留的连接请求最后还是会到达服务器,如果不进行三次握手,那么服务器就会打开两个连接。如果有第三次握手,客户端会忽略服务器之后发送的对滞留连接请求的连接确认,不进行第三次握手,因此就不会再次打开连接。 +客户端发送的连接请求如果在网络中滞留,那么就会隔很长一段时间才能收到服务器端发回的连接确认。客户端等待一个超时重传时间之后,就会重新请求连接。但是这个滞留的连接请求最后还是会到达服务器,如果不进行三次握手,那么服务器就会打开两个连接。有了第三次握手,客户端收到服务器对滞留请求的确认时,发现确认号不是自己期望的,就回复 RST,服务器收到后放弃这个连接,不会多打开一个连接。 ## TCP 的四次挥手 -

+

以下描述不讨论序号和确认号,因为序号和确认号的规则比较简单。并且不讨论 ACK,因为 ACK 在连接建立之后都为 1。 @@ -97,19 +98,41 @@ - 等待一段时间是为了让本连接持续时间内所产生的所有报文都从网络中消失,使得下一个新的连接不会出现旧的连接请求报文。 +2MSL 正好覆盖一个来回:A 的确认最多在网络中存活 1 MSL,B 超时重传的 FIN 最多再存活 1 MSL。Linux 中 TIME_WAIT 固定为 60 秒(TCP_TIMEWAIT_LEN)。 + +**大量 TIME_WAIT 与 CLOSE_WAIT** + +TIME_WAIT 出现在主动关闭的一方,使用短连接的机器会积累大量 TIME_WAIT,例如每次请求都新建连接去访问下游服务。每个 TIME_WAIT 连接占用一个本地端口 60 秒,Linux 默认的本地端口范围是 32768~60999,共 28232 个,连接同一个目标地址和端口时,每秒新建超过约 470 个连接就会耗尽端口。 + +解决方案: + +- 使用长连接或连接池,例如 HTTP 的 keep-alive、数据库连接池; +- 在发起连接的一方开启 net.ipv4.tcp_tw_reuse,允许新连接复用处于 TIME_WAIT 的端口,它依靠 TCP 时间戳选项识别旧连接的报文; +- 扩大本地端口范围 net.ipv4.ip_local_port_range。 + +另一个参数 tcp_tw_recycle 在 NAT 环境下会丢弃正常的连接,Linux 4.12 已将其移除。 + +CLOSE_WAIT 出现在被动关闭的一方,表示对方已经关闭,本方的应用还没有调用 close()。连接大量停在 CLOSE_WAIT 通常是程序的问题,例如异常分支没有关闭连接、从连接池借出的连接没有归还,需要排查代码,保证每个连接最终都会被关闭。 + ## TCP 可靠传输 TCP 使用超时重传来实现可靠传输:如果一个已经发送的报文段在超时时间内没有收到确认,那么就重传这个报文段。 一个报文段从发送再到接收到确认所经过的时间称为往返时间 RTT,加权平均往返时间 RTTs 计算如下: -

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

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

+

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

+

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

+

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

+

+ +## TCP 粘包与拆包 + +TCP 面向字节流,只保证字节按顺序到达,不保留应用每次写入的边界。发送方写入两次,接收方的一次读取可能读到两条消息拼在一起,称为粘包;也可能只读到一条消息的一部分,称为拆包(也叫半包)。 + +例如客户端依次写入 hello、world 和一条 200000 字节的消息,服务器等待 200 ms 后,用 1024 字节的缓冲区反复读取: + +

+ +产生的原因主要有以下几种: + +- 数据先进入接收缓冲区,应用读取时缓冲区里有多少就读出多少,最多读满自己的缓冲区; +- 发送方的 Nagle 算法会把多次写入的小数据合并成一个报文段发送; +- 超过最大报文段长度(Maximum Segment Size, MSS)的消息会被分成多个报文段,可能分多次读到。 + +UDP 面向报文,一次读取得到的就是对方一次发送的完整数据报,不存在粘包。 + +解决方法是在应用层约定消息的边界,常用以下三种: + +- 固定长度:每条消息长度相同,不足的部分补齐,浪费空间; +- 分隔符:用特殊字符标记消息结束,例如 Redis 协议的每一行以 `\r\n` 结束,消息中出现分隔符时需要转义; +- 长度字段:在消息头部写明消息体的长度,接收方先读长度,再读够这么多字节。 + +HTTP/1.1 同时用了后两种:首部以一个空行结束,消息体的长度由 Content-Length 给出,或者用分块传输编码在每一块前写明长度。Netty 提供了对应的解码器 FixedLengthFrameDecoder、DelimiterBasedFrameDecoder 和 LengthFieldBasedFrameDecoder。 + +用长度字段收发消息的最小实现如下,按这种格式重复上面的例子,接收方依次得到 hello、world 和 200000 字节的消息: + +```java +static final int MAX = 1 << 20; + +static void send(DataOutputStream out, String msg) throws IOException { + byte[] body = msg.getBytes(StandardCharsets.UTF_8); + out.writeInt(body.length); // 4 字节的长度 + out.write(body); + out.flush(); +} + +static String receive(DataInputStream in) throws IOException { + int len = in.readInt(); // 读满 4 字节 + if (len < 0 || len > MAX) { + throw new IOException("bad length " + len); + } + byte[] body = new byte[len]; + in.readFully(body); // 读满 len 字节,不够就继续等待 + return new String(body, StandardCharsets.UTF_8); +} +``` + +需要注意的是,长度字段来自网络,必须限制上限,否则对方发来一个 2 GB 的长度,服务器就会为它分配 2 GB 内存。 diff --git "a/notes/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234 - \347\275\221\347\273\234\345\261\202.md" "b/notes/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234 - \347\275\221\347\273\234\345\261\202.md" index 2f382744a..e82273009 100644 --- "a/notes/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234 - \347\275\221\347\273\234\345\261\202.md" +++ "b/notes/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234 - \347\275\221\347\273\234\345\261\202.md" @@ -38,7 +38,7 @@ ## IP 数据报格式 -

+

- **版本** : 有 4(IPv4)和 6(IPv6)两个值; @@ -58,7 +58,7 @@ - **片偏移** : 和标识符一起,用于发生分片的情况。片偏移的单位为 8 字节。 -

+

## IP 地址编址方式 @@ -74,7 +74,7 @@ IP 地址的编址方式经历了三个历史阶段: IP 地址 ::= {\< 网络号 \>, \< 主机号 \>} -

+

### 2. 子网划分 @@ -104,27 +104,33 @@ CIDR 的地址掩码可以继续称为子网掩码,子网掩码首 1 长度为 网络层实现主机之间的通信,而链路层实现具体每段链路之间的通信。因此在通信过程中,IP 数据报的源地址和目的地址始终不变,而 MAC 地址随着链路的改变而改变。 -

+

ARP 实现由 IP 地址得到 MAC 地址。 -

- 每个主机都有一个 ARP 高速缓存,里面有本局域网上的各主机和路由器的 IP 地址到 MAC 地址的映射表。 如果主机 A 知道主机 B 的 IP 地址,但是 ARP 高速缓存中没有该 IP 地址到 MAC 地址的映射,此时主机 A 通过广播的方式发送 ARP 请求分组,主机 B 收到该请求后会发送 ARP 响应分组给主机 A 告知其 MAC 地址,随后主机 A 向其高速缓存中写入主机 B 的 IP 地址到 MAC 地址的映射。 -

+

## 网际控制报文协议 ICMP ICMP 是为了更有效地转发 IP 数据报和提高交付成功的机会。它封装在 IP 数据报中,但是不属于高层协议。 -

+

ICMP 报文分为差错报告报文和询问报文。 -

+| 类型 | 名称 | 种类 | 用途 | +| --- | --- | --- | --- | +| 0 | 回送回答 | 询问 | ping 收到的回答 | +| 3 | 终点不可达 | 差错报告 | 主机、网络或端口不可达,Traceroute 到达终点时收到端口不可达 | +| 5 | 改变路由(重定向) | 差错报告 | 路由器告诉主机有更好的下一跳 | +| 8 | 回送请求 | 询问 | ping 发出的请求 | +| 11 | 时间超过 | 差错报告 | TTL 减到 0 时由路由器发出,Traceroute 靠它得到沿途的路由器 | +| 12 | 参数问题 | 差错报告 | 首部字段的值有错 | +| 13、14 | 时间戳请求、回答 | 询问 | 很少使用 | ### 1. Ping @@ -155,9 +161,9 @@ Traceroute 发送的 IP 数据报封装的是无法交付的 UDP 用户数据报 VPN 使用公用的互联网作为本机构各专用网之间的通信载体。专用指机构内的主机只与本机构内的其它主机通信;虚拟指好像是,而实际上并不是,它有经过公用的互联网。 -下图中,场所 A 和 B 的通信经过互联网,如果场所 A 的主机 X 要和另一个场所 B 的主机 Y 通信,IP 数据报的源地址是 10.1.0.1,目的地址是 10.2.0.3。数据报先发送到与互联网相连的路由器 R1,R1 对内部数据进行加密,然后重新加上数据报的首部,源地址是路由器 R1 的全球地址 125.1.2.3,目的地址是路由器 R2 的全球地址 194.4.5.6。路由器 R2 收到数据报后将数据部分进行解密,恢复原来的数据报,此时目的地址为 10.2.0.3,就交付给 Y。 +下图中,场所 A 和 B 的通信经过互联网,如果场所 A 的主机 X 要和另一个场所 B 的主机 Y 通信,IP 数据报的源地址是 10.1.0.5,目的地址是 10.2.0.8。数据报先发送到与互联网相连的路由器 R1,R1 对内部数据进行加密,然后重新加上数据报的首部,源地址是路由器 R1 的全球地址 203.0.113.1,目的地址是路由器 R2 的全球地址 198.51.100.2。路由器 R2 收到数据报后将数据部分进行解密,恢复原来的数据报,此时目的地址为 10.2.0.8,就交付给 Y。 -

+

## 网络地址转换 NAT @@ -165,7 +171,7 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。 在以前,NAT 将本地 IP 和全球 IP 一一对应,这种方式下拥有 n 个全球 IP 地址的专用网内最多只可以同时有 n 台主机接入互联网。为了更有效地利用全球 IP 地址,现在常用的 NAT 转换表把传输层的端口号也用上了,使得多个专用网内部的主机共用一个全球 IP 地址。使用端口号的 NAT 也叫做网络地址与端口转换 NAPT。 -

+

## 路由器的结构 @@ -173,7 +179,7 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。 分组转发结构由三个部分组成:交换结构、一组输入端口和一组输出端口。 -

+

## 路由器分组转发流程 @@ -184,7 +190,9 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。 - 若路由表中有一个默认路由,则把数据报传送给路由表中所指明的默认路由器; - 报告转发分组出错。 -

+实际的路由器用最长前缀匹配统一处理以上几种情况:特定主机路由的前缀长度为 32,默认路由为 0,查找时在匹配的表项中选前缀最长的一个。 + +

## 路由选择协议 @@ -241,4 +249,4 @@ BGP 只能寻找一条比较好的路由,而不是最佳路由。 每个 AS 都必须配置 BGP 发言人,通过在两个相邻 BGP 发言人之间建立 TCP 连接来交换路由信息。 -

+

diff --git a/tools/diagram/src/arp-hops.svg b/tools/diagram/src/arp-hops.svg new file mode 100644 index 000000000..841ea6767 --- /dev/null +++ b/tools/diagram/src/arp-hops.svg @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + IP 地址不变,MAC 地址逐段改变 + + + + + H1 + 192.168.1.10 + + + + + + + + + + R1 + + + + + + + + + + R2 + + + + + H2 + 10.0.2.20 + + 局域网 1 + + 链路 2 + + 局域网 3 + R1-1 + R1-2 + R2-1 + R2-2 + + 帧首部 + + MAC H1 → R1-1 + + IP .1.10 → 10.0.2.20 + + + 帧首部 + + MAC R1-2 → R2-1 + + IP .1.10 → 10.0.2.20 + + + 帧首部 + + MAC R2-2 → H2 + + IP .1.10 → 10.0.2.20 + + 淡紫色是帧首部中的 MAC 地址,每经过一段链路就换成这一段两端的地址; + 浅蓝色是 IP 首部中的地址,从 H1 到 H2 一直不变(.1.10 即 192.168.1.10)。 + H1 发送前用 ARP 查的是下一跳 R1-1 的 MAC 地址,而不是 H2 的;R1、R2 转发时也各自查下一跳。 + diff --git a/tools/diagram/src/arp-request.svg b/tools/diagram/src/arp-request.svg new file mode 100644 index 000000000..07f0043ec --- /dev/null +++ b/tools/diagram/src/arp-request.svg @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + ARP:A 查询 192.168.1.20 的 MAC 地址 + + + + + + + A + 192.168.1.10 + + + + + + B + 192.168.1.20 + + + + + + C + 192.168.1.30 + + + + + + D + 192.168.1.40 + + ① 广播 ARP 请求,目的 MAC ff:ff:ff:ff:ff:ff + “谁是 192.168.1.20?请告诉 192.168.1.10” + + + + 不是自己的 IP,丢弃 + 丢弃 + + ② B 单播 ARP 响应:192.168.1.20 的 MAC 是 52:54:00:aa:00:20 + + + ③ A 的 ARP 缓存 + + 192.168.1.20 → 52:54:00:aa:00:20 + A 之后发给 192.168.1.20 的帧, + 直接使用缓存中的 MAC 地址; + B 收到请求时也顺便记下 A 的映射。 + 缓存项有过期时间,过期后重新查询。 + diff --git a/tools/diagram/src/bgp.svg b/tools/diagram/src/bgp.svg new file mode 100644 index 000000000..c8645f8e4 --- /dev/null +++ b/tools/diagram/src/bgp.svg @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + BGP:自治系统之间交换路由 + + AS1 + 内部用 OSPF + + AS2 + 内部用 OSPF + + AS3 + 内部用 RIP + + AS4 + 内部用 OSPF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BGP 会话(TCP 179) + + + + 203.0.113.0/24,路径 AS3 + ① AS3 通告自己的网络 + + + + 203.0.113.0/24,路径 AS4 AS3 + ② AS4 把自己加到路径前面再转告 + 浅青色是各 AS 的 BGP 发言人。AS1 从 AS4 和 AS2 都可能收到到 203.0.113.0/24 的路由,按策略和路径长度选一条; + 路径中出现自己的 AS 号说明有环路,直接丢弃。 + diff --git a/tools/diagram/src/icmp-format.svg b/tools/diagram/src/icmp-format.svg new file mode 100644 index 000000000..2836a64bd --- /dev/null +++ b/tools/diagram/src/icmp-format.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + ICMP 报文:以 ping 发出的回送请求为例 + + IP 首部(协议 = 1) + + ICMP 报文 + + + + 类型 + 8(回送请求) + + 代码 + 0 + + 检验和 + 16 位 + + 标识符 + 区分不同的 ping 进程 + + 序号 + 每发一个加 1 + + 数据 + 通常带上发送时间,用来计算往返时间 + + 所有 ICMP 报文 + 前 4 字节相同 + + 其余部分 + 取决于类型 + ICMP 封装在 IP 数据报中,但它不是传输层协议,而是网络层的一部分。 + 目的主机返回回送回答(类型 0),标识符、序号和数据原样带回。 + 差错报告报文的数据部分是出错数据报的 IP 首部和数据的前 8 字节,源主机据此知道是哪个报文出了错。 + diff --git a/tools/diagram/src/ip-classes.svg b/tools/diagram/src/ip-classes.svg new file mode 100644 index 000000000..6348aade5 --- /dev/null +++ b/tools/diagram/src/ip-classes.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + 分类的 IP 地址 + 0 + 31 + A 类 + + 0 + + 网络号 7 位 + + 主机号 24 位 + 首字节 1~126 + 每个网络 2²⁴ − 2 台主机,例如 10.1.2.3 + B 类 + + 10 + + 网络号 14 位 + + 主机号 16 位 + 首字节 128~191 + 每个网络 65534 台主机,例如 172.16.5.4 + C 类 + + 110 + + 网络号 21 位 + + 主机号 8 位 + 首字节 192~223 + 每个网络 254 台主机,例如 192.168.1.10 + D 类 + + 1110 + + 多播组号 + 首字节 224~239 + 多播地址 + E 类 + + 1111 + + 保留 + 首字节 240~255 + 保留 + A 类中 0 开头和 127 开头的网络保留不分配,127.0.0.1 是本机回环地址。 + diff --git a/tools/diagram/src/ip-fragment.svg b/tools/diagram/src/ip-fragment.svg new file mode 100644 index 000000000..537a6d54b --- /dev/null +++ b/tools/diagram/src/ip-fragment.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + IP 分片:UDP 发送 3000 字节,经过 MTU 为 1500 的以太网 + 原始数据报 + + IP 首部 + + + UDP 首部 + 应用数据 3000 字节 + 数据部分 3008 字节 + + 0 + + 1480 + + 2960 + + 3008 + + + IP 首部 + + + UDP 首部 + 数据 1480 字节 + 分片 1:总长度 1500,MF = 1,片偏移 = 0 / 8 = 0 + + + IP 首部 + + 数据 1480 字节 + 分片 2:总长度 1500,MF = 1,片偏移 = 1480 / 8 = 185 + + + IP 首部 + + 48 字节 + 分片 3:总长度 68,MF = 0,片偏移 = 2960 / 8 = 370 + 每片的数据部分最多 1500 − 20 = 1480 字节,正好是 8 的倍数。三个分片的标识相同, + 目的主机按标识收集分片、按片偏移排列,收齐后重组;只有第一个分片带有 UDP 首部和端口号。 + diff --git a/tools/diagram/src/ip-header.svg b/tools/diagram/src/ip-header.svg new file mode 100644 index 000000000..13ec07f61 --- /dev/null +++ b/tools/diagram/src/ip-header.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + IPv4 首部 + 0 + 15 + 16 + 31 + + 版本 + 4 位 + + 首部长度 + 4 位 + + 区分服务 + 8 位 + + 总长度 + 16 位 + + 标识 + 16 位 + + 标志 + 3 位 + + 片偏移 + 13 位 + + 生存时间 + 8 位 + + 协议 + 8 位 + + 首部检验和 + 16 位 + + 源 IP 地址 + 32 位 + + 目的 IP 地址 + 32 位 + + 选项 + 长度可变 + + 填充 + 补齐 4 字节 + + 数据(例如 TCP 报文段、UDP 数据报) + + 固定 20 字节 + + 0~40 字节 + 淡紫色的三个字段用于分片:标志的后两位是 DF(为 1 时不允许分片)和 MF(为 1 时后面还有分片)。 + 首部长度以 4 字节为单位,片偏移以 8 字节为单位;总长度包括首部和数据,最大 65535 字节。 + 协议字段指出数据部分交给谁处理:1 是 ICMP,6 是 TCP,17 是 UDP。 + diff --git a/tools/diagram/src/nat.svg b/tools/diagram/src/nat.svg new file mode 100644 index 000000000..c1e1274bb --- /dev/null +++ b/tools/diagram/src/nat.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + NAPT:两台主机共用一个全球地址 + + 专用网 192.168.0.0/24 + + + + + H1 + 192.168.0.3 + + + + + H2 + 192.168.0.4 + + + + + + + + + + NAT 路由器 + 203.0.113.5 + + + + + + + + + + 服务器 + 198.51.100.7 + + + + ① H1 的请求经过 NAT,源地址和端口被改写 + + + 192.168.0.3:30000 → 198.51.100.7:443 + + + 203.0.113.5:40001 → 198.51.100.7:443 + + + NAPT 转换表:内部地址 ↔ 外部地址 + + 192.168.0.3:30000 ↔ 203.0.113.5:40001 + + 192.168.0.4:30000 ↔ 203.0.113.5:40002 + ② 响应按目的端口 40001 查表,改回 H1 + + + 198.51.100.7:443 → 203.0.113.5:40001 + + + 198.51.100.7:443 → 192.168.0.3:30000 + H1、H2 的源端口都是 30000,NAT 为它们分配 + 不同的外部端口,响应回来时靠外部端口区分。 + 外部主机无法主动连接 H1:转换表中没有对应的表项,除非在 NAT 上配置端口映射。 + diff --git a/tools/diagram/src/route-lookup.svg b/tools/diagram/src/route-lookup.svg new file mode 100644 index 000000000..d27582f18 --- /dev/null +++ b/tools/diagram/src/route-lookup.svg @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + 路由器 R0 转发分组:最长前缀匹配 + R0 的路由表 + + 目的网络 + + 下一跳 + + 192.168.1.0/24 + + 直接交付(eth0) + + 10.1.0.0/16 + + R2 + + 10.1.4.0/22 + + R3 + + 0.0.0.0/0 + + R1(默认路由) + 目的地址 10.1.5.9 + 10.1.5.9 + 00001010.00000001.00000101.00001001 + 10.1.0.0/16 + 00001010.00000001.00000000.00000000 + 10.1.4.0/22 + 00001010.00000001.00000100.00000000 + /16 和 /22 都匹配,选前缀更长的 /22,交给 R3。 + 目的地址 8.8.8.8 + 只有默认路由 0.0.0.0/0 匹配,交给 R1。 + 目的地址 192.168.1.7 + 在直接相连的网络中,用 ARP 查出 MAC 地址后直接交付。 + 0.0.0.0/0 的前缀长度为 0, + 能匹配任何地址,只在没有 + 其它表项匹配时才使用。 + 没有任何表项匹配(也没有默认路由)时,丢弃分组并向源主机发送 ICMP 终点不可达报文。 + diff --git a/tools/diagram/src/router-structure.svg b/tools/diagram/src/router-structure.svg new file mode 100644 index 000000000..821da3a05 --- /dev/null +++ b/tools/diagram/src/router-structure.svg @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + 路由器的结构 + + 路由选择部分(控制) + + + + + + + + + 路由选择处理机:运行 RIP、OSPF、BGP, + 与相邻路由器交换信息,计算出路由表, + 再生成转发表下发到各输入端口 + + 分组转发部分(数据) + 输入端口 + 转发表 + 输出端口 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 交换结构 + 把分组从输入端口 + 送到输出端口 + + 输入端口按目的地址查转发表,决定送往哪个输出端口;分组到达得比处理得快时就在端口排队,队列满了就丢弃。 + diff --git a/tools/diagram/src/tcp-cwnd.svg b/tools/diagram/src/tcp-cwnd.svg new file mode 100644 index 000000000..5efec8803 --- /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 000000000..500c362f4 --- /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 000000000..52019c990 --- /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 000000000..04a3ceef4 --- /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 000000000..0089a92a0 --- /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 000000000..d233bcca9 --- /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-sticky.svg b/tools/diagram/src/tcp-sticky.svg new file mode 100644 index 000000000..7e90c7b72 --- /dev/null +++ b/tools/diagram/src/tcp-sticky.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + 粘包与拆包:写入 3 次,读取 3 次,边界对不上 + ① 发送方写入 + write 3 次 + + hello + + world + + 第 3 条消息 200000 字节 + ② 字节流 + 没有消息边界 + + hello + + world + + + 共 200010 字节 + + + ③ 接收方读取 + read 3 次 + + 第 1 次 1024 字节 + hello + world + 1014 + + 第 2 次 131072 字节 + + 第 3 次 67914 字节 + 第 1 次读到两条完整消息和第 3 条的开头(粘包),第 3 条消息分 3 次才读完(拆包)。 + ④ 加上长度字段 + 先读 4 字节长度 + + 5 + + hello + + 5 + + world + + 200000 + + 消息体 200000 字节 + 灰色是 4 字节的长度:接收方先读满 4 字节得到长度 n,再读满 n 字节,就是一条完整的消息。 + macOS 本机实测:服务器等待 200 ms 后用 1024 字节的缓冲区读取,之后的读取大小随系统和缓冲区变化。 + diff --git a/tools/diagram/src/tcp-teardown.svg b/tools/diagram/src/tcp-teardown.svg new file mode 100644 index 000000000..270628fb0 --- /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 000000000..dfd8cc909 --- /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 位相加后取反,填入检验和;接收方重新计算,不一致就丢弃。 + diff --git a/tools/diagram/src/vpn-tunnel.svg b/tools/diagram/src/vpn-tunnel.svg new file mode 100644 index 000000000..28c24f5ac --- /dev/null +++ b/tools/diagram/src/vpn-tunnel.svg @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + VPN 隧道:场所 A 的 X 访问场所 B 的 Y + + 场所 A 10.1.0.0/16 + + 场所 B 10.2.0.0/16 + + + + + X + 10.1.0.5 + + + + + Y + 10.2.0.8 + + + + + + + + + + R1 + 203.0.113.1 + + + + + + + + + + R2 + 198.51.100.2 + + 互联网 + + 隧道 + + + + + 源 10.1.0.5 + 目的 10.2.0.8 + 数据 + + + 源 203.0.113.1 + 目的 198.51.100.2 + + + + 加密后的 + 原数据报 + + + 源 10.1.0.5 + 目的 10.2.0.8 + 数据 + ① X 发出的数据报 + ② R1 加密后加上新的首部 + ③ R2 解密,恢复原数据报 + + + 互联网上的路由器只看到 R1、R2 的全球地址,看不到专用地址和数据内容。 + 图中的全球地址取自 RFC 5737 规定的文档示例地址段。 +