以太网帧 = 14 字节头 + 46~1500 载荷 + 4 字节 FCS,最短 64、最长 1518。 「帧长」和「线上占多久」是两个数:算不算前导码与帧间隙,差 20 字节。
3.3.3 讲的是「这套东西怎么演化来的」,这一节把帧本身拆开。 这是本章最该背下来的东西,因为后面所有「为什么」都从这里来。
一、逐字段数一遍
五个部分,各司其职:
| 部分 | 字节 | 内容 |
|---|---|---|
| 前导码 | 7 | 10101010 循环,让接收方对齐时钟 |
| SFD | 1 | 10101011,标记帧真正开始的最后一个字节 |
| 目的 + 源 MAC | 12 | 6 + 6,谁收、谁发 |
| 类型/长度 | 2 | 0x0800 是 IPv4、0x0806 是 ARP |
| 载荷 | 46~1500 | 上层交下来的数据 |
| FCS | 4 | CRC-32 校验,坏了整帧丢 |
前 8 个字节(前导码 + SFD)是网卡硬件加的:软件看不到它们,也构造不出来。 它们的作用只有一个:让接收方的时钟「锁上」发送方的节奏。 所以「帧长」通常不算这 8 个字节。 第四个字段「类型/长度」是个二选一的字段: 值大于 0x0600 时当「类型」用(表示上层是什么协议), 小于等于 0x0600 时当「长度」用(老式 802.3 的用法)。 这种「一个字段两种含义」是老设计的痕迹,今天几乎全按「类型」解释。
二、为什么是 46 和 1500
两个边界,两个完全不同的理由:
| 边界 | 数值 | 理由 |
|---|---|---|
| 最小载荷 | 46 | 14 + 46 + 4 = 64,够碰撞检测 |
| 最大载荷 | 1500 | 一个站不能占线太久 |
第一行那个 46 是「倒推」出来的:最小帧必须是 64,头 14、尾 4,所以载荷最少 46。 不够就填充(padding)——所以「发 1 个字节」在线上其实是 64 字节的帧。 第二行那个 1500 是「公平」考虑:10 Mbps 下 1518 字节要占线 1.21 毫秒, 再长,语音这类实时业务就没法插进来。 注意这个数和「内存页」无关,虽然 1500 字节接近很多系统的缓冲区大小,但那是巧合。 1500 这个数后来变成了「MTU」的默认值: IP 层看到「链路 MTU 1500」,就知道「一个包最多 1500 字节」,超过就要分片。 所以「改 MTU」其实是在改「链路层允许的最大载荷」。
三、帧长与线路时间
两个口径,差 20 字节:
| 口径 | 最小帧 | 最大帧 | 说明 |
|---|---|---|---|
| 帧长 | 64 | 1518 | 不含前导码、不含 IFG |
| 线上占用 | 72 | 1526 | 加 8 字节前导码 |
| 再加帧间隙 | 84 | 1538 | 再加 12 字节空闲 |
第一行是软件眼里的「帧」:抓包工具显示的就是这个长度。 第二行是「物理层实际发出去的比特数」:多了前导码那 8 个字节。 第三行那个「帧间隙(IFG)12 字节」不是数据,是空闲: 两个帧之间必须留一段静默,让接收方能重新同步。 所以「连续满速发送」其实永远做不到 100%—— 每 1526 字节就要空 12 字节,效率上限是 1526/1538 等于 99.2%。 这也解释了为什么「1518」和「1526」两个数在规格里都常见: 交换机说「支持 1526 字节帧」,意思是「算上了前导码」; 网卡说「1518」,是「只算帧本身」。 两个都对,只是口径不同。
四、动手 Demo:手工拼一个以太网帧,数清每个字节
用 struct 真拼一个帧,再逐字段读回来。
然后看「帧长」「线上占用」「加帧间隙」这三个数怎么差出 20 字节。
import struct
def mac(s):
h = s.replace('-', '')
return bytes(int(h[i:i + 2], 16) for i in range(0, 12, 2))
dst, src, pay = '9C-B6-D0-0D-D5-0F', '28-F1-0E-18-FA-25', b'HELLO-LINK'
fr = mac(dst) + mac(src) + struct.pack('!H', 0x0800) + pay
print('帧长 %d 字节' % len(fr))
print('目的 MAC', fr[0:6].hex())
print('源 MAC', fr[6:12].hex())
print('类型 0x%04X' % struct.unpack('!H', fr[12:14])[0])
print('载荷 %r' % fr[14:])
print('上线路 %d 字节(加 8 字节前导码)' % (len(fr) + 8))
print('加 IFG %d 字节(再加 12 字节空闲)' % (len(fr) + 20))
实跑输出:
--------------------------------------------------------------------
手工拼一个以太网帧,数清每个字节
--------------------------------------------------------------------
**帧格式是这一章最该背下来的东西**,因为它解释了后面所有「为什么」。
下面用 `struct` 真拼一个,再逐字段读回来。
**代码**:`N.eth(dst, src, payload)` 拼出来的字节串长 24 字节。
字段 字节数 位置 本 Demo 的值
------------------------------------------------
目的 MAC 6 0~5 9cb6d00dd50f
源 MAC 6 6~11 28f10e18fa25
类型 2 12~13 0x0800
载荷 10 14~ HELLO-LINK
**「类型」那一行是 0x0800,意思是「后面装的是 IPv4」**:
**0x0806 是 ARP、0x86DD 是 IPv6、0x8100 是带 VLAN 标签。**
**这个字段相当于「交接单」——告诉接收方该把载荷交给哪一层的哪个模块。**
**但线上跑的不止这 24 字节,还有三段东西没算**:
部分 字节 内容 作用
--------------------------------------------------------------------
前导码 7 10101010 循环 让接收方对齐时钟
SFD 1 10101011 **标记「帧真正开始了」**
帧头 + 载荷 24 — 上面这张表
FCS 4 CRC-32 **校验,坏了整帧丢**
帧间隙 IFG 12 空闲 **给下一帧留出发送间隔**
**这五段加起来 7 + 1 + 24 + 4 + 12 = 36 字节。**
**但「帧长」只算中间那段 24 字节**:**前导码与 FCS 是网卡硬件加的,写程序的人看不到**;
**IFG 是线路空闲,根本不是字节。**
**所以「这个帧多长」和「这个帧在线上占多久」是两个不同的问题**——**前者算 24,后者要算 36。**
**换成两头极端的例子再看一遍**:**最小合法帧是 64(14 + 46 + 4),上线路加前导码是 72;**
**满载荷帧是 1518(14 + 1500 + 4),上线路加前导码是 1526。**
**设备规格里 1518 和 1526 这两个数都很常见,区别就在算不算前导码。**
**这个 Demo 的载荷只有 10 字节,所以它不是一个合法的以太网帧**:
**最小载荷是 46 字节**(这样 14 + 46 + 4 正好 64),**不够就要填充(padding)**。
**所以「发 1 个字节的数据」在线上其实是 64 字节的帧**,再加上 8 字节前导码与 12 字节线路空闲,
一共占掉 84 字节的线路时间。
**为什么最小帧是 64 字节,这件事必须算一遍**:
**10 Mbps 下,一个比特 100 纳秒,64 字节 = 512 比特 = 51.2 微秒。**
**这个 51.2 微秒就是「最远两台站之间信号往返一轮」的时间上限。**
**帧比这更短,发送方就会在「还没听到碰撞」的时候已经发完**——**于是它以为发成功了,实际上撞了。**
**所以 64 字节不是随便定的,它是「网络直径 2500 米」反推出来的。**
拼出来的字节串是 24 字节(14 头 + 10 载荷):
前 6 字节 9cb6d00dd50f 是目的 MAC,中间 6 字节是源 MAC,
0x0800 说明后面装的是 IPv4,最后 10 字节原样是载荷。
「手工造包」这条路是通的——协议不是神秘的东西,就是按位置填字节。
这个帧其实「不合法」:载荷只有 10 字节,少于最小 46,
真实网卡会把它填充到 46,帧长变成 64。
所以「发 1 个字节」在线上是 64 字节的帧 + 8 字节前导码 + 12 字节空闲 = 84 字节的线路时间。
三个数 24 / 32 / 44 的差,正好是「软件视角」和「线路视角」的差:
写程序的人只看到 24,网卡发出去是 44,中间那 20 不属于「帧」。
这就是为什么讨论帧长时必须先说口径——否则「1518 还是 1526」这种争论永远说不清。
30 秒实验
- 把载荷改成
b'x' * 46,看帧长变成多少(正好 64)。 - 把类型改成
0x0806,想想这表示什么(ARP)。 - 算一下:10 Mbps 下,一个 1518 字节的帧在线上占多久(含前导码与 IFG)?
常见误区
- 以为「帧长 64」是从前导码算起。 不含前导码,含前导码是 72。
- 以为「1500 是为了对齐内存」。 是为了公平,不让一个站占线太久。
- 以为「连续发送能跑到 100% 带宽」。 每帧之间要留 12 字节空闲,上限约 99.2%。
自测三问(点开对答案)
1. 以太网帧最短和最长各是多少字节? —— **最短 64、最长 1518**(都不含前导码)。最短是为了碰撞检测,最长是为了公平。 2. 为什么最小载荷是 46 字节? —— **因为 14 + 46 + 4 = 64。** 头尾各占一部分,载荷不够就填充(padding)。 3. 「1518」和「1526」两个数为什么都常见? —— **1518 只算帧本身,1526 加了 8 字节前导码。**口径不同,都正确。下一节换一种介质:从有线走到无线。
- ☐ (本节完,下一节 3.4.1 无线通信的种类)