Linux socket 内核结构体关系
自上而下梳理调用链条:
-
task_struct(进程) 每个进程,里面有
files_struct,保存这个进程所有打开的文件描述符。 -
files_struct → fd_array\[\] 文件描述符数组。socket 也是文件,
socket()系统调用,分配一个 fd,数组里存struct file*。 -
struct file 一切打开的文件都用这个结构体。socket 作为特殊文件,
private_data指向struct socket。 -
struct socket socket 层,系统调用相关接口API(eg:send() recv() accept())。 里面成员:
struct sock *sk,指向真正网络协议层对象。
struct sock 才是真正内核网络对象,socket 是上层封装给系统调用用的壳子。
5.struct sock
发送队列、接收队列、TCP 序号、拥塞控制、三次握手、重传、滑动窗口,全部在这里。
C 没有类继承,用结构体嵌套实现继承:
struct inet_sock包含struct sock;struct tcp_sock包含struct inet_connection_sock;一层套一层。(看图)
struct inet_sock:IP 层信息,源 IP、目的 IP 等。struct inet_connection_sock:面向连接(TCP)专用(udp没有这一层,udp不需要建立连接)。struct tcp_sock:TCP 专属,存序号、窗口、重传、拥塞控制全部 TCP 细节。
struct sock 实现多态,体现在哪里
多态体现在两点
1. 指针强制转换
拿到 struct sock *sk,可以直接强转成子类:struct tcp_sock *tp = (struct tcp_sock *)sk;或者
struct udp_sock *tp = (struct udp_sock *)sk;同一个基类指针,实际指向不同子类对象,这就是多态。
2. 函数指针(虚函数表)
struct sock里面存一堆函数指针,比如: sk->sk_sendmsg、sk->sk_recvmsg
- TCP 创建 socket 的时候,把这些函数指针赋值成 TCP 版本函数;
- UDP 创建 socket 的时候,赋值成 UDP 版本函数。
内核统一调用 sk->sk_sendmsg(sk,...),不用管是 TCP 还是 UDP,自动跑对应协议的函数。
串起来的流程
用户调用read/recv(fd,...) 进程 task_struct → files_struct → fd 找到 struct file → private_data 拿到 struct socket → socket 的 sk 指针找到 struct sock。 内核就访问 sk->sk_receive_queue,把队列里的 sk_buff 数据拷贝到用户空间。
udp结构 (没有struct inet_connection_sock这一层)

IP网络层
1.网络层 vs 传输层

- IP(网络层)= 只有 "能力" IP 只管:把数据包从一台主机送到另一台主机。 只能做到尽力交付,不保证可靠,会丢包、乱序、重复,
- TCP(传输层)= 负责 "策略" 在源主机、目的主机两端做:确认、重传、序号、流量控制,实现 100% 可靠交付。
一个有策略一个有能力,就能把数据100%可靠的从一个源主机发到目标主机
ip协议和核心作用和构成
- IP 协议作用 提供尽力交付 的能力:把数据报从 A 主机,跨多个网络送到 B 主机。
只是 "很大概率送到",不保证可靠,会丢包、乱序、重复。
2.解决的问题:主机到主机
- IP 地址用来唯一标识一台主机。
- IP 地址 = 目标网络号 + 目标主机号。
- 路由本质:数据包从一个子网,转发进入另一个子网。
对比:
- IP(网络层):主机 → 主机,靠 IP 地址。
- TCP/UDP(传输层):进程 → 进程,靠端口号。
3.几个名词
- 路由器 :配 IP 地址,专门做路由转发;工作在网络层。
现代路由器本身是小型计算机,也可以跑应用层程序,但转发数据包核心逻辑只到网络层。即不会再向上解析传输层 TCP/UDP 头部,更不会交给本机应用层程序处理,转发流程在底层就完成。
- 节点:一个路由器,一个主机都可以叫做节点
IP协议头格式

