之前我们学习了网络层的TCP/IP协议,本期我们就来学习网络链路层相关的内容
目录
[IP 地址的相对特性](#IP 地址的相对特性)
[MAC vs IP 的严谨对比](#MAC vs IP 的严谨对比)
[MTU 对 IP 协议的影响](#MTU 对 IP 协议的影响)
[IPv4 分片](#IPv4 分片)
[IPv6 彻底杜绝中间分片](#IPv6 彻底杜绝中间分片)
[MTU 对 UDP 协议的影响](#MTU 对 UDP 协议的影响)
[MTU 对 TCP 协议的影响](#MTU 对 TCP 协议的影响)
[路径 MTU 发现 (PMTUD)](#路径 MTU 发现 (PMTUD))
[ARP 的本质与归属](#ARP 的本质与归属)
[ARP 工作流程(标准场景)](#ARP 工作流程(标准场景))
[ARP 缓存管理与生存周期](#ARP 缓存管理与生存周期)
数据链路与网络层
数据链路层 :解决同一网段内、相邻节点之间的可靠(或不可靠)帧传输。
网络层 :解决跨越多个网段、任意主机之间的数据包传输和路径选择。
它们之间的对比如下:
| 对比维度 | 数据链路层 (Layer 2) | 网络层 (Layer 3) |
|---|---|---|
| 寻址方式 | 硬件地址(MAC 地址,48 bit),扁平结构,无层次 | 逻辑地址(IPv4/IPv6 地址),有网络号与主机号的层次结构 |
| 协议数据单元 (PDU) | 帧 (Frame) | 包 (Packet) |
| 转发依据 | MAC 地址表(由自学习构建),按目标 MAC 查找出口 | 路由表(静态、动态路由协议),按目标 IP 做最长前缀匹配 |
| 典型互连设备 | 交换机(二层交换机)、网桥 | 路由器、三层交换机 |
| 作用范围 | 一个广播域内(即一个局域网或 VLAN) | 跨广播域,可连接不同网络(互联网) |
| 是否分段 | 无分段能力,MTU 一般由物理层限制(如以太网 1500 字节) | 具备分片与重组能力(IPv4 路由器可分片,IPv6 由源节点分片) |
| 可靠性与流量控制 | 某些协议(如 Wi-Fi MAC)提供确认与重传;以太网仅提供 CRC 校验,不保证可靠 | IP 本身是尽力交付(无连接、无确认、无重传),可靠交付由上层(TCP)负责 |
| 广播/组播控制 | 广播帧泛滥在同一广播域,不可穿越路由器 | 路由器隔离广播域,三层广播(如 255.255.255.255)受限不可路由 |
| 核心协议例子 | 以太网 (IEEE 802.3)、Wi-Fi (802.11)、PPP | IPv4、IPv6、ICMP、IGMP |
| 关键控制协议 | 无(生成树 STP 算二层防环协议) | ARP(介于二三层之间,用二层广播完成三层 IP→MAC 映射) |
以太网
以太网是一套完整的链路层协议
以太网帧结构

MAC地址
Media Access Control address ,即介质访问控制地址,是一个 48 位 的硬件标识符,用于在同一广播域(局域网)内唯一标识一个网络接口控制器(NIC)。它是二层帧转发的唯一依据。
结构与表示
-
长度:6 octets(48 bits)。
-
表示法:十六进制冒号分隔,如
52:54:00:12:34:56。 -
结构:
-
前 24 位:组织唯一标识符 (OUI),由 IEEE RA 分配给各厂商。
-
后 24 位:设备标识,由厂商保证在同一个 OUI 内唯一。
-
-
两个特定位:
-
I/G 位(第一个字节最低位):0 表示单播地址,1 表示组播/广播。
-
G/L 位(第一个字节次低位):0 表示全球唯一(出厂烧录),1 表示本地管理(软件修改)。
-
地址特性
-
扁平性:无网络号/主机号层次,只是 48 bit 的一维编号。无法聚合,无法做层次化路由。
-
全球唯一性(规范上):OUI 制度保证了烧录地址全球唯一,但可被驱动程序修改(导致冲突风险)。
-
作用域 :仅在一个广播域(Layer 2 domain)内有效。跨越路由器后,源和目标 MAC 都会被替换。
IP 地址的相对特性
IP 地址(IPv4 32 bit,IPv6 128 bit)是逻辑地址,作用于网络层,提供全局可路由的终端标识。
-
层次性:网络前缀 + 主机号,支持地址聚合,极大压缩路由表规模。
-
分配灵活性:按拓扑分配,终端移动至不同网络会获得不同 IP。
-
作用域 :在整个互联网中保持端到端一致性。IP 包头中的源、目的 IP 在转发过程中通常不改变(除非 NAT)。
MAC vs IP 的严谨对比
| 维度 | MAC 地址 | IP 地址 |
|---|---|---|
| 所属层 | 数据链路层(Layer 2) | 网络层(Layer 3) |
| 地址长度 | 48 bit | IPv4 32 bit,IPv6 128 bit |
| 结构 | 扁平,无层次 | 分层:网络前缀 + 主机号 |
| 分配机构 | IEEE RA → 厂商 OUI | IANA → 区域互联网注册机构 → ISP/企业 |
| 唯一性保证 | 出厂烧录全球唯一(可软件覆盖) | 在同一个地址域内按规划分配唯一 |
| 地址分类 | 单播、组播、广播 | 单播、组播、广播(受限)、任播(IPv6) |
| 作用范围 | 一段链路或一个广播域 | 可跨网络,全球可路由(公网地址) |
| 在转发中的行为 | 每跳改变(重新封装帧) | 目的 IP 全程不变,源 IP 也可能不变(除 NAT) |
| 寻址/查表 | 交换机 CAM 表做精确匹配(Exact Match) | 路由器 FIB 做最长前缀匹配(LPM) |
| 解析方向 | 直接用,但需 ARP 将 IP 映射为 MAC | DNS 将域名解析为 IP,DHCP 动态分配 |
| 编程接口 | AF_PACKET, struct ethhdr |
AF_INET, struct iphdr |
关键差异一句话:
-
MAC 回答"在这个网段里,帧下一跳交给谁"。
-
IP 回答"这个数据包最终要到世界的哪个终端"。
协作模型:为什么两层地址缺一不可?
数据从主机 A(IP-A,MAC-A)发往不同子网的主机 B(IP-B,MAC-B)时:
-
网络层封装:A 的协议栈构建 IP 包,目的 IP = IP-B(全程不变)。
-
路由决策:查询路由表,下一跳为网关 R1 的 IP。
-
链路层封装:通过 ARP 获得 R1 接口的 MAC-R1,以此为目标 MAC 构建帧。
-
第一跳转发:帧到达 R1,R1 拆除帧头,取出 IP 包,再查路由,重复封装新帧(源 MAC=R1 出口,目标 MAC=R2 入口)。
-
最终跳:最后一跳路由器发现 B 在直连网段,用 ARP 查到 MAC-B,目的 MAC 改为 MAC-B。
这清晰地显示:IP 保持端到端语义,MAC 逐跳替换完成实际交付。没有 MAC 地址,数据无法跨越物理链路;没有 IP 地址,数据跨不出一个局域网。
MTU
概念
MTU 是数据链路层帧所能承载的最大载荷长度,即帧中除去链路层头部和尾部后,所能装载的最大上层协议数据量。
以标准以太网为例:
-
以太网帧头(目的MAC + 源MAC + EtherType):14 字节
-
帧尾 FCS (CRC):4 字节
-
最大帧总长(不计前导码):1518 字节
-
MTU = 1518 - 14 - 4 = 1500 字节
另需注意一个相关概念:
- 路径 MTU (Path MTU):从源到目的整条路径上所有链路 MTU 的最小值。
在 Linux 下,你可以通过 ioctl 获取接口 MTU:
cpp
#include <sys/ioctl.h>
#include <net/if.h>
#include <string.h>
int get_mtu(const char *ifname) {
int fd = socket(AF_INET, SOCK_DGRAM, 0);
struct ifreq ifr;
strcpy(ifr.ifr_name, ifname);
ioctl(fd, SIOCGIFMTU, &ifr);
int mtu = ifr.ifr_mtu;
close(fd);
return mtu;
}
MTU 对 IP 协议的影响
IP 层负责将上层数据(如 TCP 段或 UDP 数据报)封装进 IP 包。当包长超过出口链路的 MTU 时,IP 层必须处理"装不下"的问题。
IPv4 分片
IPv4 允许源端或中间路由器对数据包进行分片,目的端重组。
关键字段:
-
DF 位 (Don't Fragment):若置 1,禁止分片。路由器会丢弃包并回送 ICMP "需要分片但 DF 置位" (Type 3, Code 4) 消息,告知 MTU 值。
-
MF 位 (More Fragments):除最后一片外均置 1。
-
Offset 字段:指示片在原始包中的偏移(单位 8 字节)。
分片对终端性能的影响:
-
增大了丢包概率:丢失一个分片,整个原始包即不可用,且需要全部重新传输。
-
加重目的端重组负担(内存、CPU)。
-
某些安全设备会丢弃分片(防御攻击)。
在 C++ 中,你可以通过原始套接字手动构造 IP 头来控制分片,但更常见的操作是设置 DF 标志:
cpp
int val = IP_PMTUDISC_DO; // 总是设置 DF
setsockopt(sock, IPPROTO_IP, IP_MTU_DISCOVER, &val, sizeof(val));
当发送一个禁止分片但长度超过路径 MTU 的包时,send 会返回 EMSGSIZE 错误,应用程序可以据此调整数据大小。
IPv6 彻底杜绝中间分片
IPv6 强制源端进行分片,中间路由器绝不执行分片。遇超过出口 MTU 的包,直接丢弃并回送 ICMPv6 "Packet Too Big"。因此,IPv6 必须依赖路径 MTU 发现 (PMTUD),否则通信可能失败。
MTU 对 UDP 协议的影响
UDP 是面向数据报的协议,应用程序交给 UDP 的数据大小直接对应一个 IP 包的数据部分。因此,UDP 数据报极易遭到 IP 分片。
分片的代价
假设你要发送 4096 字节的 UDP 数据报,以太网 MTU 1500 字节,扣除 IP 头(20 字节)和 UDP 头(8 字节),可用载荷为 1472 字节。该数据报会被切成 3 个分片:
-
片 1:IP头 + 1472 字节(MF=1, Offset=0)
-
片 2:IP头 + 1472 字节(MF=1, Offset=185)
-
片 3:IP头 + 1152 字节(MF=0, Offset=369)
若任一片丢失,整个数据报丢弃。在有 1% 丢包率的链路上,一个 3 片报文的全成功概率仅 0.99³ ≈ 97%,显著低于小报文。
最佳实践
-
应用层限制 :将 UDP 载荷大小控制在
MTU - 28字节以内(20 IP 头 + 8 UDP 头),即通常的 1472 字节以内,以避免分片。DNS 协议就是典型,传统限制为 512 字节有效载荷,扩展机制 EDNS0 可协商更大缓冲,但明智的公共 DNS 实现仍会限制路径 MTU。 -
设置 DF 位:UDP 发送时也可启用 DF,通过失败反馈确定路径 MTU,但多数简单的 UDP 应用不处理,仍依赖分片。
-
数据报分段重传:若需大块传输且要求可靠,应在上层设计切分与确认机制(如 QUIC 协议那样),而不是让 IP 层分片。
从编程角度看,发送超长 UDP 报文的代码可能非常简单:
cpp
int sock = socket(AF_INET, SOCK_DGRAM, 0);
const char *msg = huge_data; // 长度 2000
sendto(sock, msg, 2000, 0, (sockaddr*)&addr, sizeof(addr));
// 如果未设置 DF,内核会悄无声息地进行 IP 分片。
如果你想主动防止分片:
cpp
int df = 1; // 或用 IP_PMTUDISC_DO
setsockopt(sock, IPPROTO_IP, IP_MTU_DISCOVER, &df, sizeof(df));
if (sendto(...) < 0 && errno == EMSGSIZE) {
// 需要减小数据报大小
}
MTU 对 TCP 协议的影响
TCP 是面向字节流的协议,有内建机制巧妙规避 IP 分片,尽量使每个 IP 包恰好填满链路层帧。
MSS------最大段大小
TCP 三次握手时,通过选项字段协商 MSS。MSS 就是 TCP 段中数据部分的最大长度,通常等于:
MSS = MTU - IP首部长度(20) - TCP首部长度(20) = MTU - 40
标准以太网下 MSS = 1460 字节。这样 TCP 段封装成 IP 包后刚好不超过 MTU,从根本上杜绝分片。
内核会自动根据出口接口的 MTU 设置 MSS。你可以通过套接字选项获取本地 MSS(但不建议手动修改):
cpp
int mss;
socklen_t len = sizeof(mss);
getsockopt(sock, IPPROTO_TCP, TCP_MAXSEG, &mss, &len);
路径 MTU 发现 (PMTUD)
即使两端 MSS 按本地 MTU 计算,中间链路可能存在更小的 MTU(如隧道)。TCP 会通过 PMTUD 动态调整:
-
发送方设置 DF 位,发送一个等于当前 MSS 的段。
-
若中间路由器因 MTU 太小而丢弃,回送 ICMP "需分片"并告知该链路 MTU。
-
发送方据此降低 MSS,重传以适配路径 MTU。
PMTUD 是 TCP 可靠传输的重要辅助,但常遭遇 ICMP 黑洞 :网络管理员屏蔽 ICMP 消息,导致发送方持续发大包而丢包。此时 TCP 尝试几次后若握手失败,可能退化为低效率。为解决此问题,产生了 PLPMTUD(分组化层路径 MTU 发现,RFC 4821),TCP 通过探测不同大小的包来主动搜索可用 MTU,不依赖 ICMP。
对性能的实质影响
-
若 MTU 变小,每个 TCP 段的 MSS 变小,TCP 有效吞吐量降低(报头开销占比增加)。极端情况如某些 VPN 隧道 MTU 降至 1300,则 MSS = 1260,传输同样数据需要更多段。
-
TCP 流量控制与拥塞控制均基于段(segment)数量,较小 MSS 可能加剧某些场景的 CPU 负载,但避免分片换来的稳定性收益更为重要。
-
在 C++ 高性能服务端开发中,当需精细调优网络参数时,了解本地接口 MTU 和路径 MTU 对 TCP buffer 大小的影响至关重要。例如,
SO_RCVBUF和SO_SNDBUF的设置应考虑到 BDP(带宽延迟积),而 BDP 与有效 MSS 有关。
总结与开发者的认知框架
-
MTU 是链路层的物理上限,IP 层必须遵从。
-
对 IP:分片是迫不得已的后备手段,IPv4 尚可用,IPv6 则强制退出中间路由器,逼迫源端重视 MTU 发现。
-
对 UDP:分片危险且无补偿机制,开发者必须主动将数据报控制在一定大小内,或在上层自行实现可靠传输与分段。
-
对 TCP:MSS 协商 + PMTUD 自动规避分片,TCP 栈已为我们做好绝大多数工作,但要注意防火墙对 ICMP 的干扰,以及跨隧道时 MTU 的异常。
从 C++ 网络编程接口看,你掌握着检测、控制、规避 MTU 的能力:
-
ioctl(SIOCGIFMTU)→ 获取接口 MTU -
setsockopt(IP_MTU_DISCOVER)→ 控制分片策略和 DF 位 -
getsockopt(TCP_MAXSEG)→ 查看协商的 MSS -
错误处理中识别
EMSGSIZE和 ICMP 带来的EINVAL或超时征兆
ARP协议
ARP 的本质与归属
-
本质:一个请求---应答协议,将 32 位 IPv4 地址动态解析为 48 位 MAC 地址。
-
所在层次:传统上视为 2.5 层协议------直接封装在以太网帧中(EtherType = 0x0806),无 IP 头部,但其服务对象是网络层。
-
作用范围 :严格限制在一个广播域(即一个局域网或 VLAN)内,ARP 请求是广播帧,永远不会被路由器转发。
-
对应 IPv6 的协议:NDP(邻居发现协议,使用 ICMPv6),不再用 ARP。
ARP 工作流程(标准场景)
假设主机 A(10.0.0.1)需要与同子网主机 B(10.0.0.2)通信,但不知道 B 的 MAC 地址。
-
检查 ARP 缓存
- A 先查找本机 ARP 表(
arp -n)。若已存在10.0.0.2的 MAC 条目,直接使用,跳过后续步骤。
- A 先查找本机 ARP 表(
-
发送 ARP 请求
-
若无缓存,A 构造 ARP 请求报文:
-
操作码 = 1
-
发送方 IP / MAC = A 的
-
目标 IP = 10.0.0.2
-
目标 MAC = 00:00:00:00:00:00
-
-
封装成以太网帧:目的 MAC = 广播 ff:ff:ff:ff:ff:ff,源 MAC = A 的 MAC,EtherType = 0x0806。
-
该帧在整个广播域中泛洪,所有主机的网卡都会收到。
-
-
主机 B 响应
-
B 的网卡收到帧,发现 EtherType 为 ARP,交给 ARP 处理。
-
B 看到目标 IP 是自己的 10.0.0.2,于是记录 A 的 IP-MAC 映射到自己的 ARP 缓存(主动学习)。
-
构造 ARP 应答:
-
操作码 = 2
-
发送方 IP / MAC = B 的
-
目标 IP / MAC = A 的
-
-
封装成以太网帧:目的 MAC = A 的 MAC(单播),直接发给 A。
-
-
A 学习并缓存
-
A 收到应答,提取 B 的 MAC,存入 ARP 缓存,老化计时器(通常 20 分钟)开始倒计时。
-
之后发往 B 的数据帧即可使用正确的目的 MAC。
-
ARP 缓存管理与生存周期
ARP 缓存是内核维护的一张核心表,在 Linux 中可通过 /proc/net/arp 或 ip neigh 查看,每个条目有以下状态:
-
REACHABLE:正常可用。
-
STALE:老化后,使用前需先确认(可达性检测)。
-
DELAY / PROBE:处于探测过程中的状态。
-
FAILED:多次探测无响应。
老化机制存在"被动更新"与"主动垃圾回收"。C++ 编程中可通过 Netlink 或 ioctl 操作此表。
ARP协议格式

注意到源MAC地址、目的MAC地址在以太网首部和ARP请求中各出现一次,对于链路层为以太网的情况是多余的,但如果链路层是其它类型的网络则有可能是必要的。
硬件类型指链路层网络类型,1为以太网;
协议类型指要转换的地址类型,0x0800为IP地址;
硬件地址长度对于以太网地址为6字节;
协议地址长度对于和IP地址为4字节;
op字段为1表示ARP请求,op字段为2表示ARP应答。
本期内容就到这里了,喜欢请点个赞谢谢
封面图自取:
