开发过程中有些细节容易被忽略,今天挑几个重点聊一聊。
🔥草莓熊Lotso:个人主页
❄️个人专栏: 《C++知识分享》 《Linux 入门到实践:零基础也能懂》
✨生活是默默的坚持,毅力是永久的享受!
🎬 博主简介:
文章目录
二. 路由:网络层的核心功能 三. IP 报文的分片与组装- 3.1 MTU 与分片的由来
- 3.2 为什么 IP 分片不应该是主流
- 3.3 MSS:TCP 避免 IP 分片的手段
- 3.4 IP 首部中的分片相关字段
- 3.5 分片的具体过程
- 3.6 组装的具体过程
- 3.7 分片内存对齐:协议设计的天才之处
- 3.7.1 协议解耦:IP 与底层链路无关
- 3.7.2 8 字节对齐:用 13 位表达 16 位偏移
- 3.7.3 最后一个分片的特殊处理
前言
当你在浏览器输入www.baidu.com并按下回车时,数据是如何从你的电脑穿越层层网络到达百度服务器的?为什么不同局域网内的主机能使用相同的192.168.1.x地址却不会冲突?为什么大文件传输时不会因为数据包过大而被丢弃?这些麻烦的答案都藏在 TCP/IP 协议栈的网络层中。网络层的核心协议 ——IP 协议,负责为每一个数据包找到从源主机到目的主机的最佳路径,解决 “去哪里"和"怎么走” 的麻烦。这篇文章将从公网的本质讲起,深入解析路由转发的核心逻辑,最后拆解 IP 分片与组装的底层实现,带你彻底搞懂网络层的工作原理。
一. 尝试搞懂公网
1.1 NAT 技术:缓解 IP 地址枯竭的关键
我们都清楚 IPv4 地址只有 32 位,理论上最多只能给出约 43 亿个地址。但全球联网设备早已远超这个数字,这其中 NAT(网络地址转换) 技术功不可没。
- 路由器的双 IP 结构:每个家用路由器都有两个 IP 地址
- LAN 口 IP:局域网网关 IP,通常为
192.168.1.1/24
10.0.0.0/8:前 8 位为网络号,共 1677 万个地址172.16.0.0/12:前 12 位为网络号,共 104 万个地址192.168.0.0/16:前 16 位为网络号,共 65536 个地址
核心结论:同一个子网内 IP 地址不能重复,不同子网 IP 地址能重复。NAT 技术让私有 IP 不会出现在公网中,变相极大地缓解了 IP 地址不足的麻烦。
1.2 公网的层级结构:从国际到家庭
真实的公网并不是一个扁平的网络,而是一个多层级的树形结构,我们能简化为以下四层:
1. 国际骨干网:由各国的国际路由器组成,通过海底光缆连接。每个国家向 ICANN 申请 IP 地址段,例如中国拥有5.0.0.0/8这样的大段地址。
2. 国内骨干网:国家将 IP 地址进一步划分给各个省份,例如陕西分配到5.1.0.0/16,河北分配到5.5.0.0/16。
3. 市级骨干网:省份再将地址划分给各个城市,例如西安分配到5.1.16.0/20。
4. 局域网:城市运营商不再使用公网 IP,而是为用户分配私有 IP,构建家庭或企业局域网。
1.3 报文的跨网传输流程
以 “俄罗斯用户访问中国西安的一台服务器” 为例,报文的传输过程如下:
1. 俄罗斯用户的报文首先到达俄罗斯的国际路由器
2. 国际路由器通过BGP 协议查询路由表,发现5.0.0.0/8属于中国,将报文转发给中国的国际路由器
3. 中国的国际路由器查询路由表,发现5.1.0.0/16属于陕西,转发给陕西的省间路由器
4. 陕西的省间路由器查询路由表,发现5.1.16.0/20属于西安,转发给西安的市级路由器
5. 西安的市级路由器通过 NAT 转换,将报文转发给对应的局域网主机
关键逻辑:报文转发的唯一依据是网络号。路由器将目的 IP 与路由表中的子网掩码进行按位与运算,得到网络号后,再转发到对应的下一跳地址。
二. 路由:网络层的核心功能
2.1 路由的基本概念
- 路由:在复杂的网络结构中,找出一条通往终点的路线。
- 一跳(Hop):数据链路层中的一个传输区间,在以太网中就是从源 MAC 地址到目的 MAC 地址的帧传输。
- 点对点通信:IP 协议实现了从源主机到目的主机的端到端通信,而数据链路层只负责一跳内的通信。
2.2 路由表的结构与转发逻辑
每个主机和路由器都维护一张路由表,我们可以在 Linux 系统中使用route命令查看:
whb@iv-ye4ege8iyo5i3z3clix9:~$ route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.0.1 0.0.0.0 UG 100 0 0 eth0
192.168.0.0 0.0.0.0 255.255.0.0 U 100 0 0 eth0
192.168.0.1 0.0.0.0 255.255.255.255 UH 100 0 0 eth0
路由表关键字段解释:
字段含义Destination目的网络地址Gateway下一跳地址(0.0.0.0 表示直连网络)Genmask子网掩码Flags标志位:U = 有效,G = 下一跳是路由器,H = 目的是主机Iface发送接口
转发逻辑(最长前缀匹配):
1. 将目的 IP 与路由表中每一条的子网掩码进行按位与运算
2. 得到网络号后,与对应的 Destination 字段算是
3. 选择前缀最长(子网掩码中 1 最多)的匹配条目
4. 按照该条目的下一跳地址和发送接口转发报文
转发示例:
- 目的 IP 为
192.168.56.3:与255.255.255.0按位与得到192.168.56.0,匹配对应条目,从 eth1 接口直接发送到目的主机 - 目的 IP 为
202.10.1.2:与所有条目都不匹配,使用缺省路由(0.0.0.0),转发给网关192.168.10.1
2.3 缺省路由
当目的 IP 与路由表中所有条目都不匹配时,报文会被转发到缺省路由指定的下一跳地址。缺省路由就像我们问路时遇到的 “扫地大妈”,她不清楚具体地址,但会告诉你去问更专业的人。
在路由表中,缺省路由的 Destination 为0.0.0.0,Genmask 为0.0.0.0,Flags 为UG。
2.4 路由表生成算法
路由表的生成有两种方式:
- 静态路由:由网络管理员手动配置,适用于小型网络
- 动态路由:通过路由协议自动生成,适用于大型网络
- 链路状态算法(OSPF)
- 边界网关协议(BGP):用于互联网骨干网的路由协议
三. IP 报文的分片与组装
3.1 MTU 与分片的由来
以太网帧的数据部分有一个最大长度限制,称为MTU(最大传输单元),标准值为1500 字节。如果 IP 数据报的大小超过了 MTU,就需要进行分片,将一个大的数据报分割成多个小的分片,每个分片独立传输。
重要原则:IP 分片对传输层透明。传输层(TCP/UDP)不需要关心数据是否被分片,分片和组装的工作完全由 IP 层负责。
3.2 为什么 IP 分片不应该是主流
虽然 IP 协议支持分片,但分片会带来严重的性能问题:
1. 丢包率大幅上升:如果一个分片丢失,整个 IP 数据报都会被丢弃,需要重传所有分片。例如,将一个报文分成 4 个分片,每个分片的丢包率为 1%,则整个报文的丢包率为1 - 0.99^4 ≈ 3.94%,是原来的 4 倍。
2. 增加路由器负担:分片需要路由器进行额外的计算和处理。
3.3 MSS:TCP 避免 IP 分片的手段
为了避免 IP 分片,TCP 引入了 MSS(最大段大小) 的概念。MSS 是 TCP 报文段中数据部分的最大长度,计算公式为:
MSS = MTU - IP首部长度 - TCP首部长度
在标准以太网中,MTU=1500,IP 首部 = 20 字节,TCP 首部 = 20 字节,因此MSS=1460 字节。TCP 会将应用层数据分割成不超过 MSS 大小的段,这样 IP 层就不需要再进行分片了。
这也解释了为什么 TCP 的滑动窗口是一段段发送的,而不是一次性发送所有数据 —— 核心目的就是为了避免 IP 分片,降低丢包率。
3.4 IP 首部中的分片相关字段
IP 首部中有三个字段专门用于分片和组装:
字段长度作用16 位标识(ID)16 位唯一标识一个 IP 数据报。同一个数据报的所有分片具有相同的 ID。3 位标志(Flags)3 位第 1 位保留;第 2 位 DF=1 表示禁止分片;第 3 位 MF=1 表示还有更多分片,MF=0 表示最后一个分片。13 位片偏移(Fragment Offset)13 位表示当前分片的数据在原始数据报数据区中的偏移量,以 8 字节为单位。
3.5 分片的具体过程
示例:假设 IP 层有一个大小为 3000 字节的报文(包含 20 字节 IP 首部和 2980 字节有效载荷),MTU=1500 字节,如何分片?
1. 计算分片数量:
每个分片的最大有效载荷 = MTU - IP 首部 = 1500 - 20 = 1480 字节
2. 2980 字节有效载荷需要分成 3 片:1480 + 1480 + 20 = 2980 字节
每个分片的字段设置:
1. 分片 1:
- 总长度:20 + 1480 = 1500 字节
- 标识:111(与原始报文相同)
- 标志位:MF=1(还有更多分片)
- 片偏移:0(0×8=0 字节)
- 总长度:20 + 1480 = 1500 字节
- 标识:111
- 标志位:MF=1
- 片偏移:185(185×8=1480 字节)
- 总长度:20 + 20 = 40 字节
- 标识:111
- 标志位:MF=0(最后一个分片)
- 片偏移:370(370×8=2960 字节)
3.6 组装的具体过程
目的主机的 IP 层接收到分片后,按照以下步骤进行组装:
1. 识别分片:如果MF=1或者MF=0且片偏移>0,则该报文是一个分片。
2. 聚合分片:根据标识字段将属于同一个数据报的所有分片聚合在一起。
3. 检查完整性:
务必收到片偏移为 0 的分片(第一片)
4. 务必收到 MF=0 的分片(最后一片)
5. 所有分片的片偏移务必连续(前一个分片的片偏移 + 数据长度 / 8 = 下一个分片的片偏移)
组装数据报:按照片偏移的顺序将所有分片的数据部分拼接起来,恢复成原始的 IP 数据报。传递给上层:将组装好的 IP 数据报传递给传输层处理。
3.7 分片内存对齐:协议设计的天才之处
很多人会有一个疑问:IP 首部的16 位总长度字段决定了 IP 报文最大可以达到2^16=65535字节,但片偏移只有 13 位,怎么可能完整表达最大 65535 字节报文的所有偏移量?这看似是协议的漏洞,实则是设计者精心设计的内存对齐机制,用最少的比特位实现了最大的表达能力。
3.7.1 协议解耦:IP 与底层链路无关
首先要明确一个核心设计原则:IP 协议要与底层数据链路层解耦
- IP 协议本身规定最大报文长度为 65535 字节,这是由 16 位总长度字段决定的,与底层链路无关
- 以太网的 MTU=1500 字节只是当前最常用的链路限制,如果未来换成无线 LAN、光纤等其他链路,MTU 可能会发生变化
- IP 协议不应该因为底层链路的变化而修改自身的核心设计,这就是 “各管各的” 解耦思想
类比:高速路限速 120 码,但汽车的设计最高时速可以达到 200 码。你可以根据限速调整车速,但不能要求所有汽车都只能造到 120 码的最高时速。
3.7.2 8 字节对齐:用 13 位表达 16 位偏移
为了解决 13 位片偏移无法覆盖 16 位总长度的问题,协议设计者引入了8 字节对齐规则:
除最后一个分片外,所有分片的有效载荷长度必须是 8 的整数倍。
这个规则的本质是:所有非最后分片的片偏移值乘以 8 后,低 3 位一定都是 0。既然低 3 位永远是 0,那就不需要在片偏移字段中存储它们,只需要存储高 13 位即可。
- 存储时:将实际偏移量除以 8(右移 3 位),只存高 13 位
- 读取时:将片偏移值乘以 8(左移 3 位),自动补上低 3 位的 0
2^13 * 8 = 65536字节的偏移量,刚好覆盖 IP 报文的最大长度 65535 字节。用 13 位就实现了原本需要 16 位才能做好的功能,这种极致的优化堪称协议设计的典范。
3.7.3 最后一个分片的特殊处理
为什么最后一个分片不需要遵守 8 字节对齐规则?
- 最后一个分片后面没有其他分片,不需要通过片偏移来定位下一个分片的起始位置
- 即使最后一个分片的长度不是 8 的整数倍,也不会影响整个报文的组装
- 例如我们之前的 3000 字节示例:最后一个分片只有 20 字节有效载荷,不是 8 的整数倍,但完全不影响组装
补充:结合之前的 3 位标志位,我们只用了3位标志+13位片偏移共 16 个比特位,就同时实现了 “区分是否分片”、“标识分片位置”、“判断是否是最后一个分片” 三个核心功能,这也是为什么说 IP 分片的字段设计是天才之作。
四. Linux 内核 IP 分片与组装源码解读
Linux 内核中 IP 分片和组装的核心函数位于net/ipv4/ip_output.c和net/ipv4/ip_input.c中。
4.1 IP 分片函数:ip_fragment ()
int ip_fragment(struct sock *sk, struct sk_buff *skb,
int (*output)(struct sock *, struct sk_buff *))
{
struct iphdr *iph = ip_hdr(skb);
int mtu = dst_mtu(skb_dst(skb));
int hlen = iph->ihl * 4;
int len = skb->len - hlen; // 有效载荷长度
int ptr = 0;
struct sk_buff *skb2;
// 检查是否禁止分片
if (iph->frag_off & htons(IP_DF)) {
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(mtu));
kfree_skb(skb);
return -EMSGSIZE;
}
// 循环分片
while (ptr < len) {
int frag_size = min(mtu - hlen, len - ptr);
// 除了最后一个分片,其他分片的长度必须是8的整数倍
if (frag_size < len - ptr)
frag_size &= ~7;
// 分配新的skb
skb2 = alloc_skb(frag_size + hlen + LL_MAX_HEADER, GFP_ATOMIC);
if (!skb2) {
kfree_skb(skb);
return -ENOMEM;
}
// 复制IP首部
skb_reserve(skb2, LL_MAX_HEADER);
skb_put(skb2, hlen + frag_size);
skb_copy_to_linear_data(skb2, iph, hlen);
// 复制数据部分
skb_copy_bits(skb, hlen + ptr, skb2->data + hlen, frag_size);
// 设置分片字段
iph2 = ip_hdr(skb2);
iph2->frag_off = htons((ptr / 8) | (ptr + frag_size < len ? IP_MF : 0));
iph2->tot_len = htons(skb2->len);
ip_send_check(iph2);
// 发送分片
output(sk, skb2);
ptr += frag_size;
}
kfree_skb(skb);
return 0;
}
核心逻辑解读:
1. 首先检查 DF 标志位,如果禁止分片,则发送 ICMP"需要分片但 DF 置位" 错误
2. 计算每个分片的大小,确保除最后一个分片外,其他分片的长度是 8 的整数倍
3. 为每个分片分配新的 skb,复制 IP 首部和对应的数据部分
4. 设置片偏移和 MF 标志位,重新计算 IP 首部校验和
5. 调用输出函数发送每个分片
4.2 IP 组装函数:ip_defrag ()
struct sk_buff *ip_defrag(struct net *net, struct sk_buff *skb, u32 user)
{
struct iphdr *iph = ip_hdr(skb);
struct ipq *qp;
int err;
// 查找或创建对应的IP队列
qp = ip_find(net, iph->id, iph->saddr, iph->daddr, iph->protocol, user);
if (!qp) {
kfree_skb(skb);
return ERR_PTR(-ENOMEM);
}
// 将分片加入队列
err = ip_frag_queue(qp, skb);
if (err) {
ipq_put(qp);
return ERR_PTR(err);
}
// 检查是否所有分片都已到达
if (ip_frag_complete(qp)) {
struct sk_buff *ret = ip_frag_reasm(qp);
ipq_put(qp);
return ret;
}
ipq_put(qp);
return ERR_PTR(-EINPROGRESS);
}
核心逻辑解读:
1. 根据 IP 标识、源 IP、目的 IP 和协议查找对应的分片队列
2. 如果队列不存在,则创建一个新的队列
3. 将当前分片加入队列,并按照片偏移排序
4. 检查是否所有分片都已到达且完整
5. 如果完整,则调用ip_frag_reasm()组装成完整的 IP 数据报并返回
核心考点总结:
1. 公网与 NAT:
私有 IP 地址段:10.0.0.0/8、172.16.0.0/12、192.168.0.0/16
2. NAT 技术通过地址转换实现多个主机共享一个公网 IP
路由转发:
1. 路由转发的核心逻辑:最长前缀匹配
2. 路由表关键字段:目的网络、子网掩码、下一跳、发送接口
3. 缺省路由用于处理所有不匹配的目的地址
IP 分片与组装:
1. MTU=1500 字节,MSS=1460 字节(标准以太网)
2. 分片相关字段:16 位标识、3 位标志(DF/MF)、13 位片偏移(以 8 字节为单位)
3. 分片会大幅增加丢包率,TCP 通过 MSS 机制避免 IP 分片
4. 接收方根据标识字段聚合分片,根据片偏移排序并检查完整性
网络层是整个 TCP/IP 协议栈的 “导航系统”,它为每一个数据包指明了前进的方向。搞懂了网络层的工作原理,你就掌握了互联网通信的核心逻辑。
结尾
🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点:
👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用
💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!
** 结语:IP 协议虽然已经诞生了 40 多年,但它依然是互联网的基石。从最初的 IPv4 到现在的 IPv6,虽然地址长度从 32 位扩展到了 128 位,但路由和分片的核心思想并没有改变。希望这篇文章能够帮助你彻底搞懂网络层 IP 协议的工作原理。
✨把这些内容吃透超牛的!放松下吧✨
>
ʕ˘ᴥ˘ʔ
>
づきらど
就写这么多吧,内容比较基础,适合入门回顾。有补充的地方欢迎留言一起完善。
评论 (0)
暂无评论