类比:两地之间选什么样交通方式,根据业务需求选择走那一条路。
-
4 位版本号 version 指定 IP 协议版本,IPv4:0100;IPv6:0110
-
4 位头部长度 header length 单位:4 字节。 普通 IP 头部 20 字节:20/4 = 5,该字段填 5;最大值 15,对应 IP 头部最大 60 字节。
-
8 位服务类型 TOS 3 位优先权(已废弃) + 4 位 TOS + 1 位保留位(必须 0)。 4 个 TOS 选项:最小延时、最大吞吐量、最高可靠性、最小成本,互斥只能选一个。
-
ssh/telnet:看重最小延时
-
ftp:看重最大吞吐量
-
16 位总长度 total length 整个 IP 数据报(头部 + 数据)总字节数。
-
16 位标识 id 标记主机发出的 IP 报文。一个报文被分片之后,所有分片拥有相同 id。
-
3 位标志字段第 1 位:保留。第 2 位:禁止分片位。置 1 禁止分片;报文超过 MTU 直接丢弃。第 3 位:更多分片。最后一片置 0,其余分片置 1,相当于结束标记。
-
13 位分片偏移 fragment offset 表示分片在原始报文的位置。 实际字节偏移 = 该字段 × 8。 除最后分片外,其余分片数据长度必须是 8 字节整数倍。
-
8 位生存时间 TTL 最大跳数,常见初始值 64。 每经过一台路由器,TTL‑1;减为 0 还没到达目的地,报文直接丢弃。 主要是用来防防止出现路由循环
-
8 位协议 标识上层是什么协议,例如 tcp、udp。
-
16 位头部校验和 CRC 校验,只校验 IP 头部,检测头部是否损坏。
-
32 位源地址、32 位目的地址 发送方 IP、接收方 IP。
-
选项字段 长度不定,最多 40 字节,一般省略。
同样2个问题
分用问题 :8位协议字段。IP 收到报文,剥掉 IP 头,看这个值,确定把数据交给上层 TCP/UDP/ICMP哪一个。
**解包问题:**根据4位头部长度,确定头部有多长,data指针偏移这么多就是数据开始部分,并且还有16 位总长度 ,减去4位头部长度,还知道数据一共有多长
理解子网划分

把整个互联网类比成一所大学:
- IP 地址 = 学号:院号+学号
- 子网(网络号) = 学院编号(计算机学院、理学院、机械学院...)
- 主机号 = 学院里面的学生个人编号
eg:06 321 (06代表机械学院,321代表自己的学号,02理学院,03经管学院......)
06→ 网络号(机械学院)321→ 主机号(李四本人,学院内部编号)
IP = 目标网络号 + 目标主机号
找目标主机,先找目标网络(学院),再找网络内部主机(学生)
校园例子理解转发
- 张三在计算机学院( 01) 捡到了机械学院李四(子06)的钱包。
- 钱包上写学号
06321 - 张三发现目标学号不属于本院,01才是计算机学院,06不是,但他单单知道不是本学院,不清楚是哪一个具体的学院(理学院,材料.....) 所以交给本院的院学生会主席,再由他发信息到校群询问 不走内网转发,交给出入口路由器,子网转发。
- 机械学院的院学生会主席看到后发现06是本学院的标识,就再在机械学院的群里找到321对应的同学
理解对应关系
| 校园故事 | 网络中的对应概念 |
|---|---|
| 张三主机,在计算机学院(01) | 源主机,处在源子网01 |
钱包上面写学号06321 |
IP 报文头部写目标 IP 地址(网络号 06 + 主机号 321) |
张三对比:目标学院06≠自己学院01,不在同一个学院 |
源主机拿目标 IP,和自己子网掩码做运算,得到目标网络号,和本机网络号对比:不在同一个子网 |
| 张三不知道机械学院具体在哪,自己送不到,交给本院学生会主席(出入口) | 不能内网转发,交给路由器 |
| 学生会主席在校园主干(校群)转发,跨学院传递钱包 | 公网转发 。一路上各个路由器只看网络号 06 ,不关心主机号 321,一跳一跳转发 |
钱包到达机械学院,机械学院学生会主席看到06是本院编号 |
数据包抵达目标子网;路由器识别目标网络号和自己接口网络号匹配,即子网匹配 |
在机械学院内部,根据321找到李四同学 |
本子网内,根据主机号 321,交付给最终目标主机李四 |
相关概念:
内网转发(本子网内转发)
源主机和目标主机在同一个子网(同一个学院),不需要走出本学院,不经过外部路由器,学院内部直接交付
公网转发(跨子网转发)
源主机和目标主机不在同一个子网(不同学院) ,本学院找不到目标主机,把数据包交给出入口路由器,跨多个子网转发。路上只看网络号,抵达目标子网才看主机号。
为什么要做子网划分(为什么大学要分学院)
子网划分其实就是给网络预先做分组编号。
- 如果不划分子网,全网所有主机全部平铺在一起。找一台主机就要在全部主机里线性遍历 ,查找效率极低。
- 子网划分相当于预先分组 。路由查找本质是淘汰过程 :先把大量无关子网直接淘汰掉(比如要找01学院的我就可以直接淘汰02,03等等其他学院的),只留下目标所在子网,再在子网内部找主机,大幅提升查找效率。(先找学院再找学生)
网段划分

