数据链路层核心:帧结构与封装、MAC地址与寻址、差错控制(CRC校验)、流量控制机制
数据链路层,说白了就是给物理层那堆「0和1」加上规矩。我刚开始接触协议栈时,总觉得这层就是个中间人,没什么技术含量。后来被坑过几次才明白——没有数据链路层,上层协议就是空中楼阁。
咱们这一章,就把数据链路层的四个核心模块拆开揉碎。帧怎么封装的?MAC地址怎么寻址?CRC校验到底怎么算?流量控制又是怎么防止丢包的?一个一个来。
帧结构与封装:数据链路层的「快递包裹」
物理层只管发比特流,它不关心这些比特代表什么。数据链路层要做的第一件事,就是把这些比特组织成有结构的「帧」。你想想看,快递员送包裹,总得有个包装盒、收件人地址、寄件人信息吧?帧就是网络世界里的快递包裹。
一个典型的以太网帧长这样:
| 字段 | 长度(字节) | 说明 |
|---|---|---|
| 前导码 | 7 | 同步时钟,让接收方准备好 |
| 帧起始定界符 | 1 | 标志帧的开始 |
| 目的MAC地址 | 6 | 谁收这个包 |
| 源MAC地址 | 6 | 谁发的这个包 |
| 类型/长度 | 2 | 上层协议类型(如IPv4=0x0800) |
| 数据载荷 | 46~1500 | 上层传下来的IP包 |
| 帧校验序列(FCS) | 4 | CRC校验值 |
这里有个细节我特别想强调——最小帧长度64字节。为什么是64?因为要保证在冲突检测机制下,发送方还没发完就能知道有没有冲突。如果帧太小,发完了冲突才回来,那就没法重传了。我在做嵌入式网卡驱动时,就遇到过因为填充字节没处理好,导致小包被交换机丢弃的坑。
关键点:帧封装不是简单的「加个头尾」。前导码用于时钟同步,FCS用于完整性校验,中间的填充字节保证最小帧长。每一段都有它的设计理由。
MAC地址与寻址:48位的「身份证」
MAC地址,全称是Media Access Control Address。每个网卡出厂时烧录了一个全球唯一的48位标识符。前24位是厂商代码(OUI),后24位是厂商自己分配的序列号。
我习惯把MAC地址比作人的身份证号——理论上全球唯一,但你可以改(软件层面)。不过在实际工程中,我强烈建议你不要随便改。有一次我在调试一个物联网网关,为了测试改了MAC地址,结果跟另一台设备冲突了,整个局域网广播风暴,排查了整整一个下午。
MAC地址的寻址方式有三种:
- 单播(Unicast): 目的MAC地址第一位是0,发给特定设备。
- 广播(Broadcast): 全F(FF:FF:FF:FF:FF:FF),发给所有设备。
- 组播(Multicast): 目的MAC地址第一位是1,发给一组设备。
交换机就是靠MAC地址表来转发帧的。它学习源MAC地址和端口的映射关系,然后根据目的MAC地址决定从哪个端口发出去。如果没学到,就泛洪到所有端口(除了接收端口)。
实战建议:抓包时看到大量广播帧,别慌。ARP请求就是广播的,这是正常行为。但如果广播帧占比超过10%,那就要查查是不是有环路或者ARP攻击了。
差错控制(CRC校验):数据完整性的「守门员」
物理层传输过程中,信号可能受干扰,比特可能翻转。数据链路层怎么知道收到的帧有没有出错?靠CRC(循环冗余校验)。
CRC的原理不复杂:发送方把数据看作一个巨大的二进制数,除以一个约定的生成多项式,余数就是CRC值。接收方用同样的方法算一遍,如果余数不为0,说明数据出错了。
以太网用的是CRC-32,生成多项式是:
G(x) = x³² + x²⁶ + x²³ + x²² + x¹⁶ + x¹² + x¹¹ + x¹⁰ + x⁸ + x⁷ + x⁵ + x⁴ + x² + x + 1
嗯,这个多项式看着挺长,但硬件实现起来非常快。我当年在FPGA上实现过CRC-32的并行计算,用LFSR(线性反馈移位寄存器)结构,一个时钟周期就能算完64位数据。
CRC能检测出:
- 所有单比特错误
- 所有双比特错误
- 所有奇数个比特错误
- 所有长度不超过32的突发错误
- 99.99999998%的长度超过32的突发错误
说白了,CRC-32的检错能力非常强。但注意,它不能纠错。发现错误怎么办?简单——丢弃,让上层重传。数据链路层不负责纠错,只负责「发现错误并扔掉」。
我曾经踩过的坑:有一次在无线链路上,CRC校验一直报错。我以为是干扰太大,折腾了半天天线和功率。最后发现是驱动里CRC计算用的多项式跟对端不一致。两边用的生成多项式不同,当然永远对不上。所以,CRC算法一定要跟协议标准对齐,别自己发明。
流量控制机制:别让发送方「撑死」接收方
发送方拼命发,接收方处理不过来,缓冲区满了就开始丢包。流量控制就是解决这个问题的。
数据链路层的流量控制主要有两种方式:
| 机制 | 工作原理 | 典型应用 |
|---|---|---|
| 停等协议 | 发一帧,等确认,再发下一帧 | 简单链路,效率低 |
| 滑动窗口 | 连续发多帧,接收方确认后窗口滑动 | 以太网、Wi-Fi、PPP |
停等协议很好理解,但效率太低了。你想想看,发一个包等一个确认,链路利用率可能不到50%。滑动窗口就不一样了——发送方可以连续发W个帧,接收方确认一个,窗口就往前滑一个。
窗口大小W怎么定?这取决于链路的带宽和延迟。我习惯用「带宽×延迟积」来估算:
窗口大小 = 带宽 × 往返时延 / 帧大小
举个例子,100Mbps的链路,往返时延10ms,帧大小1500字节:
窗口大小 = (100 × 10⁶) × (10 × 10⁻³) / (1500 × 8) ≈ 83 帧
也就是说,发送方至少要有83帧的窗口,才能把链路跑满。如果窗口太小,链路就喂不饱,白白浪费带宽。
核心思路:流量控制不是「发慢点」,而是「发得刚刚好」。窗口大小要匹配接收方的处理能力和链路的传输能力。过大导致丢包,过小导致带宽浪费。
知识体系总览
下面这张图把数据链路层的四个核心模块串起来了。我建议你把它当作一个思维导图来记:
数据链路层这四个模块,在实际工程中是环环相扣的。帧结构决定了你能传什么,MAC地址决定了传给谁,CRC保证了传得对,流量控制保证了传得稳。任何一个环节出问题,上层协议都跑不起来。
我个人建议,学这章的时候别光看书。打开Wireshark抓几个包看看,亲眼看看帧结构长什么样,MAC地址怎么填的,FCS字段的值是多少。实践出真知,这话在协议栈领域尤其适用。