📚 第三章 数据链路
📄 一句话概括

以太网帧 = 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 秒实验

  1. 把载荷改成 b'x' * 46,看帧长变成多少(正好 64)。
  2. 把类型改成 0x0806,想想这表示什么(ARP)。
  3. 算一下:10 Mbps 下,一个 1518 字节的帧在线上占多久(含前导码与 IFG)?

常见误区

  1. 以为「帧长 64」是从前导码算起。 不含前导码,含前导码是 72。
  2. 以为「1500 是为了对齐内存」。 是为了公平,不让一个站占线太久。
  3. 以为「连续发送能跑到 100% 带宽」。 每帧之间要留 12 字节空闲,上限约 99.2%。
自测三问(点开对答案) 1. 以太网帧最短和最长各是多少字节? —— **最短 64、最长 1518**(都不含前导码)。最短是为了碰撞检测,最长是为了公平。 2. 为什么最小载荷是 46 字节? —— **因为 14 + 46 + 4 = 64。** 头尾各占一部分,载荷不够就填充(padding)。 3. 「1518」和「1526」两个数为什么都常见? —— **1518 只算帧本身,1526 加了 8 字节前导码。**口径不同,都正确。

下一节换一种介质:从有线走到无线。

相关笔记