路由器
- 核心能力:连接多个不同子网(构建子网) 路由器有多个网络接口 ,每个接口配一个 IP,每个 IP 属于不同子网。
图中:一个接口
192.168.128.1(属于 128 网段),另一个接口192.168.144.1(属于 144 网段)。 这两个 IP,就是各自子网的网关地址。网关地址就是路由器接入对应本子网的网口的 IP值。
- 192.168.128.10 这台机器,网关填
192.168.128.1- 192.168.144.11 这台机器,网关填
192.168.144.1
内网路由:
所以加上掩码和网关的话上面的内网路由(192.全都是内网,不涉及公网转发)完整过程:
源:192.168.128.10,目标:192.168.144.11
- 源主机做分别那源ip和目标ip做掩码运算,发现目标网络号(192.168.144.0)和自己的网络号(192.168.128.0)不在同一个子网。
- 不能内网转发,数据包交给网关
192.168.128.1(路由器)。 - 路由器收到包,从另一个接口
192.168.144.1转发出去。 - 到达
192.168.144.0/24子网,根据主机号找到192.168.144.11主机
DHCP 协议
为什么要有: 如果没有 DHCP,就需要手动静态填写 IP、掩码、网关,家里设备多会很麻烦,还容易 IP 冲突。
- 协议层级:应用层协议,运行在路由器上面。
我们电脑手机的内网 IP,不是自己写死的,是 DHCP 服务器分配来的。
2.干什么 主机刚接入网络,自己没有 IP。主机向全网广播 DHCP 请求,路由器的 DHCP 服务收到请求,给主机分配:
- IP 地址
- 子网掩码
- 网关地址(路由器接口 IP)
- DNS 服务器地址
拿到这套参数,才可以正常上网。
DHCP就是路由器在应用层的体现,路由器上运行着应用层的 DHCP 服务
过去曾经提出⼀种划分网络号和主机号的方案,把所有IP地址分为五类,

