vault backup: 2026-05-27 00:12:00
This commit is contained in:
@@ -74,16 +74,16 @@ graph BT
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["OSI 七层模型<br/>ISO 标准"] --> B{谁先落地?}
|
||||
B -->|"1970s 末"| C["TCP/IP 已部署在 ARPANET"]
|
||||
B -->|"1980s 后"| D["OSI 标准太多太复杂"]
|
||||
B -->|"1983 Flag Day"| C["TCP/IP 正式接管 ARPANET"]
|
||||
B -->|"标准滞后"| D["OSI 标准太多太复杂"]
|
||||
C --> E["事实标准 ✅"]
|
||||
D --> E
|
||||
style E fill:#98FB98,color:#000
|
||||
```
|
||||
|
||||
- **先发优势**:ARPANET / Internet 先用上了 TCP/IP,基础设施已经建成
|
||||
- **先发优势**:1983 年 1 月 1 日(Flag Day),ARPANET 强制切换到 TCP/IP,基础设施已建成,生态不可逆转
|
||||
- **简单粗暴**:TCP/IP 只关心能不能跑通,不搞复杂的标准化流程
|
||||
- **开源免费**:Berkeley BSD Unix 内嵌 TCP/IP 协议栈,免费分发
|
||||
- **开源免费**:Berkeley BSD Unix 内嵌 TCP/IP 协议栈(1983 年 4.2BSD),免费分发
|
||||
- **OSI 的反面教材**:层层审批导致协议膨胀(X.400 邮件、X.25 分组交换),开发者根本懒得用
|
||||
|
||||
## 五层教学模型
|
||||
@@ -92,10 +92,10 @@ flowchart LR
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["应用层 — Application<br/>HTTP DNS SMTP SSH FTP SMTP POP3 IMAP WebSocket"] --> T["传输层 — Transport<br/>TCP UDP SCTP"]
|
||||
T --> N["网络层 — Internet<br/>IP IPv6 ICMP ARP NAT"]
|
||||
N --> D["链路层 — Link<br/>Ethernet Wi-Fi 802.11 VLAN HDLC PPP"]
|
||||
D --> P["物理层 — Physical<br/>双绞线 光纤 无线电 IEEE 802.15"]
|
||||
A["应用层 — Application<br/>HTTP, DNS, SMTP, SSH, FTP, POP3, IMAP, WebSocket"] --> T["传输层 — Transport<br/>TCP, UDP, SCTP"]
|
||||
T --> N["网络层 — Internet<br/>IP, IPv6, ICMP, ARP, NAT"]
|
||||
N --> D["链路层 — Link<br/>Ethernet, Wi-Fi, 802.11, VLAN, HDLC, PPP"]
|
||||
D --> P["物理层 — Physical<br/>双绞线, 光纤, 无线电, IEEE 802.15"]
|
||||
style A fill:#DDA0DD
|
||||
style T fill:#FFD700
|
||||
style N fill:#87CEEB
|
||||
@@ -153,7 +153,19 @@ sequenceDiagram
|
||||
> 每一层只知道自己那一层的 header 是什么格式,对其他层的内容一无所知。这就是**层间透明**原则。
|
||||
|
||||
> [!QUESTION] 如果每一层都要加头部,开销岂不是很大?
|
||||
> 没错。一个 1 字节的应用层数据,经过 TCP/IP/以太网三层封装后,可能膨胀到 50+ 字节(TCP 头 20B + IP 头 20B + 以太网帧头帧尾 18B)。这就是为什么对小数据量、低延迟场景,业界会选择 **UDP**(头部仅 8 字节)甚至 **QUIC**(合并传输层与加密层)来减少开销。
|
||||
> 没错。一个 1 字节的应用层数据,经过 TCP/IP/以太网三层封装后,可能膨胀到 58 字节(TCP 头 20B + IP 头 20B + 以太网帧头帧尾 18B)。这就是为什么对小数据量、低延迟场景,业界会选择 **UDP**(头部仅 8 字节)甚至 **QUIC**(合并传输层与加密层)来减少开销。
|
||||
|
||||
### MTU 与 MSS:分层边界如何约束数据大小
|
||||
|
||||
分层不只是理论模型,它直接决定了每层能传多大的数据:
|
||||
|
||||
| 概念 | 所在层 | 含义 | 典型值 |
|
||||
|------|--------|------|--------|
|
||||
| **MTU**(Maximum Transmission Unit) | 链路层 | 一个帧能承载的最大数据量 | 以太网默认 **1500B** |
|
||||
| **MSS**(Maximum Segment Size) | 传输层 | 一个 TCP 段能承载的最大应用数据 | 1500 - 20(IP) - 20(TCP) = **1460B** |
|
||||
|
||||
> [!QUESTION] 为什么下载大文件时网络层会有大量小包?
|
||||
> 当你要发一个 100KB 的文件时,TCP 会按 MSS(1460B)将其切割成约 70 个段,每个段再交给网络层封装成 Packet,最后由链路层加上帧头帧尾变成 Frame。**分层的每一级都有自己的大小限制**,超出就要分片或分段——这就是 Path MTU Discovery(路径 MTU 发现)存在的原因:找到整条路径上最小的 MTU,避免中间路由器做 IP 分片(分片会严重影响性能)。
|
||||
|
||||
## 分层在代码中的体现
|
||||
|
||||
|
||||
@@ -40,19 +40,19 @@ flowchart LR
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App as HTTP 应用
|
||||
participant TCP as TCP Socket
|
||||
participant IP as 网络层 IP
|
||||
participant ETH as 以太网驱动
|
||||
participant App as "HTTP 应用"
|
||||
participant TCP as "TCP Socket"
|
||||
participant IP as "网络层 IP"
|
||||
participant ETH as "以太网驱动"
|
||||
|
||||
App->>TCP: write("GET / HTTP/1.1...")
|
||||
Note over TCP: 分配 seq/ack, state=ESTABLISHED
|
||||
TCP->>IP: 传递 Segment (daddr=x.x.x.x, sport=1234, dport=80)
|
||||
Note over IP: 查路由表 → 下一跳网关
|
||||
IP->>ETH: 传递 Packet (dst_mac=?, src_ip=192.168.1.100, dst_ip=93.184.216.34)
|
||||
Note over ETH: ARP 解析 → dst_mac=aabb.ccdd.eeff
|
||||
ETH->>ETH: 拼接 源MAC+目的MAC+EtherType+Payload+FCS
|
||||
ETH-->>App: sendto() 返回 → 帧已发出 ✅
|
||||
App->>TCP: "write GET / HTTP/1.1..."
|
||||
Note over TCP: "分配 seq,ack, 连接已建立"
|
||||
TCP->>IP: "Segment, daddr=x.x.x.x, sport=1234, dport=80"
|
||||
Note over IP: "查路由表, 下一跳网关"
|
||||
IP->>ETH: "Packet, src_ip=192.168.1.100, dst_ip=93.184.216.34"
|
||||
Note over ETH: "ARP 解析, dst_mac=aabb.ccdd.eeff"
|
||||
ETH->>ETH: "拼接 srcMAC,dstMAC,EtherType,Payload,FCS"
|
||||
ETH-->>App: "sendto 返回, 帧已发出"
|
||||
```
|
||||
|
||||
### Ethernet II 帧完整结构
|
||||
@@ -69,22 +69,26 @@ Offset Size Field 说明
|
||||
|
||||
总长度范围:**64 ~ 1518 bytes**(不含 VLAN Tag;有 VLAN Tag 则上限 1522)。
|
||||
|
||||
> [!tip] 为什么有最小 64 字节的限制?
|
||||
> 以太网使用 CSMA/CD 检测冲突,发送方需要在发完帧之前检测到碰撞信号。64 字节是为了保证在最大网络直径内,信号能往返一次。如果 IP 包太小导致帧不够 64 字节,链路层会自动填充 **Padding** 补齐到最小长度。接收端根据 IP 头部的 "Total Length" 字段识别真实载荷边界,剥离多余的填充。
|
||||
|
||||
> [!note] MTU = 1500 是什么意思?
|
||||
> MTU(Maximum Transmission Unit)是链路层允许的最大 Payload 大小,即 IP 包的最大体积。超过会被分片(Fragmentation),但分片会降低性能且增加丢包风险——这也是现代应用层(如 TLS、gRPC)倾向于小包传输的原因。
|
||||
> MTU(Maximum Transmission Unit)是链路层允许的最大 Payload 大小,即 IP 包(头部 + 载荷)的最大体积。超过 1500 字节的 IP 包会被路由器**分片(Fragmentation)**——拆成多个小片段分别传输,接收端再重组。
|
||||
> 分片的代价:任何一个分片丢失都会导致整个包重传;每片都需要独立的 IP 头,浪费带宽。因此 TCP 在三次握手时会协商 **MSS**(Maximum Segment Size)来主动避分片,而 UDP 应用(如 DNS、QUIC)则通过程序层面控制载荷大小。更底层的 **PMTU Discovery** 机制还能动态探测路径上最小的 MTU。
|
||||
|
||||
## 接收端:逐层解封装
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant ETH as 网卡收到 Frame
|
||||
participant IP as 剥离以太网头
|
||||
participant TCP as 剥离 TCP 头
|
||||
participant App as 提取 HTTP 数据
|
||||
participant ETH as "网卡收到 Frame"
|
||||
participant IP as "剥离以太网头"
|
||||
participant TCP as "剥离 TCP 头"
|
||||
participant App as "提取 HTTP 数据"
|
||||
|
||||
ETH->>IP: 校验 FCS OK → 取 EtherType=0x0800 → 剥离以太网头
|
||||
IP->>TCP: 校验 IP Checksum → 取 Protocol=6(TCP) → 剥离 IP 头
|
||||
TCP->>App: 按 Sequence Number 重组 → 剥离 TCP 头
|
||||
App->>App: 解析 "GET / HTTP/1.1..." 🎉
|
||||
ETH->>IP: "FCS 校验通过, EtherType=0x0800, 剥离以太网头"
|
||||
IP->>TCP: "IP Checksum 正确, Protocol=6 TCP, 剥离 IP 头"
|
||||
TCP->>App: "按 Sequence Number 重组, 剥离 TCP 头"
|
||||
App->>App: "解析 GET / HTTP/1.1..."
|
||||
```
|
||||
|
||||
每层的动作可以概括为三步:
|
||||
@@ -95,32 +99,88 @@ sequenceDiagram
|
||||
|
||||
## 各层 PDU 对照表
|
||||
|
||||
| OSI 名称 | PDU | TCP/IP 对应 | 关键标识字段 |
|
||||
|----------|-----|-------------|-------------|
|
||||
| 数据 (Data) | 报文段 | 应用层消息 | — |
|
||||
| 段 (Segment) | TCP Segment | 传输层 PDU | 源端口 + 目的端口 |
|
||||
| 包 (Packet) | IP Datagram | 网络层 PDU | 源 IP + 目的 IP + Protocol |
|
||||
| 帧 (Frame) | Ethernet Frame | 链路层 PDU | 源 MAC + 目的 MAC + EtherType |
|
||||
| 比特流 (Bits) | Bitstream | 物理层信号 | 电信号 / 光脉冲 |
|
||||
| OSI 层 | PDU 名称 | TCP/IP 对应 | 关键标识字段 |
|
||||
|--------|---------|-------------|-------------|
|
||||
| 应用层 | 消息 (Message) | 应用层数据 | HTTP 请求行、DNS 报文等 |
|
||||
| 传输层 | 段/数据报 (Segment / Datagram) | 传输层 PDU | 源端口 + 目的端口 |
|
||||
| 网络层 | 包 (Packet / Datagram) | 网络层 PDU | 源 IP + 目的 IP + Protocol |
|
||||
| 链路层 | 帧 (Frame) | 链路层 PDU | 源 MAC + 目的 MAC + EtherType |
|
||||
| 物理层 | 比特流 (Bits) | 物理层信号 | 电信号 / 光脉冲 / 无线电波 |
|
||||
|
||||
> [!QUESTION] TCP 叫 Segment,UDP 叫什么?
|
||||
> UDP 的 PDU 通常叫 **Datagram**(数据报),因为它不建立连接、不做分段重组,每个数据报独立发送。所以同一层可以有不同的 PDU 名称,取决于使用的传输协议。
|
||||
|
||||
## NAT 场景下的封装变化
|
||||
|
||||
NAT 路由器修改了中间层的地址:
|
||||
|
||||
```
|
||||
发送端内部主机 NAT 路由器 外部 Web 服务器
|
||||
┌──────────────────────┐ ┌──────────────────────┐ ┌──────────────────────┐
|
||||
│ Src IP: 10.0.0.5:45678 │ │ SNAT: 改写 Src IP │ │ Dst IP: 203.0.113.1:80 │
|
||||
│ Dst IP: 203.0.113.1:80 │ → │ │ → │ │
|
||||
│ │ │ → Src IP: 8.8.8.8:45678 │ │ ← Src IP: 203.0.113.1:80 │
|
||||
│ │ │ → Dst IP: 203.0.113.1:80 │ │ → Src IP: 10.0.0.5:45678 │
|
||||
└──────────────────────┘ └──────────────────────┘ └──────────────────────┘
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Host as "内部主机"
|
||||
participant NAT as "NAT 路由器"
|
||||
participant Server as "外部服务器"
|
||||
|
||||
Host->>NAT: "Src=10.0.0.5:45678 Dst=203.0.113.1:80"
|
||||
Note over NAT: "SNAT 改写源地址"
|
||||
NAT->>Server: "Src=8.8.8.8:45678 Dst=203.0.113.1:80"
|
||||
Server->>NAT: "Src=203.0.113.1:80 Dst=8.8.8.8:45678"
|
||||
Note over NAT: "查 NAT 表, 反向翻译"
|
||||
NAT->>Host: "Src=203.0.113.1:80 Dst=10.0.0.5:45678"
|
||||
```
|
||||
|
||||
注意:NAT 不触碰应用层载荷,只是在中途修改了 IP 和 TCP 端口。这也意味着 **传输层的 Checksum 需要重新计算**。
|
||||
NAT **不触碰应用层载荷**,只修改 IP 头和 TCP/UDP 端口。但这也意味着**传输层的 Checksum 需要重新计算**,因为 TCP/UDP 的校验和覆盖了伪首部(Pseudo Header)中的源/目的 IP 地址。
|
||||
|
||||
## TCP vs UDP:封装有什么不同?
|
||||
|
||||
两者都在传输层,但封装风格截然不同:
|
||||
|
||||
| 对比项 | TCP | UDP |
|
||||
|--------|-----|-----|
|
||||
| 首部大小 | 最小 20 字节,最大 60 字节(含 Options) | 固定 8 字节 |
|
||||
| 连接状态 | 三次握手建连接,携带 seq/ack 等状态 | 无连接,每个数据报独立 |
|
||||
| 分段重组 | 传输层自动分段和重组 | 不分段,超过 MTU 则由 IP 层分片 |
|
||||
| 校验和 | 强制(IPv4/IPv6) | IPv4 可选,IPv6 强制 |
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph "TCP Segment"
|
||||
A["TCP Header 20-60B<br/>SrcPort DstPort Seq Ack Flags..."] --> B["Payload"]
|
||||
end
|
||||
subgraph "UDP Datagram"
|
||||
C["UDP Header 8B<br/>SrcPort DstPort Len Chksum"] --> D["Payload"]
|
||||
end
|
||||
|
||||
style A fill:#FFD700
|
||||
style C fill:#FFA07A
|
||||
```
|
||||
|
||||
> [!QUESTION] UDP 头只有 8 字节,是不是"更好"?
|
||||
> 更小的头部确实开销更低,但你失去的是可靠传输、流量控制、拥塞控制等保障。选择哪个取决于业务场景:文件传输用 TCP,游戏/视频/QUIC 用 UDP + 应用层自建可靠性。
|
||||
|
||||
## TLS 封装:在哪一层"套娃"?
|
||||
|
||||
TLS(Transport Layer Security)常被认为是"应用层协议",但它实际上在 **OSI 第 5-6 层(会话/表示层)** 工作,夹在应用层和传输层之间:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph "HTTPS = HTTP + TLS + TCP"
|
||||
H["HTTP 报文"] --> T["TLS 加密<br/>+ TLS Record Header 5B"]
|
||||
T --> C["TCP Segment"]
|
||||
C --> I["IP Packet"]
|
||||
end
|
||||
|
||||
style H fill:#DDA0DD
|
||||
style T fill:#FF6347
|
||||
style C fill:#FFD700
|
||||
style I fill:#87CEEB
|
||||
```
|
||||
|
||||
关键点:TLS Record 协议会在 HTTP 载荷前添加 **5 字节的 Record Header**(ContentType + Version + Length),然后整个 TLS Record 作为 TCP 的 Payload 继续向下封装。所以 HTTPS 比 HTTP 多了 TLS 的封装开销(握手阶段更大,数据阶段约 ~40 字节 overhead)。
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[hhs/NETWORK/OSI与TCP-IP模型对比]] — 分层模型的起源与对比
|
||||
- [[hhs/NETWORK/IPv4协议详解]] — IP 包的详细格式
|
||||
- [[hhs/NETWORK/TCP状态机详解]] — TCP 段的结构与状态管理
|
||||
- [[hhs/NETWORK/01-基础概念/01-OSI与TCP-IP模型对比]] — 分层模型的起源与对比
|
||||
- [[hhs/NETWORK/03-网络层/01-IPv4首部与分段重组]] — IP 包的详细格式与分片机制
|
||||
- [[hhs/NETWORK/04-传输层/01-TCP段结构与状态机]] — TCP 段的结构与状态管理
|
||||
- [[hhs/NETWORK/04-传输层/06-UDP协议与SCTP]] — UDP 的无连接封装特点
|
||||
- [[hhs/NETWORK/03-网络层/08-NAT原理与应用]] — NAT 的详细原理与配置
|
||||
|
||||
@@ -48,7 +48,7 @@ sequenceDiagram
|
||||
|
||||
Src->>Bcast: ARP Request "192.168.1.20 是谁?→ ff:ff:ff:ff:ff:ff"
|
||||
Dst-->>Src: ARP Reply "我是!MAC = aa:bb:cc:11:22:33"
|
||||
Src->>Cache: 写入缓存条目 (TTL ≈ 15~30 分钟)
|
||||
Src->>Cache: 写入缓存条目 (Linux 默认 ≈ 30s, Windows ≈ 15~45s, 可配置)
|
||||
Note over Src,Dst: 之后的通信直接用此 MAC,不再发 ARP
|
||||
```
|
||||
|
||||
@@ -67,7 +67,7 @@ $ arp -a
|
||||
|
||||
## 二、IP 地址(IPv4 32 位 / IPv6 128 位)
|
||||
|
||||
### IPv4 地址结构
|
||||
### IPv4 首部结构
|
||||
|
||||
```
|
||||
版本(4 bits) IHL(4 bits) DSCP(8 bits) Total Length(16 bits)
|
||||
@@ -91,6 +91,31 @@ Options (可选)...
|
||||
| NAT | 广泛使用(缓解地址耗尽) | 不需要(地址充足) |
|
||||
| SLAAC | — | 支持无状态自动配置 |
|
||||
| 组播 | 有限支持 | 原生支持 |
|
||||
| 地址解析 | ARP(广播) | NDP / Neighbor Discovery(组播,替代 ARP) |
|
||||
|
||||
### IPv6 地址表示与压缩
|
||||
|
||||
IPv6 地址共 128 位,用冒号分隔为 8 组,每组 4 个十六进制数:
|
||||
|
||||
```
|
||||
完整写法: 2001:0db8:0000:0000:0000:0000:0000:0001
|
||||
压缩规则 1 — 每组前导零可省略: 2001:db8:0:0:0:0:0:1
|
||||
压缩规则 2 — 连续全零组用 :: 替代(仅一次): 2001:db8::1
|
||||
```
|
||||
|
||||
> [!QUESTION] `::` 能用几次?
|
||||
> 只能用**一次**。如果出现两个 `::`,解析器无法确定每组省略了多少个零,地址就会歧义。
|
||||
|
||||
### IPv6 特殊地址
|
||||
|
||||
| 地址 | 用途 |
|
||||
|------|------|
|
||||
| `::1` | 环回地址,等价于 IPv4 的 `127.0.0.1` |
|
||||
| `::` | 未指定地址,等价于 IPv4 的 `0.0.0.0` |
|
||||
| `fe80::/10` | 链路本地地址(Link-Local),同一链路自动通信 |
|
||||
| `ff00::/8` | 组播地址 |
|
||||
|
||||
> 更多 IPv6 细节见 [[hhs/NETWORK/03-网络层/03-IPv6地址与扩展头部]]。
|
||||
|
||||
### IP 地址分类(历史)
|
||||
|
||||
@@ -104,6 +129,29 @@ Options (可选)...
|
||||
|
||||
> [!warning] CIDR 已淘汰分类编址
|
||||
> 现代网络全部使用 **CIDR(Classless Inter-Domain Routing)** 无类编址,子网掩码可以是任意位数(如 /23、/27)。
|
||||
> CIDR 用"前缀长度"取代了固定的 A/B/C 类划分,例如 `192.168.1.0/24` 表示前 24 位是网络号,剩余 8 位是主机号。
|
||||
> 更多细节见 [[hhs/NETWORK/03-网络层/02-CIDR与子网划分]]。
|
||||
|
||||
### 特殊 IP 地址
|
||||
|
||||
| 地址 | 用途 |
|
||||
|------|------|
|
||||
| `127.0.0.1` / `::1` | 环回地址(Loopback),本机内部通信 |
|
||||
| `0.0.0.0` | 表示"本机所有网卡"(服务端 bind 时常用) |
|
||||
| `169.254.x.x` | 链路本地地址(DHCP 失败时自动分配) |
|
||||
|
||||
### 私有 IP 地址段
|
||||
|
||||
互联网上的路由器**不会转发**来自以下范围的数据包,它们仅用于内网,通过 NAT 转换为公网 IP 后才能访问互联网:
|
||||
|
||||
| 类别 | 地址范围 | CIDR | 可用地址数 |
|
||||
|------|---------|------|-----------|
|
||||
| A 类 | `10.0.0.0` ~ `10.255.255.255` | `10.0.0.0/8` | ~1677 万 |
|
||||
| B 类 | `172.16.0.0` ~ `172.31.255.255` | `172.16.0.0/12` | ~104 万 |
|
||||
| C 类 | `192.168.0.0` ~ `192.168.255.255` | `192.168.0.0/16` | ~6.5 万 |
|
||||
|
||||
> [!tip] 私有地址 + NAT = 解决 IPv4 耗尽
|
||||
> 一个公网 IP 可以通过 NAT 映射背后成百上千个私有 IP,这就是为什么 IPv4 至今仍在大规模使用。详见 [[hhs/NETWORK/03-网络层/08-NAT原理与应用]]。
|
||||
|
||||
## 三、端口号(16 位)
|
||||
|
||||
@@ -134,7 +182,8 @@ flowchart LR
|
||||
|
||||
| 端口 | 协议 | 服务 |
|
||||
|------|------|------|
|
||||
| 21 | FTP | 文件传输控制 |
|
||||
| 20 | FTP-Data | 文件传输数据通道 |
|
||||
| 21 | FTP | 文件传输控制通道 |
|
||||
| 22 | SSH | 安全远程登录 |
|
||||
| 25 | SMTP | 邮件发送 |
|
||||
| 53 | DNS | 域名解析 |
|
||||
@@ -164,6 +213,7 @@ flowchart TD
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[hhs/NETWORK/OSI与TCP-IP模型对比]] — 三层地址对应 OSI 的不同层级
|
||||
- [[hhs/NETWORK/IPv4协议详解]] — IP 地址的详细编码与 CIDR 计算
|
||||
- [[hhs/NETWORK/TCP状态机详解]] — 四元组如何唯一标识 TCP 连接
|
||||
- [[hhs/NETWORK/01-基础概念/01-OSI与TCP-IP模型对比]] — 三层地址对应 OSI 的不同层级
|
||||
- [[hhs/NETWORK/03-网络层/01-IPv4首部与分段重组]] — IP 首部的详细字段与分段机制
|
||||
- [[hhs/NETWORK/03-网络层/02-CIDR与子网划分]] — CIDR 计算与子网划分实战
|
||||
- [[hhs/NETWORK/04-传输层/01-TCP段结构与状态机]] — 四元组如何唯一标识 TCP 连接
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
tags: [计算机网络, 带宽, 延迟, RTT, 吞吐量, BDP]
|
||||
tags: [计算机网络, 带宽, 延迟, RTT, 吞吐量, BDP, Jitter, Goodput]
|
||||
create time: 2026-05-17 22:40
|
||||
---
|
||||
|
||||
@@ -29,26 +29,52 @@ create time: 2026-05-17 22:40
|
||||
> [!tip] bit vs Byte:记住除 8
|
||||
> 运营商说的 "100M 宽带" 是 **100 Mbps**。换算成下载速度约 `100 ÷ 8 ≈ 12.5 MB/s`。
|
||||
|
||||
### 延迟(Latency / Propagation Delay)
|
||||
### 延迟(Latency)
|
||||
|
||||
信号从发送端传播到接收端所需的时间,仅取决于**物理距离和介质光速**:
|
||||
一个数据包从发送到被接收所经历的**总时间**,由四部分组成:
|
||||
|
||||
$$\text{Prop. Delay} = \frac{\text{Distance}}{v}$$
|
||||
$$\text{Latency} = \text{传播延迟} + \text{传输延迟} + \text{排队延迟} + \text{处理延迟}$$
|
||||
|
||||
其中 $v$ 是传播速度。光纤中光速约为 $2 \times 10^8$ m/s(真空中光速的 2/3)。
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["传播延迟<br/>Propagation"] --> B["传输延迟<br/>Transmission"]
|
||||
B --> C["排队延迟<br/>Queuing"]
|
||||
C --> D["处理延迟<br/>Processing"]
|
||||
|
||||
| 场景 | 典型延迟 |
|
||||
style A fill:#DDA0DD,color:#000
|
||||
style B fill:#87CEEB,color:#000
|
||||
style C fill:#FFD700,color:#000
|
||||
style D fill:#98FB98,color:#000
|
||||
```
|
||||
|
||||
| 延迟类型 | 公式 | 取决于 | 典型值 |
|
||||
|---------|------|--------|--------|
|
||||
| **传播延迟** | $\frac{\text{Distance}}{v}$ | 物理距离、介质 | 光纤中 $v \approx 2 \times 10^8$ m/s(真空中光速的 2/3) |
|
||||
| **传输延迟** | $\frac{\text{Packet Size}}{\text{Bandwidth}}$ | 数据包大小、链路带宽 | 1500B 以太网帧在 1Gbps 链路上 ≈ 12 μs |
|
||||
| **排队延迟** | 不确定 | 路由器拥塞程度 | 空闲时 ≈ 0,拥塞时可达数百 ms |
|
||||
| **处理延迟** | 不确定 | 路由器/交换机性能 | 通常 < 1 ms |
|
||||
|
||||
> [!question] 这四类延迟,哪个最"不可控"?
|
||||
> 排队延迟。传播延迟由物理距离决定(选不了机房位置就改不了),传输延迟由包大小和带宽决定,处理延迟通常很小。但排队延迟随网络拥塞程度剧烈波动——这也是为什么它直接关联**抖动(Jitter)** 和 **丢包**。
|
||||
|
||||
| 场景 | 典型单向延迟(主要贡献:传播延迟) |
|
||||
|------|---------|
|
||||
| 同机房内 | 0.1–1 ms |
|
||||
| 同城(同一城市数据中心) | 1–5 ms |
|
||||
| 跨省(国内) | 20–40 ms |
|
||||
| 跨洋(中美) | 80–150 ms |
|
||||
| 卫星轨道(LEO) | 20–40 ms |
|
||||
| TCP 握手 + TLS 1.3 | 1~2 RTT(额外 RTT × 加密计算时间) |
|
||||
|
||||
> [!tip] TCP + TLS 的额外延迟
|
||||
> 建立一个 HTTPS 连接还需要 **TCP 三次握手(1 RTT)+ TLS 1.3 握手(1 RTT)**,在数据发出前就已经"花掉"了 2 个 RTT。这也是 HTTP/2、连接复用和 TLS Session Resumption 存在的重要原因。
|
||||
|
||||
### RTT(Round-Trip Time)
|
||||
|
||||
从发送方发出数据包到收到确认应答的总时间。**RTT = 2 × 单向延迟 + 排队处理时间 + 传输时间**。
|
||||
从发送方发出数据包到收到确认应答的总时间。严格来说:
|
||||
|
||||
$$\text{RTT} = 2 \times (\text{传播延迟} + \text{传输延迟} + \text{排队延迟} + \text{处理延迟})$$
|
||||
|
||||
实际测量中,`ping` 工具测得的 RTT 主要反映传播延迟(往返),而 TCP 的 RTT 估算(用于计算 RTO)还需包含协议栈处理时间。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
@@ -60,6 +86,22 @@ flowchart LR
|
||||
style D fill:#98FB98,color:#000
|
||||
```
|
||||
|
||||
### 抖动(Jitter)
|
||||
|
||||
连续数据包之间延迟的**变化量**。即使平均延迟很低,抖动大也会严重影响体验:
|
||||
|
||||
$$\text{Jitter} = |\text{RTT}_n - \text{RTT}_{n-1}|$$
|
||||
|
||||
| 应用类型 | 对抖动的敏感度 | 说明 |
|
||||
|---------|--------------|------|
|
||||
| 语音通话 / 视频会议 | 🔴 极高 | 抖动 > 30ms 就会出现卡顿、断续 |
|
||||
| 在线游戏 | 🔴 高 | 操作响应不稳定,玩家体验崩塌 |
|
||||
| 文件下载 | 🟢 无所谓 | 只关心总吞吐量,不在乎每个包的到达节奏 |
|
||||
| HTTP API 调用 | 🟡 中等 | 长尾延迟影响 P99 响应时间 |
|
||||
|
||||
> [!question] 如何对抗抖动?
|
||||
> 答案是**接收端缓冲(Jitter Buffer)**。音视频应用会在接收端先攒一小段数据再播放,用少量额外延迟换取平滑输出。WebRTC 的 jitter buffer 通常 20–200ms。
|
||||
|
||||
### 吞吐量(Throughput)
|
||||
|
||||
单位时间内**实际成功传输的数据量**,受限于最窄环节:
|
||||
@@ -86,6 +128,11 @@ flowchart LR
|
||||
> [!question] 为什么我的千兆网卡只跑到 100MB/s?
|
||||
> 因为千兆以太网理论上限是 `1Gbps ÷ 8 = 125MB/s`,去掉以太网帧头、IP 头、TCP 头等协议开销后,纯 Payload 大约 **94 MB/s**。如果还用了 HTTPS/TLS,CPU 加解密还会进一步限制吞吐量。
|
||||
|
||||
> [!info] Goodput vs Throughput:别搞混
|
||||
> **Throughput**(吞吐量)是单位时间内传输的**总数据量**(含协议头)。**Goodput**(有效吞吐量)是应用层实际收到的**有效数据量**,要扣除所有协议头、重传包、确认包等开销。
|
||||
> $$\text{Goodput} < \text{Throughput} \leq \text{Bandwidth}$$
|
||||
> 用 `iperf3` 测出来的通常是 throughput;你的文件下载速度反映的更接近 goodput。
|
||||
|
||||
## 带宽时延积(BDP)
|
||||
|
||||
**BDP(Bandwidth-Delay Product)** 定义了链路上"在途"数据的最大量——这是维持满带宽所需的发送缓冲大小:
|
||||
@@ -138,28 +185,46 @@ flowchart TD
|
||||
|
||||
## Go 中的实践示例
|
||||
|
||||
```go
|
||||
// 设置 TCP 连接保持活跃,定期检测死链接
|
||||
tcp := net.ListenConfig{
|
||||
Control: func(network, address string, c syscall.RawConn) error {
|
||||
return c.Control(func() {
|
||||
// 开启 keepalive,每 30s 探测一次
|
||||
syscall.SetsockoptInt(int(c.Fd()), syscall.SOL_SOCKET,
|
||||
syscall.SO_KEEPALIVE, 1)
|
||||
})
|
||||
},
|
||||
}
|
||||
### 测量 RTT(TCP 连接延迟)
|
||||
|
||||
// HTTP Transport 的连接池配置
|
||||
transport := &http.Transport{
|
||||
MaxIdleConns: 100, // 最多空闲连接数
|
||||
MaxIdleConnsPerHost: 10, // 每个 host 最多 10 个空闲
|
||||
IdleConnTimeout: 90 * time.Second, // 空闲超时释放
|
||||
```go
|
||||
// 测量一次 TCP 连接的 RTT —— 最直接的延迟指标
|
||||
func measureRTT(addr string) (time.Duration, error) {
|
||||
start := time.Now()
|
||||
conn, err := net.DialTimeout("tcp", addr, 5*time.Second)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
conn.Close()
|
||||
return time.Since(start), nil // TCP 握手完成 ≈ 1 RTT
|
||||
}
|
||||
```
|
||||
|
||||
### 按 BDP 调大 TCP 缓冲区
|
||||
|
||||
```go
|
||||
// 高带宽长延迟链路(如跨洋 100Mbps, RTT 150ms)的 BDP = 1.875MB
|
||||
// 系统默认的 TCP 窗口(128KB~256KB)远远不够
|
||||
transport := &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
d := net.Dialer{}
|
||||
conn, err := d.DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tcpConn := conn.(*net.TCPConn)
|
||||
// 根据 BDP 设置读写缓冲区(2MB,略大于 BDP)
|
||||
tcpConn.SetReadBuffer(2 << 20)
|
||||
tcpConn.SetWriteBuffer(2 << 20)
|
||||
return tcpConn, nil
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
> [!tip] 为什么不直接用系统默认窗口?
|
||||
> Linux 默认 `tcp_rmem` 的最大值通常是 6MB,但单连接的初始窗口只有 ~128KB。对于高 BDP 链路,窗口增长(慢启动)到填满管道需要好几个 RTT,前几秒吞吐量会远低于带宽上限。应用层手动设大缓冲区 + 启用窗口缩放(`tcp_window_scaling`)可以加速这个过程。
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[hhs/NETWORK/OSI与TCP-IP模型对比]] — 网络分层中各层的角色
|
||||
- [[hhs/NETWORK/TCP状态机详解]] — TCP 的拥塞控制直接影响吞吐量
|
||||
- [[hhs/NETWORK/TCP内核参数调优]] — BDP 对应内核参数 rmem/wmem
|
||||
- [[hhs/NETWORK/01-基础概念/01-OSI与TCP-IP模型对比]] — 网络分层中各层的角色
|
||||
- [[hhs/NETWORK/09-性能优化/01-TCP内核调优与连接复用]] — BDP 对应内核参数 rmem/wmem
|
||||
|
||||
@@ -7,7 +7,29 @@ create time: 2026-05-17 22:50
|
||||
|
||||
## 概述
|
||||
|
||||
Ethernet(以太网)是目前最主流的局域网技术,由 Xerox、DEC 和 Intel 在 1980 年联合制定(IEEE 802.3)。理解 Ethernet 帧结构是掌握网络底层通信的基础——每一帧都包含了足够的信息来让网卡判断"这是给我的吗?内容完整吗?上层协议是什么?"
|
||||
Ethernet(以太网)是目前最主流的局域网技术。最初由 Robert Metcalfe 在 Xerox PARC 于 1973 年发明,1980 年由 Xerox、DEC、Intel 联合发布 DIX 标准,1983 年 IEEE 正式发布 802.3 标准。
|
||||
|
||||
理解 Ethernet 帧结构是掌握网络底层通信的基础——每一帧都包含了足够的信息来让网卡判断"这是给我的吗?内容完整吗?上层协议是什么?"
|
||||
|
||||
## 物理层封装:前导码与帧间隔
|
||||
|
||||
在以太网帧到达网线之前,物理层(PHY)会自动加上 **前导码 (Preamble)** 和 **帧首定界符 (SFD)**,这部分对软件不可见,但理解它有助于理解"帧"与"包"的边界。
|
||||
|
||||
```
|
||||
|<---------- Preamble + SFD (8B) -------->|<------------- Ethernet Frame ------------>|
|
||||
┌───────────────────────┬────────┬─────────┬──────────┬──────────┬──────────┬─────────┐
|
||||
│ Preamble (7 bytes) │ SFD(1B)│Dest MAC │ Src MAC │ EtherType│ Payload │ FCS │
|
||||
│ 10101010 × 7 │10101011│ 6 bytes │ 6 bytes │ 2 bytes │ 46-1500B │ 4 bytes │
|
||||
└───────────────────────┴────────┴─────────┴──────────┴──────────┴──────────┴─────────┘
|
||||
↑ 14B Header
|
||||
```
|
||||
|
||||
- **Preamble**: 7 字节的 `10101010` 交替模式,用于接收方的**时钟同步**(位同步)
|
||||
- **SFD** (Start Frame Delimiter): `10101011`,最后两个连续 1 表示"帧数据从下一个字节开始"
|
||||
- **IFG** (Inter-Frame Gap): 帧与帧之间至少间隔 **12 字节**(96 bit time),让网卡有时间处理上一帧
|
||||
|
||||
> [!question] 为什么前导码是 7 字节而不是 8 字节?
|
||||
> 前 7 字节用于时钟同步,SFD 单独标记帧起始。接收方需要足够的跳变沿来锁定频率,7 字节 × 8 bit = 56 次跳变,在早期的 10Mbps 以太网中大约需要 5.6μs 完成同步。
|
||||
|
||||
## Ethernet II 帧格式(最常见)
|
||||
|
||||
@@ -39,34 +61,60 @@ Ethernet(以太网)是目前最主流的局域网技术,由 Xerox、DEC
|
||||
|
||||
现代 Gigabit+ 网卡已不用 CSMA/CD,但为了兼容仍保留此限制。
|
||||
|
||||
## IEEE 802.3 / 802.3Q(VLAN Tagged)帧
|
||||
## IEEE 802.1Q(VLAN Tagged)帧
|
||||
|
||||
当网络中使用 VLAN 时,帧中会插入一个 4 字节的 802.1Q Tag:
|
||||
当网络中使用 VLAN 时,帧中会**插入**一个 4 字节的 802.1Q Tag。注意:TPID 占据了原本 EtherType 的位置,真正的 EtherType 跟在 TCI 之后:
|
||||
|
||||
```
|
||||
┌──────────┬──────────┬──────┬──────┬──────────┬─────────────┬──────────┐
|
||||
│ Dest MAC │ Src MAC │ Type │ TPID │ TCI/VID │ Payload │ FCS │
|
||||
│ 6 bytes │ 6 bytes │ 2B │2B │ 2B │ 46–1500 B │ 4 bytes │
|
||||
└──────────┴──────────┴──────┴──────┴──────────┴─────────────┴──────────┘
|
||||
┌──────────┬──────────┬─────────────┬──────────┬──────────┬─────────────┬──────────┐
|
||||
│ Dest MAC │ Src MAC │ TPID (0x8100)│ TCI │ EtherType│ Payload │ FCS │
|
||||
│ 6 bytes │ 6 bytes │ 2 bytes │ 2 bytes │ 2 bytes │ 42–1500 B │ 4 bytes │
|
||||
│ │ │ │ PCP DEI │ 0x0800 │ │ │
|
||||
│ │ │ │ 3b 1b │ │ │ │
|
||||
│ │ │ │ VID(12b) │ │ │ │
|
||||
└──────────┴──────────┴─────────────┴──────────┴──────────┴─────────────┴──────────┘
|
||||
←──────── 4B 802.1Q Tag ────────→
|
||||
```
|
||||
|
||||
新增字段:
|
||||
- **TPID** (Tag Protocol Identifier): `0x8100` — 标识这是带 VLAN Tag 的帧
|
||||
- **TCI** (Tag Control Information): 包含 PCP(优先级,3 bit)、CFI(规范格式指示符,1 bit)、**VID**(VLAN ID,12 bit)
|
||||
- **TPID** (Tag Protocol Identifier): `0x8100` — 占据 EtherType 的位置,标识"这不是普通协议类型,而是 VLAN Tag"
|
||||
- **TCI** (Tag Control Information): 包含 PCP(优先级,3 bit)、DEI(丢弃适格指示符,1 bit)、**VID**(VLAN ID,12 bit)
|
||||
|
||||
VID 取值范围 1–4094,共 4094 个可用 VLAN。
|
||||
> [!note] CFI → DEI
|
||||
> 早期标准中该位叫 CFI (Canonical Format Indicator),用于标记 MAC 地址是否为规范格式。802.1Q-2011 修订后改为 DEI (Drop Eligible Indicator),用于标识在网络拥塞时可优先丢弃的帧。
|
||||
|
||||
VID 取值范围 0–4095,其中 **VID 0** 用于表示优先级帧(不含 VLAN 信息),**VID 4095** 为保留值,实际可用 VLAN 为 1–4094(共 4094 个)。
|
||||
|
||||
> [!question] 插入 4 字节 Tag 后帧会不会超过最大长度?
|
||||
> 会。带 Tag 的帧最大长度从 1518 字节扩展到 **1522 字节**。这也是为什么交换机端口通常区分"最大帧长度"配置。
|
||||
|
||||
## 常见 EtherType 对照表
|
||||
|
||||
| EtherType | 十六进制 | 协议 |
|
||||
|-----------|---------|------|
|
||||
| 协议 | EtherType | 说明 |
|
||||
|------|-----------|------|
|
||||
| IPv4 | `0x0800` | IP 数据包 |
|
||||
| IPv6 | `0x86DD` | IPv6 数据包 |
|
||||
| ARP | `0x0806` | 地址解析协议 |
|
||||
| RARP | `0x8035` | 反向 ARP(历史遗留) |
|
||||
| PPPoE | `0x8864` | PPP over Ethernet |
|
||||
| PPPoE Discovery | `0x8863` | PPPoE 发现阶段 |
|
||||
| PPPoE Session | `0x8864` | PPPoE 会话阶段 |
|
||||
| 802.1Q | `0x8100` | VLAN 标记 |
|
||||
| LACP | `0x8809` | 链路聚合控制协议 |
|
||||
| LLDP | `0x88CC` | 链路层发现协议 |
|
||||
|
||||
### EtherType vs Length:如何区分?
|
||||
|
||||
这是以太网中一个经典的历史遗留问题。Ethernet II 和 IEEE 802.3 原始标准在同一个 2 字节位置上使用了**不同的语义**:
|
||||
|
||||
| 值范围 | 含义 | 对应标准 |
|
||||
|--------|------|----------|
|
||||
| ≥ `0x0600` (1536) | **EtherType** — 上层协议标识 | Ethernet II |
|
||||
| ≤ `0x05DC` (1500) | **Length** — Payload 字节数 | IEEE 802.3 LLC/SNAP |
|
||||
|
||||
由于合法的 Payload 长度最大为 1500 字节,而合法的 EtherType 最小为 1536,两者不会重叠。**现代网络中绝大多数帧都是 Ethernet II 格式**,802.3 LLC/SNAP 仅在一些特殊场景(如 STP、某些工业协议)中出现。
|
||||
|
||||
> [!question] 如果你用 Wireshark 抓到一个该字段值为 0x05EE 的帧,它是什么?
|
||||
> `0x05EE` = 1518,落在 1501–1535 之间——这是一个非法值,正常的以太网实现不会产生这样的帧。
|
||||
|
||||
## MAC 地址空间与 OUI
|
||||
|
||||
@@ -83,22 +131,69 @@ link/ether aa:bb:cc:dd:ee:ff brd ff:ff:ff:ff:ff:ff
|
||||
# Linux 也可以用 ethtool -P eth0 查看出厂 MAC
|
||||
```
|
||||
|
||||
### 单播/多播位(LSB of First Byte)
|
||||
### 第一个字节的两个重要比特位
|
||||
|
||||
第一个字节的最低比特位决定寻址类型:
|
||||
MAC 地址的第一个字节中,**最低两位**各自承载独立含义(注意:在网络传输中 MAC 地址按 **LSB first** 顺序发送,但日常书写采用 MSB first):
|
||||
|
||||
| LSB | 类型 | 示例 |
|
||||
|-----|------|------|
|
||||
| 0 | 单播 (Unicast) | `aa:bb:cc:dd:ee:ff` |
|
||||
| 1 | 多播 (Multicast) | `01:00:5e:xx:xx:xx`(IPv4 组播) |
|
||||
| 1 | 广播 (Broadcast) | `ff:ff:ff:ff:ff:ff` |
|
||||
```
|
||||
第一字节: b7 b6 b5 b4 b3 b2 b1 b0
|
||||
↑ I/G 位 (Individual/Group)
|
||||
↑ U/L 位 (Universal/Local)
|
||||
```
|
||||
|
||||
#### I/G bit(bit 0)— 单播/组播
|
||||
|
||||
| 值 | 类型 | 说明 | 示例 |
|
||||
|----|------|------|------|
|
||||
| 0 | 单播 (Unicast) | 发给单一设备 | `aa:bb:cc:dd:ee:ff` |
|
||||
| 1 | 组播 (Multicast) | 发给一组设备 | `01:00:5e:xx:xx:xx`(IPv4 组播) |
|
||||
|
||||
广播地址 `ff:ff:ff:ff:ff:ff` 是组播的特例(所有 bit 为 1)。
|
||||
|
||||
#### U/L bit(bit 1)— 全局/本地管理
|
||||
|
||||
| 值 | 类型 | 说明 |
|
||||
|----|------|------|
|
||||
| 0 | 全局管理 (GUA) | 由 IEEE 分配 OUI,厂商保证唯一(网卡出厂 MAC) |
|
||||
| 1 | 本地管理 (LAA) | 管理员手动设置或软件生成(如 Docker 容器 MAC、虚拟网卡) |
|
||||
|
||||
> [!tip] 如何快速判断?看 MAC 第一个字节的十六进制最后一位
|
||||
> - `a` → 1010 → LSB=0 → 单播
|
||||
> - `3` → 0011 → LSB=1 → 多播/广播
|
||||
> - `a` → 101**0** → I/G=0, U/L=0 → 单播 + 全局管理
|
||||
> - `3` → 001**1** → I/G=1, U/L=1 → 组播 + 本地管理
|
||||
> - `2` → 001**0** → I/G=0, U/L=1 → 单播 + 本地管理(如 Docker 生成的 MAC)
|
||||
|
||||
### MAC 地址的比特序
|
||||
|
||||
MAC 地址在网线上传输时采用 **LSB first**(小端序),这与我们日常书写 `aa:bb:cc:dd:ee:ff` 的 MSB first 习惯相反。这也解释了为什么 IPv4 组播 MAC 的前缀是 `01:00:5e` 而不是 `01:00:5f`——因为 `01` 的实际传输顺序是 `10000000`,I/G 位确实是第 1 个被发出的比特。
|
||||
|
||||
## MTU、MSS 与 Jumbo Frame
|
||||
|
||||
### 关键概念区分
|
||||
|
||||
| 概念 | 层级 | 标准值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| **MTU** (Maximum Transmission Unit) | 链路层 | 1500 bytes | 帧中 Payload 的最大长度(不含 Header 和 FCS) |
|
||||
| **MSS** (Maximum Segment Size) | 传输层 (TCP) | 1460 bytes | TCP 数据段的最大载荷,= MTU - 20(IP) - 20(TCP) |
|
||||
| **Max Frame** | 链路层 | 1518 bytes | 整个以太网帧(含 Header 14B + FCS 4B) |
|
||||
|
||||
### Jumbo Frame(巨型帧)
|
||||
|
||||
部分交换机和网卡支持 **Jumbo Frame**,将 MTU 提升到 **9000 字节**(甚至更大):
|
||||
|
||||
| 帧类型 | MTU | 最大帧长 | 适用场景 |
|
||||
|--------|-----|----------|----------|
|
||||
| 标准以太网 | 1500 B | 1518 B | 通用网络 |
|
||||
| Jumbo Frame | 9000 B | 9018 B | 数据中心、iSCSI、NFS 存储网络 |
|
||||
|
||||
> [!warning] Jumbo Frame 的使用陷阱
|
||||
> 1. **全链路必须一致**:路径上任何一台设备不支持 Jumbo Frame,就会导致分片或丢包
|
||||
> 2. **非标准协议**:IEEE 从未正式标准化 Jumbo Frame(>1500 的 MTU),各家厂商实现略有差异
|
||||
> 3. **实际收益**:减少帧头开销比例,CPU 中断次数降低,在大数据传输场景下吞吐量提升 10%–30%
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[hhs/NETWORK/OSI与TCP-IP模型对比]] — 以太网属于链路层
|
||||
- [[hhs/NETWORK/MAC地址与广播域]] — MAC 地址的工作范围
|
||||
- [[hhs/NETWORK/Switch与路由器]] — Switch 如何使用 MAC 表转发帧
|
||||
- [[hhs/NETWORK/01-基础概念/01-OSI与TCP-IP模型对比]] — 以太网属于链路层
|
||||
- [[hhs/NETWORK/02-链路层/03-MAC地址与广播域]] — MAC 地址的工作范围
|
||||
- [[hhs/NETWORK/02-链路层/04-Switch与路由器]] — Switch 如何使用 MAC 表转发帧
|
||||
- [[hhs/NETWORK/02-链路层/02-CSMA-CD与以太网退避]] — CSMA/CD 碰撞检测机制
|
||||
- [[hhs/NETWORK/02-链路层/05-VLAN与Trunk]] — 802.1Q VLAN 的实际部署
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
tags: [计算机网络, CSMA-CD, 以太网退避, 冲突检测]
|
||||
tags: [计算机网络, CSMA-CD, 以太网退避, 冲突检测, 碰撞域, 全双工, 半双工]
|
||||
create time: 2026-05-17 23:00
|
||||
---
|
||||
|
||||
@@ -40,19 +40,39 @@ flowchart TD
|
||||
| 3 | **Collision Detection** | 发送过程中持续监测是否有冲突信号 |
|
||||
| 4 | **Collision Handling** | 检测到冲突 → 发 Jam Signal → 执行退避 → 重试 |
|
||||
|
||||
## Slot Time(时隙)
|
||||
|
||||
在讲解退避算法之前,先认识一个核心概念——**Slot Time(时隙/争用期)**:
|
||||
|
||||
$$\text{Slot Time} = 512 \text{ bit-time} = 51.2 \mu s \quad (\text{在 10Mbps 以太网下})$$
|
||||
|
||||
时隙是 CSMA/CD 中所有时间计算的基本单位,它的值约等于**最大往返传播延迟(~46.4μs)+ Jam Signal 时间(~3.2μs)+ 安全余量**。退避算法中的等待时间、最小帧长、冲突检测窗口,全部以它为基准。
|
||||
|
||||
> [!QUESTION] 为什么 Slot Time 要比理论往返延迟长一点?
|
||||
> 因为检测到冲突后还需要发送 Jam Signal 确保所有节点都感知到碰撞。如果时隙刚好等于往返延迟,发送方在时隙结束时还没完成 Jam Signal 的发送,就可能误以为"没冲突"。
|
||||
|
||||
## Jam Signal(强化冲突信号)
|
||||
|
||||
检测到冲突后,发送方会立刻发出一个 **32 bit 的 Jam Signal**,目的有二:
|
||||
|
||||
1. **强化冲突**:确保网络上所有节点(包括距冲突点较远的节点)都能检测到碰撞
|
||||
2. **延长冲突持续时间**:使冲突信号维持足够长的时间,避免有节点"刚好错过"碰撞窗口
|
||||
|
||||
Jam Signal 的传输时间约 3.2μs(10Mbps),已包含在 Slot Time 的计算中。
|
||||
|
||||
## 二进制指数退避算法
|
||||
|
||||
冲突后的等待时间通过随机退避来避免再次冲突:
|
||||
冲突后的等待时间通过**随机退避**来避免再次碰撞——选到相同随机数的概率随窗口增大而降低:
|
||||
|
||||
$$\text{等待时间} = \min(r, 2^{10}-1) \times 512 \text{ bit-time}$$
|
||||
$$\text{backoff} = r \times \text{Slot Time},\quad r \in \{0, 1, \ldots, \min(2^k - 1, 1023)\}$$
|
||||
|
||||
其中 $r$ 为重传次数,`512 bit-time` 是最小帧长对应的传输时间(即争用期)。
|
||||
其中 $k$ 为该帧的重传次数。每次冲突后窗口翻倍(指数增长),上限固定在 1023。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
k["重传次数 k"] --> r["r = random(0 ~ 2^k - 1)"]
|
||||
r --> capped["capped at 2^10 - 1 = 1023 (k > 10)"]
|
||||
capped --> backoff["backoff_time = r × 512 bit-time"]
|
||||
k["重传次数 k"] --> window["窗口大小 min(2^k - 1, 1023)"]
|
||||
window --> r["r = random(0 ~ 窗口大小)"]
|
||||
r --> backoff["backoff = r × 51.2μs"]
|
||||
|
||||
style k fill:#DDA0DD,color:#000
|
||||
style backoff fill:#98FB98,color:#000
|
||||
@@ -60,69 +80,140 @@ flowchart TD
|
||||
|
||||
### 退避表
|
||||
|
||||
| 重传次数 k | 窗口大小 2^k - 1 | 可能等待的 slot 数范围 |
|
||||
|-----------|------------------|---------------------|
|
||||
| 1 | 1 | {0, 1} |
|
||||
| 2 | 3 | {0, 1, 2, 3} |
|
||||
| 3 | 7 | {0..7} |
|
||||
| 4 | 15 | {0..15} |
|
||||
| 5–10 | 最大值 1023 | {0..1023} |
|
||||
| >10 | 仍为 1023 | 上限固定 |
|
||||
| 重传次数 k | 窗口大小 min(2^k - 1, 1023) | r 的取值范围 | 最大等待时间 |
|
||||
|-----------|---------------------------|-------------|------------|
|
||||
| 1 | 1 | {0, 1} | 51.2μs |
|
||||
| 2 | 3 | {0..3} | 153.6μs |
|
||||
| 3 | 7 | {0..7} | 358.4μs |
|
||||
| 4 | 15 | {0..15} | 768μs |
|
||||
| 5–10 | 2^k - 1 → 1023 | {0..1023} | 52.4ms |
|
||||
| >10 | 1023(上限固定) | {0..1023} | 52.4ms |
|
||||
|
||||
如果重传 16 次仍然失败,帧被丢弃并向上层报告错误。
|
||||
> [!tip] 为什么窗口要"封顶"在 1023?
|
||||
> 如果不封顶,k=16 时窗口可达 65535,等待时间超过 3 秒——太久了。封顶保证了最坏情况下的重传延迟有上界,同时也意味着高冲突率下不同节点的退避选择可能重复,这就是网络严重拥塞时的"公平性代价"。
|
||||
|
||||
如果重传 **16 次**仍然失败,帧被丢弃并向上层报告错误。
|
||||
|
||||
### 示例
|
||||
|
||||
假设节点 A 和 B 同时发送,发生冲突:
|
||||
|
||||
```
|
||||
时刻 0ms: A 和 B 同时发送 → 冲突!
|
||||
两者都发送 32bit Jam Signal
|
||||
时刻 0.1ms: A 选 r=3 → 等 3×512bit-time = 3×51.2μs ≈ 154μs
|
||||
B 选 r=1 → 等 1×51.2μs ≈ 51μs
|
||||
时刻 0.05ms: B 先等完,先尝试发送 → A 还在等
|
||||
时刻 0.15ms: A 等完,发现信道已被 B 占用 → 退避重选 r
|
||||
...
|
||||
时刻 0μs: A 和 B 同时开始发送
|
||||
时刻 23.2μs: A 检测到冲突(假设距冲突点约 23.2μs)
|
||||
A 和 B 均发送 Jam Signal (32bit, 约 3.2μs)
|
||||
此时 Slot Time 计时仍在继续...
|
||||
时刻 51.2μs: Slot Time 结束,进入退避期
|
||||
A 选 r=3 → 等 3 × 51.2μs = 153.6μs
|
||||
B 选 r=1 → 等 1 × 51.2μs = 51.2μs
|
||||
时刻 102.4μs: B 先等完 → 检测信道空闲 → 发送 ✅
|
||||
时刻 204.8μs: A 等完 → 检测信道忙碌 → 等待...
|
||||
(A 的本次重传计数 k=2,下次窗口扩大为 {0..3})
|
||||
```
|
||||
|
||||
> [!QUESTION] 第二次冲突时,A 和 B 还是同时选到相同 r 怎么办?
|
||||
> 第二次冲突时窗口翻倍到 {0..3},选到相同值的概率从 1/2 降到 1/4。指数退避的核心思想就是:**冲突越频繁,等待范围越大,再次碰撞的概率越低**。当然,极端情况下仍可能连续冲突——这就是为什么 16 次后要放弃。
|
||||
|
||||
## 最小帧长与争用期
|
||||
|
||||
### 为什么最小帧是 64 字节?
|
||||
|
||||
关键公式:**帧传输时间 ≥ 2 × 端到端传播延迟**
|
||||
核心原则:**帧传输时间 ≥ 往返传播延迟**
|
||||
|
||||
如果帧太短,可能在冲突信号到达之前就已经发完了——发送方根本不知道发生过冲突。
|
||||
如果帧太短,发送方可能在冲突信号返回之前就已经发完了——根本不知道发生过冲突,也就无法重传。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant A as 节点 A
|
||||
participant H as 最远节点 H
|
||||
Note over A,H: 传播延迟 = τ
|
||||
participant A as "节点 A"
|
||||
participant H as "最远节点 H"
|
||||
Note over A,H: "传播延迟 τ"
|
||||
|
||||
A->>H: 开始发送一个超短帧 (长度 < 2τ)
|
||||
H->>A: 冲突信号沿原路返回
|
||||
Note over A: 此时 A 已经发完了...<br/>没检测到冲突!🔥
|
||||
A->>H: "发送一个超短帧 (长度 < 2τ)"
|
||||
H->>A: "冲突信号返回"
|
||||
Note over A: "此时 A 已经发完了...<br/>没检测到冲突!"
|
||||
```
|
||||
|
||||
所以规定:
|
||||
所以必须满足:
|
||||
|
||||
$$\text{最小帧长} \geq 2 \times \tau \times \text{带宽}$$
|
||||
$$\text{最小帧长} \geq 2 \times \tau_{\max} \times \text{带宽}$$
|
||||
|
||||
对于经典 10Mbps 以太网,最长电缆段 2500m,$\tau \approx 12.5\mu s$:
|
||||
其中 $\tau_{\max}$ 是网络中最远两个节点之间的**单程传播延迟**。
|
||||
|
||||
$$\text{最小帧长} \geq 2 \times 12.5\mu s \times 10^7 \text{ bps} = 250,000 \text{ bits?}$$
|
||||
### 从理论推导到工程标准
|
||||
|
||||
等等,实际工程中使用了中继器和更短的网段组合,最终标准定为 **512 bit-time = 64 bytes**。
|
||||
对于经典 10Mbps 以太网(10BASE5 粗缆 + 4 个中继器,总跨度约 2500m):
|
||||
|
||||
## 现代以太网中的命运
|
||||
$$\tau_{\max} \approx 46.4\mu s \quad \text{(含中继器延迟)}$$
|
||||
|
||||
| 年代 | 拓扑 | 介质 | CSMA/CD? |
|
||||
|------|------|------|---------|
|
||||
| 1980s | 总线型 | 同轴电缆 | ✅ 必需 |
|
||||
| 1990s | 星型 + Hub | 双绞线 | ✅ 存在但极少冲突 |
|
||||
| 2000s+ | 全双工 Switch | 双绞线/光纤 | ❌ 已禁用 |
|
||||
代入公式:
|
||||
|
||||
全双工模式下,收发通道分离(两根线对),不存在碰撞的可能,CSMA/CD 自动关闭。从 Gigabit Ethernet (802.3ab) 起,交换机端口默认全双工运行。
|
||||
$$\text{最小帧长} \geq 46.4\mu s \times 10^7 \text{ bps} \approx 464 \text{ bits}$$
|
||||
|
||||
IEEE 在此基础上统一向上取整,取了一个**干净的 2 的幂次方**——512 bits(64 bytes),这也是 Slot Time 的值。多出的 48 bits 作为安全余量,同时覆盖了 Jam Signal 的传输时间。
|
||||
|
||||
> [!tip] 帧长与 Slot Time 的关系
|
||||
> 最小帧长 = Slot Time × 带宽 = 512 bit-time × 1 bps = **512 bits = 64 bytes**
|
||||
>
|
||||
> 这不是巧合——最小帧长的设计保证了:只要帧还在传输中(长度 ≥ Slot Time),冲突就一定能被检测到。退避算法以 Slot Time 为单位,最小帧长以 Slot Time 为下限,二者在设计上是统一的。
|
||||
|
||||
### 碰撞域(Collision Domain)
|
||||
|
||||
理解 CSMA/CD 就不得不理解**碰撞域**——所有共享同一冲突检测范围的节点组成一个碰撞域:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph CD1["碰撞域 1 (Hub 连接)"]
|
||||
A["节点 A"] --- Hub["Hub 集线器"]
|
||||
B["节点 B"] --- Hub
|
||||
C["节点 C"] --- Hub
|
||||
end
|
||||
Switch["交换机"] --- Hub
|
||||
Switch --- D["节点 D"]
|
||||
Switch --- E["节点 E"]
|
||||
subgraph CD2["碰撞域 2"]
|
||||
D
|
||||
E
|
||||
end
|
||||
|
||||
style CD1 fill:#FFE4E1,stroke:#FF6B6B,stroke-width:2px
|
||||
style CD2 fill:#E1F5FE,stroke:#4FC3F7,stroke-width:2px
|
||||
```
|
||||
|
||||
- **Hub**:不隔离碰撞域——所有连接到同一 Hub 的设备属于**同一个**碰撞域
|
||||
- **Switch**:每个端口是一个独立碰撞域,从根本上消除了 CSMA/CD 的需求
|
||||
- **广播域**则不同,由 VLAN 划分,和 CSMA/CD 无关(详见 [[05-VLAN与Trunk]])
|
||||
|
||||
## 半双工 vs 全双工
|
||||
|
||||
CSMA/CD 的"监听-发送-检测-退避"机制本质上是**半双工**的——同一时间只能收或发,不能同时进行。全双工模式通过物理上分离收发通道彻底绕开了这个问题:
|
||||
|
||||
| 模式 | 通道 | 冲突 | CSMA/CD | 典型场景 |
|
||||
|------|------|------|---------|---------|
|
||||
| 半双工 (Hub) | 共享 | 存在 | 必需 | 早期总线/Hub 网络 |
|
||||
| 全双工 (Switch) | 收发分离 | 不存在 | 自动关闭 | 现代交换网络 |
|
||||
|
||||
从 **Gigabit Ethernet (802.3ab)** 起,交换机端口默认全双工运行。虽然标准中仍保留了半双工 CSMA/CD 定义(用于兼容),但实际网络中已几乎不再使用。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph HalfDuplex["半双工 (Hub)"]
|
||||
direction LR
|
||||
H1["节点"] -- "发送" --> H2["Hub"]
|
||||
H2 -- "接收" --> H1
|
||||
end
|
||||
subgraph FullDuplex["全双工 (Switch)"]
|
||||
direction LR
|
||||
S1["节点"] -- "发送" --> S2["Switch"]
|
||||
S2 -- "接收" --> S1
|
||||
end
|
||||
|
||||
style HalfDuplex fill:#FFF3E0,stroke:#FF9800
|
||||
style FullDuplex fill:#E8F5E9,stroke:#4CAF50
|
||||
```
|
||||
|
||||
> [!note] 半双工 vs 全双工的本质区别
|
||||
> - **半双工**:收发共用一条通道,同一时刻只能单向通信,需要 CSMA/CD 协调
|
||||
> - **全双工**:收发各有独立通道(物理上两对线),可同时双向通信,CSMA/CD 自动关闭
|
||||
|
||||
> [!note] 如何检查当前网卡是否启用了 CSMA/CD?
|
||||
> ```bash
|
||||
@@ -130,9 +221,11 @@ $$\text{最小帧长} \geq 2 \times 12.5\mu s \times 10^7 \text{ bps} = 250,000
|
||||
> Supports Collision Detection: yes
|
||||
> Current Collision Detection: disabled (full-duplex)
|
||||
> ```
|
||||
> 现代网卡通常显示 "disabled (full-duplex)",说明 CSMA/CD 已关闭。
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[hhs/NETWORK/Ethernet帧结构]] — 以太网 II 帧的格式定义
|
||||
- [[hhs/NETWORK/Switch与路由器]] — Switch 如何通过全双工消除冲突
|
||||
- [[hhs/NETWORK/VLAN与Trunk]] — VLAN 隔离冲突域
|
||||
- [[01-Ethernet帧结构]] — 以太网 II 帧的格式定义,64 字节最小帧长的来源
|
||||
- [[04-Switch与路由器]] — Switch 如何通过全双工消除冲突
|
||||
- [[05-VLAN与Trunk]] — VLAN 如何隔离广播域
|
||||
- [[03-MAC地址与广播域]] — 广播域与碰撞域的区别
|
||||
|
||||
Reference in New Issue
Block a user