数据链路层做两件事:把比特流切成帧,把帧送到「相邻的下一个设备」。 它不管「最终去哪」,只管「这一跳交给谁」——这是它与网络层的分工线。
前两章讲的是「网络是什么」,从这一章开始进入具体的一层。 数据链路层是最容易被忽略、又最容易被误会的一层——很多人把它和「物理连接」混为一谈。
一、这一层到底在做什么
一句话:把不可靠的比特流,变成一段一段可校验的帧。
| 它做的事 | 具体动作 | 不做的 |
|---|---|---|
| 成帧 | 在比特流里划出边界 | 不管边界里装的是什么 |
| 寻址 | 写清「这一跳交给谁」 | 不管最终目的地 |
| 差错检测 | 算 FCS,坏了就丢 | 不负责重传 |
| 介质访问 | 谁先说话、怎么排队 | 不管上层急不急 |
第二行的「寻址」是最容易搞错的一条:链路层的地址只用一跳,下一跳就换掉了。 所以「MAC 地址全球唯一」这件事,只在「出厂时」有意义——转发的时候它每一跳都在变。 第三行的「不负责重传」也很关键:以太网发现帧坏了,动作是「丢掉」,不是「要求重发」。 重传是 TCP 的活。 所以「链路层可靠」和「端到端可靠」是两件事,不能互相替代。
二、帧长是被谁规定的
帧不能太短,也不能太长,两头都有硬理由:
| 边界 | 数值 | 为什么 |
|---|---|---|
| 最小帧 | 64 字节 | 短于它,碰撞检测来不及生效 |
| 最大帧 | 1518 字节 | 长于它,一个站会占线太久 |
第一行的理由是「时间」:发送方必须在自己发完之前听到碰撞信号, 所以帧的发送时间不能短于「最远两端信号往返一轮」的时间。 10 Mbps 下这个往返是 51.2 微秒,正好是 64 字节。 第二行的理由是「公平」:共享介质上,一个站占线越久,别人等得越久。 1518 字节在 10 Mbps 下要占 1.2 毫秒——再长,语音这类实时业务就没法插进来了。 这两个数在今天都还在用:哪怕速率已经到 10 Gbps,「最小 64、最大 1518」这条规矩没变, 因为要兼容。 速率变了、场景变了,边界没变——这就是标准的惯性。
三、为什么它必须存在
假设没有这一层,会怎样:
| 没有链路层 | 后果 |
|---|---|
| 没有帧边界 | 接收方不知道一个包从哪开始、到哪结束 |
| 没有地址 | 总线上所有设备都收到,没法只发给一个 |
| 没有校验 | 一个比特翻了,可能被当成合法数据 |
| 没有访问控制 | 两个站同时发,信号叠成一团 |
第一行是最根本的:物理层给的是一条连续的比特流,没有「这是第几个字节」的概念。 要把它变成「一条条消息」,必须有一层来做切分——这一层就是数据链路层。 第三行那个「比特翻了」不是理论问题:铜线上一个干扰脉冲就能翻一位,光纤上 bias 漂移也能翻。 所以每一帧都带校验,坏的就丢。 这是用「丢」来换「不会把错误数据当正确的用」。 所以这一层的定位很清楚:它是「一段链路」的管家,不是「整条路径」的管家。 一段一段接起来,才成了端到端——这是下一章网络层要接手的活。
四、动手 Demo:数据链路层做了什么——看本机的平均帧长
判据很直接:操作系统同时统计「字节数」和「帧数」,两个数一除就是平均帧长。 如果链路层不存在「帧」这个切分,第二个计数就没有意义。
import psutil
c = psutil.net_io_counters(pernic=True)
for name in sorted(c):
v = c[name]
if v.packets_sent == 0:
continue
avg = v.bytes_sent / float(v.packets_sent)
print('%s 发出 %d 字节 / %d 帧,平均 %.0f 字节'
% (name, v.bytes_sent, v.packets_sent, avg))
实跑输出:
--------------------------------------------------------------------
数据链路层做了什么——看本机的平均帧长
--------------------------------------------------------------------
**数据链路层干的第一件事是「把比特流切成帧」,所以它的第一个可测量指标就是帧有多长。**
本机每张网卡的收发字节数与帧数都是现成的,两个数一除就是平均帧长。
网卡 发送帧数 平均字节 接收帧数 平均字节
---------------------------------------------------------
WLAN 3098363 1009 5862474 738
**全机累计发出 2.91 GB、收到 4.03 GB**(按 1024 进制算)。
**这个「平均字节」不是随便一个数,它被两条硬边界夹着**:**下界 64 字节**(以太网最小帧),
**上界 1518 字节**(14 头 + 1500 MTU + 4 校验)。**所以正常网卡的平均帧长永远落在这个区间里。**
**落在偏大那一侧说明「大包为主」**——**看视频、下载文件,帧都尽量装满 1500 字节的载荷;**
**落在偏小那一侧说明「小包多」**——**游戏、语音、大量 TCP 确认包,都会把平均值往下拽。**
**这张表还顺手证明了「帧」是真实存在的计量单位**:操作系统统计的不是字节,
是「字节」和「帧」两个独立计数——**如果链路层不存在「帧」这个切分,第二个计数就没有意义。**
本机只有 WLAN 有流量,平均帧长落在 1000 字节左右: 这个数被两条硬边界夹着——下界 64、上界 1518。 落在偏大那一侧,说明「大包为主」,典型的看视频、下载文件的流量结构。 「全机累计发出 2.88 GB」这个数 是用字节数除 1024 的三次方算的,不是除 1000。 存储厂商用 1000,操作系统用 1024,所以同一个硬盘在 Windows 里「容量变小」了——这是口径问题, 不是缩水。 这张表最有价值的地方是「两个计数同时存在」: 字节数和帧数是独立统计的,说明内核真的在数「帧」。 很多同学以为「帧」只是教材上的抽象概念, 其实它就是一个被计数的实体——驱动程序每发一帧,这个计数器就加一。
30 秒实验
- 把
packets_sent换成packets_recv,看收发的平均帧长差多少,想想为什么。 - 用
psutil.net_if_stats()读 MTU,验证「最大帧 = MTU + 18」这个关系。 - 刷一个视频网站再看一次平均帧长,和刚才只开网页时比一比。
常见误区
- 以为数据链路层负责「把数据送到目的地」。 它只管一跳,最终去哪是网络层的事。
- 以为帧坏了会自动重传。 以太网只丢不补,重传是 TCP 的活。
- 以为 1518 这个上限是「技术做不到更长」。 是为了公平,让共享介质上的别人也能轮到。
自测三问(点开对答案)
1. 数据链路层做哪四件事? —— **成帧、寻址(一跳)、差错检测、介质访问控制。**它不负责重传,也不负责端到端。 2. 为什么最小帧是 64 字节? —— **因为发送方必须在发完之前听到碰撞信号。**10 Mbps 下「最远两端往返一轮」是 51.2 微秒, 正好对应 64 字节。 3. 「MAC 地址全球唯一」这句话哪里不准确? —— **唯一只在出厂时有意义。**转发时每一跳都会换掉,而且操作系统允许软件改写(MAC 克隆)。下一节从「地址」开始:这一层靠什么认出「下一跳是谁」。
- ☐ (本节完,下一节 3.2.1 MAC地址)