随着 Internet 的飞速发展,这种划分方案的局限性很快显现出来
- A 类:一个大网,主机位 24 位,一个网络可以有一千六百多万台主机
- B 类:主机位 16 位,一个网络最多 65534 台主机(6 万 5 千多)
- C 类:主机位 8 位,一个网最多 254 台主机
现实问题: 几乎没有哪家公司,一个子网里面真的放 6 万台机器。 很多企业规模就几千台设备,想要比 C 类(254 台)大一点的网段,只能申请一整个 B 类地址。
申请一个 B 类,直接拿到 65534 个主机 IP。但公司实际只用 2000 个,剩下六万多个 IP 直接闲置,别人不能用,就白白浪费。 A 类网络更恐怖,一个 A 类一千六百多万地址,绝大多数机构根本用不完,大量地址被占住闲置。
结果:
- B 类地址被疯狂申请,很快全部耗尽;
- A 类很多网络拿在手里,大片地址空着,没法回收分给别人。
这就是有类 IP 划分的巨大缺陷:网络大小只能固定三档(A/B/C),不能灵活选网段大小,造成 IP 大量浪费。
针对这种情况提出了新的划分方案,称为 CIDR (Classless Interdomain Routing)(无类别域间路由)
CIDR
CIDR 就是干掉 A/B/C 这个死板分类,可以自由规定网络位多长。
CIDR 写法:IP / 前缀长度
例子:192.168.1.0 /22 /22:前 22 位是网络位,剩下 32‑22 =10 位是主机位。
简单计算
IPv4 一共 32 位
/24:网络 24 位,主机 8 位 → 和传统 C 类一样/16:网络 16 位,主机 16 位 → 和传统 B 类一样/8:网络 8 位,主机 24 位 → 和传统 A 类一样
但还可以写 /21 /22 /23,这是 ABC 分类做不到的(靠子网掩码实现)
子网掩码和 CIDR 的对应
| CIDR 前缀 | 子网掩码 |
|---|---|
| /24 | 255.255.255.0 |
| /23 | 255.255.254.0 |
| /22 | 255.255.252.0 |
| /16 | 255.255.0.0 |
理解子网掩码
网络号=子网掩码&IP地址

