🧩 IP分片和重组:数据包的"拆装游戏"

93 阅读6分钟

知识点编号:025
难度等级:⭐⭐(掌握)
面试频率:🔥🔥🔥


🎯 一句话总结

IP分片就像快递太大装不下,拆成小包裹分别寄,到了再拼起来!📦


🤔 为什么需要分片?

问题:
数据包太大 > MTU(最大传输单元)
无法一次发送!

示例:
数据:5000字节
MTU:1500字节
5000 > 1500  发不了!

解决:
把5000字节拆成4个包:
包1:1500字节
包2:1500字节
包3:1500字节
包4:500字节

生活比喻:
你要寄一个大箱子
快递:太大了,装不下车!
你:那我拆成4个小箱子
快递:OK!✓

📏 MTU(最大传输单元)

MTU:Maximum Transmission Unit
一个数据包的最大大小

常见MTU值:
- 以太网:1500字节(最常见)
- PPPoE:1492字节
- VPN:通常更小
- 环回接口:65535字节

计算可用空间:
MTU = 1500字节
- IP头部:20字节
- TCP头部:20字节
= MSS:1460字节(最大段大小)

MSS(Maximum Segment Size):
TCP数据的最大大小
MSS = MTU - IP头 - TCP头

🔪 IP分片过程

IPv4分片

示例:发送3000字节数据,MTU=1500

原始数据包:
+--------+--------+
| IP头20 | 数据3000 |
+--------+--------+
总长度:3020字节 > MTU(1500) ❌

分片后:
片1:
+--------+--------+
| IP头20 | 数据1480 | 
+--------+--------+
总长度:1500字节 ✓
标识:12345
偏移:0
MF:1(还有更多片)

片2:
+--------+--------+
| IP头20 | 数据1480 |
+--------+--------+
总长度:1500字节 ✓
标识:12345(相同)
偏移:185(1480/8)
MF:1(还有更多片)

片3:
+--------+--------+
| IP头20 | 数据40  |
+--------+--------+
总长度:60字节 ✓
标识:12345(相同)
偏移:370(2960/8)
MF:0(最后一片)

关键字段:
- 标识(ID):相同ID表示同一个包
- 偏移(Offset):数据在原包中的位置(单位:8字节)
- MF标志:More Fragments,是否还有更多片

IPv6分片

IPv6不同:
- 路由器不分片!
- 只有源节点可以分片
- 使用分片扩展头部

优势:
- 路由器转发更快
- 避免分片带来的性能问题

源节点分片过程:
1. 通过路径MTU发现找到最小MTU
2. 在源节点分片
3. 路由器只转发,不分片

🧩 重组过程

接收方收到分片:

步骤1:检查标识(ID)
- 相同ID的片属于同一个包

步骤2:按偏移排序
- 偏移0:第一片
- 偏移185:第二片
- 偏移370:第三片

步骤3:检查是否收全
- 检查MF标志
- MF=0表示最后一片

步骤4:重组
- 按偏移拼接数据
- 恢复原始数据包

步骤5:交付
- 交给上层协议(TCP/UDP)

超时:
如果60秒内没收全,丢弃所有片

⚠️ 分片的问题

问题1:性能下降

分片导致:
- 路由器处理慢(要分片)
- 网络负担重(多个包)
- 丢包风险大(丢一片,全部重传)

示例:
原本:1个包
分片:3个包
丢包概率:P → 3P

问题2:防火墙问题

防火墙只看第一片:
- 第一片有端口号
- 后续片没有端口号
- 防火墙可能拒绝后续片

结果:无法重组,通信失败

问题3:安全问题

分片攻击:
- Ping of Death:发送超大ping包
- Teardrop:重叠的分片
- Tiny Fragment:超小的第一片

防御:
- 限制分片大小
- 检查重叠
- 丢弃异常分片

💻 Java代码查看MTU

import java.net.*;
import java.util.*;

public class MTUChecker {
    
    public static void main(String[] args) {
        try {
            // 获取所有网络接口
            Enumeration<NetworkInterface> interfaces = 
                NetworkInterface.getNetworkInterfaces();
            
            while (interfaces.hasMoreElements()) {
                NetworkInterface ni = interfaces.nextElement();
                
                System.out.println("\n接口:" + ni.getName());
                System.out.println("  显示名:" + ni.getDisplayName());
                System.out.println("  MTU:" + ni.getMTU() + " 字节");
                
                // 获取IP地址
                Enumeration<InetAddress> addresses = ni.getInetAddresses();
                while (addresses.hasMoreElements()) {
                    InetAddress addr = addresses.nextElement();
                    System.out.println("  地址:" + addr.getHostAddress());
                }
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

// 输出示例:
// 接口:eth0
//   显示名:Ethernet
//   MTU:1500 字节
//   地址:192.168.1.10

🔧 避免分片的方法

方法1:设置DF标志

DF(Don't Fragment):不分片标志

设置DF=1:
- 如果包太大,不分片
- 直接丢弃,返回ICMP错误
- "Fragmentation Needed"

用途:
- 路径MTU发现(PMTUD)
- 避免分片带来的问题

方法2:路径MTU发现

PMTUD(Path MTU Discovery):

过程:
1. 设置DF=1,发送大包
2. 如果路由器MTU小,返回ICMP错误
3. 收到错误,减小包大小
4. 重复,直到找到最小MTU
5. 按最小MTU发送,避免分片

优点:
- 找到最优MTU
- 避免分片
- 提高性能

方法3:TCP MSS协商

TCP建立连接时:
客户端:我的MSS=1460
服务器:我的MSS=1460

双方使用最小的MSS
确保TCP段不会太大
避免IP层分片

Java设置MSS:
// 无法直接设置MSS
// 由TCP自动协商
// 基于接口MTU计算

🐛 常见面试题

Q1:什么是IP分片?为什么需要分片?

答案:

IP分片是将大的IP数据包拆分成小包的过程。

原因:
1. MTU限制
   - 链路层有最大传输单元(MTU)
   - 以太网MTU通常是1500字节
   - 超过MTU无法传输

2. 异构网络
   - 不同网络MTU不同
   - 需要适应最小MTU

过程:
- 源节点或路由器分片
- 每片有相同标识(ID)
- 偏移字段指示位置
- MF标志表示是否还有更多片
- 目标主机重组

问题:
- 性能下降
- 丢包风险增加
- 防火墙可能拒绝

避免方法:
- 路径MTU发现
- TCP MSS协商
- 应用层控制数据大小

Q2:IPv4和IPv6的分片有什么区别?

答案:

IPv4分片:
- 源节点可以分片
- 路由器也可以分片
- 任何节点都可以分片

IPv6分片:
- 只有源节点可以分片
- 路由器不能分片
- 如果包太大,返回ICMP错误

原因:
IPv6设计目标:
- 简化路由器处理
- 提高转发效率
- 分片由源节点负责

优势:
- 路由器更快
- 网络性能更好
- 迫使应用层优化

实现:
IPv6源节点:
1. 路径MTU发现
2. 找到最小MTU
3. 按最小MTU分片
4. 使用分片扩展头部

🎓 总结

IP分片的关键点:

  1. 原因:包太大 > MTU
  2. 过程:拆分 → 传输 → 重组
  3. 字段:标识、偏移、MF标志
  4. 问题:性能下降、丢包风险
  5. 避免:PMTUD、MSS协商

记忆口诀

包大MTU装不下 📦
分片拆开来传达 ✂️
标识偏移加MF 🏷️
目标重组恢复它 🧩
最好避免别分片 ⚠️

文档创建时间:2025-10-31