IP 固定:140.252.20.68,这是一个 B 类 IP。
例子 1:子网掩码 255.255.255.0
掩码二进制:11111111.11111111.11111111.00000000
- 前面 24 个
1:网络位 - 末尾 8 个
0:主机位(主机号)
①求网络号(网络地址):IP & 掩码(按位与)
只看最后 1 字节: IP 最后 1 字节:68 → 0100 0100 掩码最后 1 字节:0 → 0000 0000
0100 0100
& 0000 0000
------------------------------------------
0000 0000 → 0
网络号:140.252.20.0
主机号全部为 0 → 网络地址,代表这个子网本身,不能分配给电脑
②求广播地址:把全部主机号置 1
主机位是后面 8 位,全部写 1:11111111 =255 广播地址:140.252.20.255
主机号全部为 1 → 广播地址,用于广播,不能分配给电脑
③子网范围
网络号 ~ 广播地址 → 140.252.20.0 ~ 140.252.20.255
④可用主机数量
主机位 8 位:2^8-2 = 254台 减 2:扣掉【主机全 0 的网络地址】、【主机全 1 的广播地址】 真正能用 IP:140.252.20.1 ~ 140.252.20.254
例子 2:子网掩码 255.255.255.240
掩码二进制:11111111.11111111.11111111.11110000
- 前面 28 个
1:网络位 - 末尾 4 个
0:主机位(主机号)
①求网络号(按位与 &)
IP 末尾字节68:0100 0100 掩码末尾字节240:1111 0000
0100 0100
& 1111 0000
------------------------------------------
0100 0000 → 64
网络号:140.252.20.64
这里主机 4 位全部是 0,网络地址,不给主机用。
②求广播地址:全部主机号置1
网络号末尾字节:0100 0000 把后面 4 位主机号改成 1:0100 1111 =79 广播地址:140.252.20.79
主机 4 位全部是 1,广播地址,不给主机用。
③子网范围
140.252.20.64 ~ 140.252.20.79
④可用主机数量
主机位 4 位:2^4-2 =14台 真正能用 IP:140.252.20.65 ~ 140.252.20.78
主机号全 0、全 1 是什么
- 主机号全部比特为 0 :网络地址(网段号),用来标识这整个子网,不能分配给主机。
- 主机号全部比特为 1 :广播地址,向本子网所有设备发广播数据包,不能分配给主机。
子网掩码的作用(结合两张图理解)
可以通过设置掩码二进制中 1 的个数,重新设置网络号,充分利用 IP 地址资源
同样 IP:140.252.20.68
- 掩码
255.255.255.0(24 个 1) → 网络号140.252.20.0,子网很大,256 个 IP - 掩码
255.255.255.240(28 个 1) → 网络号140.252.20.64,子网很小,16 个 IP
- 改变掩码里面
1的数量,同一个 IP 算出来的网络号直接变,子网大小就被我们人为切割 。
- 1 越多 → 网络位变长,主机位变短,子网就越小(即一个子网内的主机数少)。
- 靠子网掩码切出很多小小的子网,需要多大切多大,剩下 IP 留给别的子网使用,IP 就不会浪费 。相当于是动态的形式了,不是以前固定写死了的类别
IP 地址的数量限制
我们知道,IP 地址 (IPv4) 是一个 4 字节 32 位的正整数。那么一共只有 2 的 32 次方 个 IP 地址,大概是 43 亿左右。而 TCP/IP 协议规定,每个主机都需要有一个 IP 地址。
这意味着,一共只有 43 亿台主机能接入网络么?
实际上,由于一些特殊的 IP 地址的存在,数量远不足 43 亿;另外 IP 地址并非是按照主机台数来配置的,而是每一个网卡都需要配置一个或多个 IP 地址。
CIDR 在一定程度上缓解了 IP 地址不够用的问题 (提高了利用率,减少了浪费,但是 IP 地址的绝对上限并没有增加),仍然不是很够用。这时候有三种方式来解决:
- 动态分配 IP 地址:只给接入网络的设备分配 IP 地址。因此同一个 MAC 地址的设备,每次接入互联网中,得到的 IP 地址不一定是相同的;
- NAT 技术 (后面会重点介绍);
- IPv6: IPv6 并不是 IPv4 的简单升级版。这是互不相干的两个协议,彼此并不兼容;IPv6 用 16 字节 128 位来表示一个 IP 地址;但是目前 IPv6 还没有普及
私有 IP & 公网 IP
私有 IP(内网 IP,局域网用)3 段固定范围
10.*172.16.* ~ 172.31.*192.168.*
私有 IP只能局域网内部通信,不能跑到互联网(公网)上面传输。(后面解释)
公网 IP(全局 IP)
除上面三段以外,其余全部是公网 IP。 公网 IP 全世界唯一,互联网上通信必须用公网 IP。
NAT过程

核心规则:每经过一台做 NAT 的路由器,就把报文的src源IP改成这台路由器自己 WAN 口对外 IP,目的 IP 全程不变。
完整流程:
家里电脑发出原始包
电脑:192.168.1.201,访问抖音122.77.241.3
src:192.168.1.201 dst:122.77.241.3
源是内网私有 IP。
2、经过自家家用路由器 NAT
路由器 WAN 口 IP:10.1.1.2,把源 IP 替换成 WAN 口 IP
src:10.1.1.2 dst:122.77.241.3
私有 IP
192.168.1.201从 IP 头部消失,路由器内部 NAT 表记录:哪个连接对应家里哪台主机。
3、再经过运营商路由器(又一层 NAT)
运营商路由器 WAN 口公网 IP:122.77.241.4,再次改写源 IP
src:122.77.241.4 dst:122.77.241.3
现在报文带着公网源 IP,进入广域网发给抖音服务器。
抖音服务器收到包,只看见源是公网122.77.241.4,完全看不见原始内网192.168.1.201。
4、抖音回复报文(回程)
抖音发回来的报文:
src:122.77.241.3 dst:122.77.241.4
目标地址是运营商公网 IP。 互联网路由根据目标 IP,一定会把这个包送到这个公网 IP 所属的运营商路由器。
运营商查自己 NAT 转换表,把目标 IP 改回10.1.1.2,转发给你家路由器; 你家路由器再查 NAT 表,目标改回内网192.168.1.201,内网转发交给电脑。
为什么一定要NAT 地址转换
因为内网 IP 可能重复(内网 IP 可以重复;但是同一个子网内部 IP 不能重复。)
A 家电脑:192.168.1.201;B 家电脑也可以是 192.168.1.201 ,IP 一模一样。数据包根本无法正确回送根本不知道谁是哪一个,通信直接报废。(也解释了私有 IP不能直接在公网路由转发的原因)
所以靠 NAT 转换,把重复的内网 IP 换成独一无二的公网 IP 去互联网通信
NAT 解决 IP 地址不足,体现在哪里
- 私有 IP 地址:可以全世界重复使用
- 公网 IP 地址:全世界必须唯一,不能重复
所以大量主机可以重复使用私有内网 IP,经过NAT 转换之后,一整个局域网内所有设备,共用同一个公网 IP 去访问外网 。大大节约 IP 资源,
补充:
- 运营商掌控上网权限 家里所有上网报文,全部先经过运营商机房路由器。
- 欠费:运营商直接丢弃你的报文,不让转发到公网,就上不了网。
- 运营商可以决定你的数据包能不能去往互联网。
- 报文会经过运营商设备,明文数据会被看到 你的数据包全部过运营商机房,所以如果携带有有些"敏感"信息就不能成功转到
长城技术:代指网络审查、过滤,运营商可以拦截、丢弃特定报文。
相关概念:
局域网,内网,子网不用太过于纠结,可以当作一个东西
LAN(局域网)LAN口的值就是网关
WAN(广域网)WAN口的值可能是公网ip,也可能是运营商二级 NAT后的值(私网ip)
网关:是一台具体设备的 IP 地址(路由器 LAN 口的值),是一个实实在在的主机 IP(192.168.1.1)
网络号:是一个网段的编号,代表一整个局域网,不是一台设备(192.168.1.0)
尝试理解公网
真实的网络结构非常复杂,即涉及到划分公网 IP 的组织,ICANN,还要在全球范围内进行区域划分, 比如亚太,北美,欧洲等,又要考虑各个国家内部的 ISP 代理,整体拓扑非常复杂,我们简化所有过程,简单理解公网即可

把公网类比成全世界城市交通网:
- 最上层:俄罗斯、美国、英国等国家,各自占一个大网段(如
1.0.0.0/8),国家之间靠国际骨干网相连,路由器跑 BGP 路由协议,互相交换全网路由信息。 - 国家内部:再拆分各个省份(陕西
5.1.0.0/16、河南5.2.0.0/16、浙江5.3.0.0/16),国内骨干网互通。 - 省份内部:再拆各个城市(西安
5.1.16.0/20、延安5.1.32.0/20)。 - 城市下面才是普通家庭 / 公司内网。
公网 IP 每一段,层层划分:国家→省份→城市,每一级路由器都有路由表,知道某个网段该往哪个接口转发。
问题 1:国外俄罗斯要访问目标地址:5.1.16.X
完整流程:
- 俄罗斯主机发包,源是俄罗斯公网 IP,目标
5.1.16.X。本机掩码运算,发现目标不在本国网段,交给本国网关。 - 俄罗斯路由器查路由表:
5.0.0.0/8属于中国,把数据包转发到国际骨干网,送到中国的国家级路由器。 - 中国骨干路由器:目标
5.1.16.X属于5.1.0.0/16(陕西省),转发给陕西省的出口路由器。 - 陕西省路由器:目标
5.1.16.X属于西安网段5.1.16.0/20,转发给西安的出入口路由器。 - 西安路由器继续转发,把数据包送到
5.1.16.X这台主机。
全程:IP 源目 IP 不改变,没有 NAT,只有每一跳二层 MAC 帧头不断更换。 回程报文原路返回。
问题 2:河北的报文,发往5.1.16.X
- 河北主机发包,目标
5.1.16.X,判断不在本网段,交给河北网关。 - 河北路由器查路由:目标网段
5.1.16.0/20属于陕西5.1.0.0/16,交给国内骨干网。 - 国内骨干网转发给陕西省路由器。
- 陕西路由器再交给西安路由器。
- 最终送达主机
5.1.16.X。
关键点:每一级路由器只看目标 IP 的网络号 ,查表选择下一跳,不需要知道目标主机具体在哪条线下。
路由
- 路由表可以使用 route 命令查看
- 如果目的 IP 命中了路由表,就直接转发即可;
- 路由表里的default(缺省路由 / 默认路由)条目 : 包含下一跳网关地址 和本机发送接口。当数据包的目标 IP,和路由表里上面所有条目全都匹配不上的时候,就走这条缺省路由,从指定接口,把包发给这个下一跳网关。

- 路由表的 Destination 是目的网络地址,Genmask 是子网掩码,Gateway 是下一跳地址,Iface 是发送接口,Flags 中的 U 标志表示此条目有效 (可以禁用某些条目),G 标志表示此条目的下一跳地址是某个路由器的地址,没有 G 标志的条目表示目的网络地址是与本机接口直接相连的网络,不必经路由器转发;
转发过程例 1: 如果要发送的数据包的目的地址是 192.168.56.3
- 跟第一行的子网掩码做与运算得到 192.168.56.0,与第一行的目的网络地址不符
- 再跟第二行的子网掩码做与运算得到 192.168.56.0,正是第二行的目的网络地址,因此从 eth1 接口发送出去;
转发过程例 2: 如果要发送的数据包的目的地址是 202.10.1.2
- 依次和路由表前几项进行对比,发现都不匹配;
- 按缺省路由条目,从 eth0 接口发出去,发往 192.168.10.1 路由器;
- 由 192.168.10.1 路由器根据它的路由表决定下一跳地址;
TCP的16 位标识 ID,**3 位标志,**13 位片偏移

- 16 位标识 ID :同一个大 IP 数据报拆分出来的所有分片,拥有相同标识 ID;接收方靠这个 ID,把一堆分片识别成属于同一个原始报文。
- 3 位标志
- 最高位保留;
- DF 位:不分片位 。DF=1,路由器禁止分片,包太大直接丢弃,返回 ICMP;
- MF 位:更多分片位。
MF=1:后面还有分片;MF=0:这是最后一片。
- 13 位片偏移 :代表本片数据,相对于原始报文数据开头的偏移(以8 字节为单位)。接收方根据偏移,把分片按顺序拼回完整数据。
核心结论:IP 报文可以进行分片与重组 。 重要:只要任意一个分片丢失,整个原始报文直接作废,全部重传。接收方不会上交残缺数据,只会等待全部分片,缺一片就组装失败。
MTU、MSS,为什么会分片
- MTU(链路层最大传输单元) :数据链路层一帧最多能承载的数据字节,以太网默认 1500 字节。
- MSS(最大段大小) :TCP 传给 IP 层的数据部分(不含报头)最大长度。
MSS = MTU − IP头部(20) − TCP头部(20) = 1460字节
MSS 只属于 TCP,和 UDP 无关。UDP 没有 MSS 概念。
分片发生过程
传输层给网络层交出大数据包,IP 总长度 > MTU (1500) ,路由器就把这个 IP 报文切割成多个分片,一个个转发。 分片是网络层 (IP) 被迫做的事情,不是主流,不推荐(后面会再讲为什么不是主流)。
分片坏处:只要丢任意一片,整个报文报废,丢包概率大幅上升。
结论:过多分片,会导致丢包概率变大。
怎么减少分片?
分片发生在 IP 层;但避免分片的工作,交给传输层(TCP)做。(减少分片的本质在传输层) TCP 知道 MSS,每次交给 IP 的数据不超过 MSS (1460),加上 IP 头 + TCP 头,整个 IP 报文刚好 1500,就不会触发 IP 分片
UDP 没有 MSS 机制,UDP 发大数据,直接交给 IP,就会触发 IP 分片。
为什么滑动窗口不一次性发大报文,要多次发呢?
所以现在看来主要原因就是为了避免IP分片,保证可靠性,二来就是如果有一段丢了,只重传丢失那一小段,不用重传全部,节省开销
怎么做到IP 分片识别、收全判断、组装?
1.怎么判断一个 IP 包是不是分片报文
两种情况代表这是分片: a. MF(更多分片)=1:后面还有别的分片,肯定是分片包。
b. MF=0,但是片偏移 > 0 :说明这是最后一片分片,属于分片。
那什么是原始完整不分片的包:
MF=0 && 片偏移=0。两个条件同时满足,代表没有被分片。
2、接收方怎么把分片归为一组,怎么判断是否全部收齐
a. 16 位标识 ID 相同的分片,说明属于同一个原始大报文,能够放到一组。
b. 判断有没有收全: 必须收到MF=0 的那一片(最后分片)。 只有拿到最后一片,才知道整个报文总长度。 三种收不全丢包场景:
- 第一片丢失(片偏移 = 0 的分片丢了)
- 中间某一片丢失(不太好判断,看第3步组装的过程)
- 结尾分片丢失(MF=0 那片丢了)
只要任意一片没到,接收方就组装失败,不会上交上层,等待超时直接全部丢
3.组装(重组)过程
- 同一标识 ID 的全部分片,按照片偏移从小到大升序排序。
- 校验连续:
当前分片片偏移 + 当前分片数据长度(按8字节单位) = 下一片的片偏移。(如果不连续不就相当于中间一片丢了吗) - 拼接到一起,还原完整 IP 数据,交给上层传输层。
eg:
注意:片偏移单位 = 8 字节
原始 IP 数据:4000 字节,被路由器分成 3 片:
| 分片 | 片偏移 (8 字节为单位) | 数据字节数 | MF 标志 |
|---|---|---|---|
| 第 1 片 | 0 | 1480 | 1(还有分片) |
| 第 2 片 | 185 | 1480 | 1(还有分片) |
| 第 3 片 | 370 | 1040 | 0(最后一片) |
换算:1480 ÷ 8 = 185
- 第 1 片片偏移 0,数据占 185 个 8 字节单元。
0 + 185 = 185→ 正好等于第 2 片的片偏移 185,连续。- 第 2 片 1480 字节,同样 185 个单元;
185+185 = 370→ 和第 3 片片偏移 370 对上。
接收方重组步骤
- 三个分片 ID 相同,把它们拿出来,按片偏移从小到大排:0 →185 →370。
- 校验连续:
0+185 =185✔185+185 =370✔是连续的 - 片偏移为0的第1片收到,加上连续所以中间也没有丢失,最后收到 MF=0 的最后一片,确认全部收齐。
- 直接把三段数据拼接,恢复 4000 字节完整数据,上交 TCP/UDP
丢包场景
如果第 2 片丢了: 收到分片:0、370。 0+185 !=370,发现中间缺口。收不到全部分片,直接全部丢弃,不交给上层。
MSS补充:
TCP 希望发送报文段的时候,尽量避免在网络层发生 IP 分片 ,接收方也同样要保障分片处理的问题。因此 TCP 在三次握手阶段,双方会互相通告自己的 MSS ,最终取两者的最小值作为连接的 MSS。(所以一般就不会分片,即不是主流)
握手阶段就协商确定 MSS,发送方按照这个 MSS 填充数据,绝大多数情况下就不会触发 IP 分片,大大减少分片发生的概率。
注意:路径上的 MTU 是有可能动态变化的,但不影响 MSS 的基础逻辑,上层 TCP 会基于协商好的 MSS 控制报文大小。