Appearance
子网划分与 CIDR
概念
子网划分(subnetting)是把 IPv4 地址里原本的"主机号"再借出若干位,当作"子网号",从而把一个物理网络切成若干个逻辑小网络;CIDR(Classless Inter-Domain Routing,无类域间路由)则是直接取消"分类地址"这套粒度粗糙的规则,改用"任意长度的前缀 /n"来切分地址块,并允许把若干个连续的小地址块合并成一个大块对外通告。
一句话说清它们的关系:子网划分是"往下切"(一个网络切成多个子网),CIDR 是"往上合"(多个小网络合成一个大块);两者用的是同一把尺子——前缀长度 /n。
★ 本章要解决的三件事:
| 问题 | 对应内容 |
|---|---|
| 一个网络怎么切成多个 | 借位 → 子网掩码 → 网络地址 / 广播地址 |
| 多个网络怎么合成一个 | CIDR 聚合(路由汇聚)+ 对齐条件 |
| 路由表里有好几条都匹配怎么办 | 最长前缀匹配 |
★ 为什么要有子网划分(直接动因):上一章(net/30-ip.md)指出分类地址"粒度太粗"——A 类给 1677 万个地址用不完、C 类只给 254 个不够用,中间没有合适的档位。 子网划分允许"内部再分细":对外仍是一个网络,对内却是十几个小网——既让单位内部能按部门/楼层/机房分网,又让路由器的工作量不增加(对外只需一条路由条目)。
★ 为什么还要有 CIDR(第二个动因):子网划分只解决了"切得开",没解决"路由表爆掉"——互联网上百万个 C 类网络,每个都要一条路由条目,骨干路由器扛不住。 CIDR 用"聚合"把连续的小块合并成一个大前缀,一条条目顶几百条——这才是把路由表压下来的关键。
与上一章的衔接:上一章讲"地址怎么编"(32 位、分类、私有块),本章讲"地址怎么分配与聚合"(借位、掩码、前缀);而"私有地址三块"将在下一章 net/32-nat.md 里被 NAT 用起来。
原理
一、从"两级编址"到"三级编址"
分类地址是"两级编址":地址 = 网络号 + 主机号,分界点由类别决定(A 类切在第 8 位、B 类第 16 位、C 类第 24 位)。
子网划分把它变成"三级编址":
text
分类地址 (两级): |<-------- 32 位 -------->|
| 网络号 | 主机号 |
^
分界点由"类别"决定 (A/B/C 固定)
子网划分 (三级): |<-------- 32 位 -------->|
| 网络号 | 子网号 | 主机号 |
^
从"主机号"里借出来的位
★ 对外: 仍然只有"网络号", 路由器看不见子网号 -> 路由表不变大
★ 对内: 本单位自己用"子网号"再分一次 -> 内部能分部门★ 借位的账(C 类地址 /24 借 n 位,这是本章的主战场):
| 借位 n | 新前缀 | 子网掩码 | 子网数 | 块大小 | 每子网地址数 | 可用主机数 |
|---|---|---|---|---|---|---|
| 0 | /24 | 255.255.255.0 | 1 | 256 | 256 | 254 |
| 1 | /25 | 255.255.255.128 | 2 | 128 | 128 | 126 |
| 2 | /26 | 255.255.255.192 | 4 | 64 | 64 | 62 |
| 3 | /27 | 255.255.255.224 | 8 | 32 | 32 | 30 |
| 4 | /28 | 255.255.255.240 | 16 | 16 | 16 | 14 |
| 5 | /29 | 255.255.255.248 | 32 | 8 | 8 | 6 |
| 6 | /30 | 255.255.255.252 | 64 | 4 | 4 | 2 |
| 7 | /31 | 255.255.255.254 | 128 | 2 | 2 | 0(点对点链路,RFC 3021) |
| 8 | /32 | 255.255.255.255 | 256 | 1 | 1 | 0(主机路由) |
⚠️ 三个必须记住的对应关系:① 借 n 位 → 子网数
;② 每子网地址数 = 块大小 ;③ 可用主机数 = 块大小 − 2(去掉网络地址与广播地址)。 借的位越多,子网越多、每个越小——这就是"粒度"的调节旋钮。
⚠️ 早期的一个历史争议:RFC 950 时代规定"子网号不能全 0、也不能全 1"(因为全 0 子网号与"本网络"的表达方式撞车)——若按这个老规矩,/26 的 4 个子网里只有中间 2 个能用,可用主机数要按
个子网算。 现代标准(RFC 1812 起)已允许全 0 与全 1 子网号,408 的通行口径也是"全部可用"——但若题目明确写"按旧规定",就老老实实减 2。
二、子网掩码:把"切在哪"变成一次按位与
子网掩码(subnet mask)是一个"前面连续若干个 1、后面全 0"的 32 位数:1 的位置标记"网络号 + 子网号",0 的位置标记"主机号"。
★ 核心恒等式(一切子网计算的起点):
★ 四个由掩码直接导出的量:
| 量 | 求法 | 锚点(192.168.10.130/26) |
|---|---|---|
| 掩码 | 前缀 n → 前 n 位为 1 | 255.255.255.192 |
| 块大小 | ||
| 网络地址 | IP AND 掩码 | 192.168.10.128 |
| 广播地址 | 网络地址 OR (NOT 掩码) = 网络地址 + 块大小 − 1 | 192.168.10.191 |
| 可用范围 | 网络地址 + 1 至 广播地址 − 1 | 192.168.10.129 ~ 192.168.10.190(62 个) |
★ 锚点的手算过程(一步步来):
text
已知: IP = 192.168.10.130, 前缀 = /26
第①步 写掩码
/26 -> 前 26 位是 1 -> 第 4 字节前 2 位是 1, 后 6 位是 0
第 4 字节 = 1100 0000 = 192
掩码 = 255.255.255.192
第②步 算块大小
块大小 = 2^(32-26) = 2^6 = 64
★ 也等于 "256 - 掩码第 4 字节" = 256 - 192 = 64
第③步 找网络地址 (IP AND 掩码)
第 4 字节: 130 = 1000 0010
192 = 1100 0000
AND -> 1000 0000 = 128
网络地址 = 192.168.10.128
★ 速算法: 130 落在哪个 64 的倍数区间?
64 x 2 = 128 <= 130 < 64 x 3 = 192 -> 网络地址第 4 字节 = 128
第④步 找广播地址
广播地址 = 网络地址 + 块大小 - 1 = 128 + 64 - 1 = 191
广播地址 = 192.168.10.191
第⑤步 可用范围与数量
可用 = 129 ~ 190, 共 190 - 129 + 1 = 62 个
★ 验算: 块大小 64 - 2 = 62 一致⚠️ "块大小"是本章最好的心算工具:它把一个抽象的掩码变成"每 64 个地址一段"——只要把 IP 的最后一个字节除以块大小向下取整、再乘回块大小,就是网络地址(锚点里
余 2, )。 这比写二进制按位与快得多,考试时能省大量时间。
三、切一个网络:192.168.1.0/24 借 3 位 → 8 个 /27
★ 锚点(本章定死):把 192.168.1.0/24 借 3 位,切成 8 个 /27 子网。
text
借位前: 192.168.1.0000 0000 / 24
网络号 24 位 = 192.168.1 | 主机号 8 位 = xxxxxxxx
借 3 位: 192.168.1.000 xxxxx / 27
网络号 24 位 | 子网号 3 位 | 主机号 5 位
★ 子网号 3 位 -> 2^3 = 8 个子网
★ 主机号 5 位 -> 2^5 = 32 个地址 -> 30 台可用
★ 块大小 = 2^5 = 32
8 个子网的地址块 (第 4 字节按 32 一段切):
┌──────┬──────────────┬─────────────────┬──────────────┬──────────────────┐
│ 子网 │ 网络地址 │ 可用范围 │ 广播地址 │ 子网号(二进制) │
├──────┼──────────────┼─────────────────┼──────────────┼──────────────────┤
│ 1 │ .0 │ .1 ~ .30 │ .31 │ 000 │
│ 2 │ .32 │ .33 ~ .62 │ .63 │ 001 │
│ 3 │ .64 │ .65 ~ .94 │ .95 │ 010 │
│ 4 │ .96 │ .97 ~ .126 │ .127 │ 011 │
│ 5 │ .128 │ .129 ~ .158 │ .159 │ 100 │
│ 6 │ .160 │ .161 ~ .190 │ .191 │ 101 │
│ 7 │ .192 │ .193 ~ .222 │ .223 │ 110 │
│ 8 │ .224 │ .225 ~ .254 │ .255 │ 111 │
└──────┴──────────────┴─────────────────┴──────────────┴──────────────────┘
★ 第 1 字节 ~ 第 3 字节始终是 192.168.1, 只有第 4 字节在变
★ 8 x 32 = 256 -> 恰好铺满整个 /24, 无空洞⚠️ 掩码统一为
255.255.255.224:8 个子网用的是同一个掩码——因为借的位数相同。 这就是"定长子网划分"(FLSM,Fixed Length Subnet Mask);若各子网掩码不同,就是 VLSM(见本节第五部分)。
⚠️ 一个常考的细节:
192.168.1.0/27这个网络本身(第 1 个子网)现在也能用了——"全 0 子网号"在旧规定里被禁止,现代标准允许;同理192.168.1.224/27(全 1 子网号)也能用。 所以 8 个子网一个不少。
四、CIDR:取消分类,改用前缀;并用聚合压平路由表
CIDR 把"分类地址"这个概念直接删掉:地址写成 a.b.c.d/n,n 可以取 0 到 32 之间的任何值——不再有"必须 /8、/16、/24"这回事。
★ CIDR 的两半功能:
| 功能 | 方向 | 说明 |
|---|---|---|
| 分配 | 往下切 | 可以用 /22、/26、/19 这种任意前缀把地址块分出去——一个单位需要 500 个地址,就给一个 /23(510 个可用),不必给整个 B 类 |
| 聚合 | 往上合 | 把若干个连续的同前缀长度的小块,合并成一个更大的前缀对外通告(路由汇聚 / route summarization) |
★ 聚合锚点:4 个连续的 /24 → 1 个 /22
text
200.10.0.0/24 -> 第 3 字节 = 0 = 000000 00
200.10.1.0/24 -> 第 3 字节 = 1 = 000000 01
200.10.2.0/24 -> 第 3 字节 = 2 = 000000 10
200.10.3.0/24 -> 第 3 字节 = 3 = 000000 11
^^^^^^
前 6 位完全相同 (都是 000000)
★ 第 3 字节的"可变位"只有低 2 位 -> 前缀 = 24 - 2 = 22
聚合结果 = 200.10.0.0/22★ 聚合成不成立的判据(对齐条件):
★ 反例(考试爱考):200.10.1.0/24 与 200.10.2.0/24 不能聚合成 /23——因为 /23 要求起始地址的第 3 字节是偶数,而 1 是奇数:1.0 属于 200.10.0.0/23、2.0 属于 200.10.2.0/23,两者不在同一个 /23 里。 若硬要覆盖这两个块,最小合法前缀是 /22,但那会"多覆盖" 200.10.0.0/24 与 200.10.3.0/24——多覆盖的后果是流量被错误地吸引过来,这可能是故障,也可能是攻击(路由劫持)。
★ 最长前缀匹配(Longest Prefix Match)是 CIDR 时代的转发规则:
路由器把目的 IP 与路由表中每一条目逐条做"前缀比较",凡前缀能匹配上的都算候选;在全部候选中选"前缀最长(最具体)"的那一条转发。
★ 锚点(路由表四条,看它选谁):
| 路由条目 | 前缀长度 | 目标 200.10.1.200 是否匹配 |
|---|---|---|
0.0.0.0/0(默认路由) | 0 | 匹配(任何地址都匹配) |
200.10.0.0/16 | 16 | 匹配(前 16 位相同) |
200.10.1.0/24 | 24 | 匹配(前 24 位相同) |
200.10.1.128/25 | 25 | 匹配(200 在 128 ~ 255 之间) |
结论:四条全匹配,取最长 → /25 那条。 /0 那条永远排在最后(是"兜底",不是"最优")。
⚠️ "默认路由
/0为什么不打败所有条目":因为它匹配"所有"的同时也是最短的——最长前缀匹配的排序依据是"具体程度",不是"匹配的先后"。 这条规则是 CIDR 能工作的前提:没有它,聚合出来的大块会把小块全部遮蔽。
五、VLSM:一个地址块,按需切成不同大小
VLSM(Variable Length Subnet Mask,可变长子网掩码)允许在同一个地址块内,用不同的前缀长度切出不同大小的子网——因为各部门的需求本来就不一样,用同一把尺子切纯属浪费。
★ 锚点:地址块 192.168.1.0/24,四个部门需求 60 / 30 / 20 / 10 台
text
第①步 把需求换算成"需要的地址数" (主机数 + 2)
60 台 -> 需 62 -> 2^6 = 64 够 -> 主机号 6 位 -> /26
30 台 -> 需 32 -> 2^5 = 32 够 -> 主机号 5 位 -> /27
20 台 -> 需 22 -> 2^5 = 32 够 -> 主机号 5 位 -> /27
10 台 -> 需 12 -> 2^4 = 16 够 -> 主机号 4 位 -> /28
第②步 按"需求从大到小"依次分配 (大块对齐要求更严, 必须先占位)
┌──────┬──────┬────────┬──────────────┬────────────────┬──────────────┐
│ 部门 │ 需求 │ 前缀 │ 网络地址 │ 可用范围 │ 广播地址 │
├──────┼──────┼────────┼──────────────┼────────────────┼──────────────┤
│ A │ 60台 │ /26 │ 192.168.1.0 │ .1 ~ .62 │ .63 │
│ B │ 30台 │ /27 │ 192.168.1.64 │ .65 ~ .94 │ .95 │
│ C │ 20台 │ /27 │ 192.168.1.96 │ .97 ~ .126 │ .127 │
│ D │ 10台 │ /28 │ 192.168.1.128│ .129 ~ .142 │ .143 │
└──────┴──────┴────────┴──────────────┴────────────────┴──────────────┘
剩余未分配: 192.168.1.144 ~ 192.168.1.255 (112 个地址, 可留给后续扩容)⚠️ VLSM 为什么必须"从大到小":大块(如
/26)要求起始地址是 64 的整数倍,可选位置本来就少;先切小块会把整数倍的落脚点占掉或打碎——锚点里若先把192.168.1.0/28分给 D,/26就只能落在.64、.128、.192三处,.0那一段被 D 占住后,一个/24里剩下的可落脚点从 4 个变成 3 个,地址利用率下降。 这条"先大后小"是 VLSM 的标准做法。
⚠️ VLSM 与"子网划分"的关系不是并列而是包含:子网划分 = 用一个固定掩码切一次(FLSM);VLSM = 切多次、每次掩码可以不同。 今天的路由器与主机都支持 VLSM(因为转发只按"最长前缀匹配",本来就允许掩码不同);不支持 VLSM 的老协议(RIPv1、IGRP)必须全网同一个掩码——这也是 RIPv2 要加"掩码字段"的原因(见
net/33-routing.md)。
六、三个易混概念的界分
| 概念 | 切什么 | 谁决定 | 典型写法 |
|---|---|---|---|
| 分类地址 | 固定切在 8/16/24 位 | 首位模式(0 / 10 / 110) | 192.168.1.0(隐含 /24) |
| 子网划分 | 从主机号借位 | 单位自己定 | 192.168.1.0/26 |
| CIDR | 任意前缀 + 可聚合 | ISP / 骨干 | 200.10.0.0/22(= 4 个 /24) |
⚠️ 三者不是三个历史阶段,而是三层叠加:CIDR 从语法上包含了子网划分(
/26就是 CIDR 记法)——区别只在用途:/26通常写在"内部细切"的场合,/22通常写在"对外聚合通告"的场合。 题目里看到/n就直接用 CIDR 那套算,不必再问"这是哪一类"。
示例
例 1:C 实现——掩码、网络地址、广播地址与子网切分
参数:锚点
192.168.10.130/26;子网切分用192.168.1.0/24借 3 位;聚合用200.10.0.0/24~200.10.3.0/24。
#include <stdio.h>
/* ---- 32 位地址工具 ---- */
unsigned ip(unsigned a, unsigned b, unsigned c, unsigned d) {
return (a << 24) | (b << 16) | (c << 8) | d;
}
void show(unsigned x) {
printf("%u.%u.%u.%u", (x >> 24) & 0xFFu, (x >> 16) & 0xFFu,
(x >> 8) & 0xFFu, x & 0xFFu);
}
/* 前缀 n -> 掩码: 前 n 位是 1 */
unsigned mask_of(int n) {
return (n == 0) ? 0u : (0xFFFFFFFFu << (32 - n));
}
int main(void) {
int i;
unsigned m, net, bcast, size, base;
printf("① 掩码速查 (C 类地址借位)\n");
for (i = 24; i <= 30; i++) {
m = mask_of(i);
size = 1u << (32 - i);
printf(" /%-2d 掩码 = ", i);
show(m);
printf(" 块大小 = %2u 可用主机 = %u\n", size, size - 2);
}
printf("\n");
printf("② 锚点: 192.168.10.130/26\n");
m = mask_of(26);
net = ip(192, 168, 10, 130) & m;
bcast = net | ~m;
size = 1u << (32 - 26);
printf(" 掩码 = ");
show(m);
printf(" 块大小 = %u\n", size);
printf(" 网络地址 = ");
show(net);
printf(" 广播地址 = ");
show(bcast);
printf("\n");
printf(" 可用范围 = ");
show(net + 1);
printf(" ~ ");
show(bcast - 1);
printf(" 可用主机数 = %u\n", size - 2);
printf("\n");
printf("③ 192.168.1.0/24 借 3 位 -> 8 个 /27 子网\n");
for (i = 0; i < 8; i++) {
base = ip(192, 168, 1, 0) + (unsigned)(i * 32);
printf(" 子网 %d: ", i + 1);
show(base);
printf("/27");
printf(" 广播 = ");
show(base + 31);
printf(" 可用 = ");
show(base + 1);
printf(" ~ ");
show(base + 30);
printf("\n");
}
printf("\n");
printf("④ CIDR 聚合: 200.10.0.0/24 ~ 200.10.3.0/24 -> /22\n");
for (i = 0; i < 4; i++) {
printf(" ");
show(ip(200, 10, (unsigned)i, 0));
printf("/24\n");
}
m = mask_of(22);
printf(" 聚合结果 = ");
show(ip(200, 10, 0, 0) & m);
printf("/22 掩码 = ");
show(m);
printf(" 覆盖 %u 个 /24\n", 1u << 2);
printf("\n");
printf("⑤ 位数账\n");
printf(" /26 块大小 = 2^%d = %u ; /22 块大小 = 2^%d = %u\n",
32 - 26, 1u << 6, 32 - 22, 1u << 10);
printf(" 一个 /24 借 3 位 -> 子网数 2^3 = %u\n", 1u << 3);
return 0;
}
c 本站为静态站,不提供在线运行;可复制到本地用 gcc / python 执行
预期输出:
① 掩码速查 (C 类地址借位)
/24 掩码 = 255.255.255.0 块大小 = 256 可用主机 = 254
/25 掩码 = 255.255.255.128 块大小 = 128 可用主机 = 126
/26 掩码 = 255.255.255.192 块大小 = 64 可用主机 = 62
/27 掩码 = 255.255.255.224 块大小 = 32 可用主机 = 30
/28 掩码 = 255.255.255.240 块大小 = 16 可用主机 = 14
/29 掩码 = 255.255.255.248 块大小 = 8 可用主机 = 6
/30 掩码 = 255.255.255.252 块大小 = 4 可用主机 = 2
② 锚点: 192.168.10.130/26
掩码 = 255.255.255.192 块大小 = 64
网络地址 = 192.168.10.128 广播地址 = 192.168.10.191
可用范围 = 192.168.10.129 ~ 192.168.10.190 可用主机数 = 62
③ 192.168.1.0/24 借 3 位 -> 8 个 /27 子网
子网 1: 192.168.1.0/27 广播 = 192.168.1.31 可用 = 192.168.1.1 ~ 192.168.1.30
子网 2: 192.168.1.32/27 广播 = 192.168.1.63 可用 = 192.168.1.33 ~ 192.168.1.62
子网 3: 192.168.1.64/27 广播 = 192.168.1.95 可用 = 192.168.1.65 ~ 192.168.1.94
子网 4: 192.168.1.96/27 广播 = 192.168.1.127 可用 = 192.168.1.97 ~ 192.168.1.126
子网 5: 192.168.1.128/27 广播 = 192.168.1.159 可用 = 192.168.1.129 ~ 192.168.1.158
子网 6: 192.168.1.160/27 广播 = 192.168.1.191 可用 = 192.168.1.161 ~ 192.168.1.190
子网 7: 192.168.1.192/27 广播 = 192.168.1.223 可用 = 192.168.1.193 ~ 192.168.1.222
子网 8: 192.168.1.224/27 广播 = 192.168.1.255 可用 = 192.168.1.225 ~ 192.168.1.254
④ CIDR 聚合: 200.10.0.0/24 ~ 200.10.3.0/24 -> /22
200.10.0.0/24
200.10.1.0/24
200.10.2.0/24
200.10.3.0/24
聚合结果 = 200.10.0.0/22 掩码 = 255.255.252.0 覆盖 4 个 /24
⑤ 位数账
/26 块大小 = 2^6 = 64 ; /22 块大小 = 2^10 = 1024
一个 /24 借 3 位 -> 子网数 2^3 = 8⚠️ 三点说明:
- 本机无 C 编译器:此段代码逐行人工审查,并用等价的 Python 实现实跑核对,输出逐字一致。
net | ~m求广播地址:掩码取反后"主机号"位全 1、网络号位全 0,或进网络地址就得到"网络号不变、主机号全 1"——这正是广播地址的定义。 等价写法是net + size - 1,两种都可以;注意~m在 C 里是 32 位无符号取反(m为unsigned int),不会带出符号位问题。1u << (32 - i)中的u不能省:当32 - i较大时(如/0),1 << 32在 32 位int上是未定义行为;加u后是"无符号移位",虽然<< 32仍不合法,但至少排除了"符号溢出"这一层。 代码里循环只到/30,移位最多 8 位,安全。
例 2:Python——借位表、锚点、聚合、最长前缀匹配与 VLSM
def pad(s, w):
"""按"显示宽度"补空格: 汉字算 2 列, 否则终端里对不齐"""
return s + ' ' * max(0, w - sum(2 if ord(c) > 0x2000 else 1 for c in s))
def ip(a, b, c, d):
return (a << 24) | (b << 16) | (c << 8) | d
def show(x):
return '%d.%d.%d.%d' % ((x >> 24) & 255, (x >> 16) & 255, (x >> 8) & 255, x & 255)
def mask_of(n):
return 0 if n == 0 else (0xFFFFFFFF << (32 - n)) & 0xFFFFFFFF
def describe(addr, n):
m = mask_of(n)
net = addr & m
size = 1 << (32 - n)
bcast = net + size - 1
return net, bcast, size
print('=== ① C 类地址借位表 (/24 借 n 位) ===')
print(' ' + pad('借位', 6) + pad('前缀', 8) + pad('掩码', 20) + pad('子网数', 10)
+ pad('块大小', 10) + '可用主机数')
for n in range(0, 9):
pre = 24 + n
m = mask_of(pre)
size = 1 << (32 - pre)
usable = size - 2 if size >= 2 else 0
print(' ' + pad(str(n), 6) + pad('/%d' % pre, 8) + pad(show(m), 20)
+ pad(str(2 ** n), 10) + pad(str(size), 10) + str(usable))
print(' ★ 借 n 位 -> 子网数 2^n; 每子网地址数 = 块大小 = 2^(8-n); 可用 = 块大小 - 2')
print()
print('=== ② 锚点: 192.168.10.130/26 ===')
addr, pre = ip(192, 168, 10, 130), 26
m = mask_of(pre)
net, bcast, size = describe(addr, pre)
print(' IP = %s' % show(addr))
print(' 掩码 = %s (前缀 /%d)' % (show(m), pre))
print(' 块大小 = 2^(32-%d) = %d (也 = 256 - 掩码末字节 = 256 - %d)'
% (pre, size, m & 255))
print(' 第 4 字节 = %d -> %d // %d = %d 余 %d -> 落在第 %d 段'
% (addr & 255, addr & 255, size, (addr & 255) // size, (addr & 255) % size,
(addr & 255) // size + 1))
print(' 网络地址 = %s (IP AND 掩码)' % show(net))
print(' 广播地址 = %s (网络地址 + 块大小 - 1)' % show(bcast))
print(' 可用范围 = %s ~ %s 共 %d 个' % (show(net + 1), show(bcast - 1), size - 2))
print(' ★ 块大小是最快的心算工具: 130 // 64 = 2, 2 x 64 = 128')
print()
print('=== ③ 192.168.1.0/24 借 3 位 -> 8 个 /27 子网 ===')
print(' ' + pad('子网', 6) + pad('网络地址', 18) + pad('可用范围', 34)
+ pad('广播地址', 18) + '子网号')
for i in range(8):
base = ip(192, 168, 1, 0) + i * 32
print(' ' + pad(str(i + 1), 6) + pad(show(base) + '/27', 18)
+ pad('%s ~ %s' % (show(base + 1), show(base + 30)), 34)
+ pad(show(base + 31), 18) + format(i, '03b'))
print(' ★ 8 x 32 = 256, 恰好铺满 /24; 掩码统一 255.255.255.224 (FLSM)')
print()
print('=== ④ CIDR 聚合与对齐条件 ===')
blocks = [ip(200, 10, i, 0) for i in range(4)]
print(' 待聚合: ' + ', '.join(show(b) + '/24' for b in blocks))
# 逐位比较, 找最长公共前缀
first = blocks[0]
common = 32
for b in blocks[1:]:
d = first ^ b
if d == 0:
continue
common = min(common, 32 - d.bit_length())
m = mask_of(common)
print(' 最长公共前缀 = /%d' % common)
print(' 聚合结果 = %s/%d 掩码 = %s 覆盖 %d 个原块'
% (show(first & m), common, show(m), 2 ** (32 - common) // 256))
print(' ★ 可聚合条件: 块数 = 2^k, 且起始地址是 2^k 个地址的整数倍')
print()
print('=== ⑤ 不能聚合的反例: 200.10.1.0/24 与 200.10.2.0/24 想合成 /23 ===')
a1, a2 = ip(200, 10, 1, 0), ip(200, 10, 2, 0)
d = a1 ^ a2
common = 32 - d.bit_length()
print(' 两块 XOR 的最高不同位 -> 最长公共前缀 = /%d' % common)
print(' 200.10.1.0/23 覆盖 %s ~ %s (不含 200.10.2.0)' % (show(mask_of(23) & a1), show((mask_of(23) & a1) + 511)))
print(' 200.10.2.0/23 覆盖 %s ~ %s (不含 200.10.1.0)' % (show(mask_of(23) & a2), show((mask_of(23) & a2) + 511)))
print(' ★ /23 要求第 3 字节是偶数: 1(奇) 与 2(偶) 分属不同 /23 -> 合不了')
print(' ★ 要覆盖这两块最小只能 /22, 但那会多覆盖 200.10.0.0/24 与 200.10.3.0/24')
print()
print('=== ⑥ 最长前缀匹配 ===')
table = [('0.0.0.0', 0, 'R0 默认路由'), ('200.10.0.0', 16, 'R1'),
('200.10.1.0', 24, 'R2'), ('200.10.1.128', 25, 'R3')]
dst = ip(200, 10, 1, 200)
print(' 目的地址 = %s' % show(dst))
hit = []
for base_s, pre, nh in table:
b = [int(x) for x in base_s.split('.')]
base = ip(b[0], b[1], b[2], b[3])
ok = (dst & mask_of(pre)) == base
if ok:
hit.append((pre, nh))
print(' ' + pad(base_s + '/%d' % pre, 20) + pad(nh, 16)
+ ('匹配' if ok else '不匹配'))
best = max(hit)
print(' 候选 %d 条, 取最长前缀 -> /%d, 下一跳 = %s' % (len(hit), best[0], best[1]))
print(' ★ 默认路由 /0 匹配所有, 但最短 -> 永远排在最后')
print()
print('=== ⑦ VLSM: 192.168.1.0/24 按 60/30/20/10 台分配 ===')
need = [('A', 60), ('B', 30), ('C', 20), ('D', 10)]
cursor = ip(192, 168, 1, 0)
print(' ' + pad('部门', 6) + pad('需求', 8) + pad('前缀', 8) + pad('网络地址', 18)
+ pad('可用范围', 34) + '广播地址')
for name, hosts in need:
pre = 32
while (1 << (32 - pre)) - 2 < hosts:
pre -= 1
size = 1 << (32 - pre)
net = cursor
print(' ' + pad(name, 6) + pad('%d台' % hosts, 8) + pad('/%d' % pre, 8)
+ pad(show(net), 18)
+ pad('%s ~ %s' % (show(net + 1), show(net + size - 2)), 34)
+ show(net + size - 1))
cursor += size
print(' 剩余未分配: %s ~ 192.168.1.255' % show(cursor))
print(' ★ 必须"需求从大到小"分配: 大块对齐要求更严, 先占位')
print()
print('=== ⑧ 三个易混概念的界分 ===')
for a, b in [('分类地址', '切在 8/16/24 位, 由首位模式决定'),
('子网划分', '从主机号借位, 由单位自己定 (FLSM)'),
('CIDR 聚合', '任意前缀 + 合并连续小块 (对外通告)')]:
print(' ' + pad(a, 14) + b)
print(' ★ 三者是叠加关系, 不是三个历史阶段')
python 本站为静态站,不提供在线运行;可复制到本地用 gcc / python 执行
输出对照(真实运行结果):
=== ① C 类地址借位表 (/24 借 n 位) ===
借位 前缀 掩码 子网数 块大小 可用主机数
0 /24 255.255.255.0 1 256 254
1 /25 255.255.255.128 2 128 126
2 /26 255.255.255.192 4 64 62
3 /27 255.255.255.224 8 32 30
4 /28 255.255.255.240 16 16 14
5 /29 255.255.255.248 32 8 6
6 /30 255.255.255.252 64 4 2
7 /31 255.255.255.254 128 2 0
8 /32 255.255.255.255 256 1 0
★ 借 n 位 -> 子网数 2^n; 每子网地址数 = 块大小 = 2^(8-n); 可用 = 块大小 - 2
=== ② 锚点: 192.168.10.130/26 ===
IP = 192.168.10.130
掩码 = 255.255.255.192 (前缀 /26)
块大小 = 2^(32-26) = 64 (也 = 256 - 掩码末字节 = 256 - 192)
第 4 字节 = 130 -> 130 // 64 = 2 余 2 -> 落在第 3 段
网络地址 = 192.168.10.128 (IP AND 掩码)
广播地址 = 192.168.10.191 (网络地址 + 块大小 - 1)
可用范围 = 192.168.10.129 ~ 192.168.10.190 共 62 个
★ 块大小是最快的心算工具: 130 // 64 = 2, 2 x 64 = 128
=== ③ 192.168.1.0/24 借 3 位 -> 8 个 /27 子网 ===
子网 网络地址 可用范围 广播地址 子网号
1 192.168.1.0/27 192.168.1.1 ~ 192.168.1.30 192.168.1.31 000
2 192.168.1.32/27 192.168.1.33 ~ 192.168.1.62 192.168.1.63 001
3 192.168.1.64/27 192.168.1.65 ~ 192.168.1.94 192.168.1.95 010
4 192.168.1.96/27 192.168.1.97 ~ 192.168.1.126 192.168.1.127 011
5 192.168.1.128/27 192.168.1.129 ~ 192.168.1.158 192.168.1.159 100
6 192.168.1.160/27 192.168.1.161 ~ 192.168.1.190 192.168.1.191 101
7 192.168.1.192/27 192.168.1.193 ~ 192.168.1.222 192.168.1.223 110
8 192.168.1.224/27 192.168.1.225 ~ 192.168.1.254 192.168.1.255 111
★ 8 x 32 = 256, 恰好铺满 /24; 掩码统一 255.255.255.224 (FLSM)
=== ④ CIDR 聚合与对齐条件 ===
待聚合: 200.10.0.0/24, 200.10.1.0/24, 200.10.2.0/24, 200.10.3.0/24
最长公共前缀 = /22
聚合结果 = 200.10.0.0/22 掩码 = 255.255.252.0 覆盖 4 个原块
★ 可聚合条件: 块数 = 2^k, 且起始地址是 2^k 个地址的整数倍
=== ⑤ 不能聚合的反例: 200.10.1.0/24 与 200.10.2.0/24 想合成 /23 ===
两块 XOR 的最高不同位 -> 最长公共前缀 = /22
200.10.1.0/23 覆盖 200.10.0.0 ~ 200.10.1.255 (不含 200.10.2.0)
200.10.2.0/23 覆盖 200.10.2.0 ~ 200.10.3.255 (不含 200.10.1.0)
★ /23 要求第 3 字节是偶数: 1(奇) 与 2(偶) 分属不同 /23 -> 合不了
★ 要覆盖这两块最小只能 /22, 但那会多覆盖 200.10.0.0/24 与 200.10.3.0/24
=== ⑥ 最长前缀匹配 ===
目的地址 = 200.10.1.200
0.0.0.0/0 R0 默认路由 匹配
200.10.0.0/16 R1 匹配
200.10.1.0/24 R2 匹配
200.10.1.128/25 R3 匹配
候选 4 条, 取最长前缀 -> /25, 下一跳 = R3
★ 默认路由 /0 匹配所有, 但最短 -> 永远排在最后
=== ⑦ VLSM: 192.168.1.0/24 按 60/30/20/10 台分配 ===
部门 需求 前缀 网络地址 可用范围 广播地址
A 60台 /26 192.168.1.0 192.168.1.1 ~ 192.168.1.62 192.168.1.63
B 30台 /27 192.168.1.64 192.168.1.65 ~ 192.168.1.94 192.168.1.95
C 20台 /27 192.168.1.96 192.168.1.97 ~ 192.168.1.126 192.168.1.127
D 10台 /28 192.168.1.128 192.168.1.129 ~ 192.168.1.142 192.168.1.143
剩余未分配: 192.168.1.144 ~ 192.168.1.255
★ 必须"需求从大到小"分配: 大块对齐要求更严, 先占位
=== ⑧ 三个易混概念的界分 ===
分类地址 切在 8/16/24 位, 由首位模式决定
子网划分 从主机号借位, 由单位自己定 (FLSM)
CIDR 聚合 任意前缀 + 合并连续小块 (对外通告)
★ 三者是叠加关系, 不是三个历史阶段八条结论:
- ★ 借位的三个对应:借 n 位 →
个子网、每子网 个地址(C 类)、可用 台——/26是 4 个子网、62 台;/27是 8 个子网、30 台。 - ★ 网络地址 = IP AND 掩码:锚点
192.168.10.130/26→ 网络地址192.168.10.128;心算走"块大小 64: , "。 - ★ 广播地址 = 网络地址 + 块大小 − 1 =
192.168.10.191;可用范围 129 ~ 190,共 62 个。 192.168.1.0/24借 3 位 → 8 个/27:.0 / .32 / .64 / .96 / .128 / .160 / .192 / .224, 恰好铺满、无空洞;掩码统一255.255.255.224(FLSM)。- ★ CIDR 聚合锚点:4 个连续
/24(200.10.0.0~200.10.3.0)→200.10.0.0/22,掩码255.255.252.0,一条条目顶四条。 - ★ 聚合有对齐条件:块数必须是 2 的幂、起始地址必须按块大小对齐——
200.10.1.0/24与200.10.2.0/24合不成/23(第 3 字节一奇一偶),只能/22,而且会多覆盖。 - ★ 最长前缀匹配:四条全匹配时取
/25——默认路由/0匹配所有但最短,永远兜底。 - VLSM 按需切:同一个
/24里切出/26、/27、/27、/28,必须"需求从大到小"分配(大块对齐要求更严)。
考点
考点
1. 必背结论
- 子网划分 = 从主机号借位当子网号;三级编址 = 网络号 + 子网号 + 主机号;对外仍是两级,所以路由表不变大。
- ★ 借位账(C 类):借 n 位 → 子网数
、块大小 、可用主机 。 - ★ 掩码速查:
/25= 255.255.255.128(128)//26= .192(64)//27= .224(32)//28= .240(16)//29= .248(8)//30= .252(4),括号里是块大小。 - ★ 核心恒等式:网络地址 = IP AND 掩码;广播地址 = 网络地址 + 块大小 − 1;可用主机数 = 块大小 − 2。
- ★★ 锚点
192.168.10.130/26:掩码255.255.255.192、网络地址192.168.10.128、广播192.168.10.191、可用129~190共 62 个。 - ★★ 锚点
192.168.1.0/24借 3 位:8 个/27(.0/.32/.64/.96/.128/.160/.192/.224),每个 30 台可用,掩码统一255.255.255.224。 - ★ CIDR:"无类" = 不再看 A/B/C,直接用
/n;两大功能 = 按需分配 + 聚合通告。 - ★★ 聚合锚点:
200.10.0.0/24~200.10.3.0/24(4 个)→200.10.0.0/22,掩码255.255.252.0。 - ★ 聚合条件:块数
,且起始地址是 个地址的整数倍;奇数起始的第 3 字节合不出/23。 - ★ 最长前缀匹配:多条匹配时取前缀最长;默认路由
/0匹配所有但永远最后。 - ★ VLSM:同块内用不同掩码,按需求从大到小分配;RIPv1 不支持 VLSM(无掩码字段),RIPv2 才支持。
- 全 0 / 全 1 子网号:旧规定禁用、现代标准允许——题目没特别说明就按"全部可用"算。
2. 高频陷阱
- 把
/26的块大小算成 26:错。块大小 ——前缀数字是"1 的个数",不是"块大小"。 - 网络地址算成"把主机位清 0"但用了错的掩码:错。掩码必须与前缀一致——
/26的末字节是192而不是224(那是/27)。 - 广播地址写成"网络地址 + 块大小":错。是"网络地址 + 块大小 − 1"——
128 + 64 = 192错,应为191。 - 可用主机数不加"减 2":错。网络地址与广播地址都不能给主机——
/26是 62 而不是 64。 - 把借位后的子网数算成
:错,记反了。借 n 位是 个子网——/27借 3 位 → 8 个子网。 - 认为"借的位越多,每个子网越大":错。借得越多、子网越多、每个越小(块大小
随 n 递减)。 - 把"块大小"与"掩码末字节"混为一谈:错。两者互补——
256 - 掩码末字节 = 块大小(256 - 192 = 64)。 - 聚合时把地址顺序忽略:错。必须"连续且起始对齐"——
200.10.0.0与200.10.2.0两个/24合不成/23(少了1.0,中间有洞)。 - 聚合出"能覆盖但多覆盖"的前缀:要警惕。
1.0/24+2.0/24用/22能覆盖,但同时把.0与.3也吞进来——这在考试里算"不正确"的答案。 - 最长前缀匹配答成"先匹配上的":错。是"前缀最长的"——与路由表顺序无关(表项顺序只有在"前缀长度也相同"时才用来决定优先)。
- 认为"CIDR 里的
/n必须来自分类边界":错。任意 0 ~ 32 都合法——/19、/22、/26都是常态。 - 把 VLSM 的分配顺序写成"从小到大":错。必须从大到小——大块对齐约束更强,先切小块会打碎大块的落脚点。
- 认为"同一网络里所有子网掩码必须相同":在 VLSM 下错。只有 FLSM 才要求相同。
3. 解题模板("子网划分计算题")
① 看清楚给的是"前缀 /n"还是"掩码"
② 写出掩码: 前 n 位 1 -> 通常只需看最后一个非 255 字节
③ 算块大小 = 2^(32-n), 也 = 256 - 该字节
④ 求网络地址: 目标字节 // 块大小 * 块大小
⑤ 求广播地址: 网络地址 + 块大小 - 1
⑥ 可用主机数 = 块大小 - 2; 可用范围 = 网络地址+1 ~ 广播地址-1
⑦ 切子网题(给一个块, 要 m 个子网):
借位 n = ceil(log2(m)); 新前缀 = 原前缀 + n
子网地址依次 + 块大小, 逐段列出来
★ 要"每子网至少 k 台主机"反推: 找最小的 n 使 2^(32-newpre) - 2 >= k
⑧ 聚合题(给若干个块, 要合成一条):
把起始地址写成二进制, 找"最长公共前缀"
★ 先排序再两两合并: 只有"地址连续 + 数量是 2 的幂 + 起始对齐"才能合
合并几块: 2^j 块 -> 前缀减少 j 位
⑨ 最长前缀匹配题:
逐个算 (目的IP AND 掩码) 是否等于 该条目网络地址
全部匹配的里面取前缀最大者4. 与相邻章节的接口
net/30-ip.md(IP 数据报与 IPv4 地址):上一章的"分类地址粒度太粗 + A 类 126 个网络 / C 类 254 台主机"是本章的直接动因;上一章的"私有地址三块"在本章照旧使用(192.168.1.0/24就是一块 C 类私有地址)。net/32-nat.md(NAT 与 ICMP):本章的"私有地址 + 前缀"是 NAT 的前提——NAT 让192.168.x.x的主机共用一个公网地址上网;ICMP 差错报文里会带"出错数据报的首部 + 前 8 字节",而首部里就有本章关注的地址与掩码信息。net/33-routing.md(路由协议):本章的"最长前缀匹配"是路由转发的判定规则;RIP 通告的目的网络带掩码(RIPv2 才支持 VLSM)、OSPF 的 LSA 里也带掩码——路由协议通告的正是本章算出来的"网络地址/前缀"。net/34-ipv6.md(IPv6):IPv6 的/64是"子网号固定 64 位"的极端简化——一台主机永远有 64 位主机号可用,本章这套"借位"在 IPv6 里基本消失(但"前缀长度"这个概念被继承下来)。net/41-tcp.md(TCP):"子网划分定出网络边界"影响 TCP 的重传超时判断(同一子网内 RTT 小、跨子网 RTT 大);"最大报文段长度 MSS"与本章的"块大小"是两种"按需定尺寸"的同一思路。ds/32-hash.md(散列表):"块大小必须是 2 的幂、用位掩码代替除法"与散列表里"容量取 2 的幂、用hash & (cap-1)取桶"是同一个工程技巧——本章的"前缀位掩码"就是把这件事做到了地址结构上。
小结
- 子网划分 = 往下切,CIDR = 往上合,两者共用同一把尺子
/n。 - ★ 借位账(C 类):借 n 位 →
个子网、块大小 、可用 台。 - 掩码速查:
/25128、/26192、/27224、/28240、/29248、/30252(括号外的数是掩码末字节)。 - ★ 核心恒等式:网络地址 = IP AND 掩码;广播地址 = 网络地址 + 块大小 − 1;可用 = 块大小 − 2。
- ★ 锚点
192.168.10.130/26:网络地址192.168.10.128、广播192.168.10.191、可用 129 ~ 190 共 62 个。 - ★ 锚点
192.168.1.0/24借 3 位:8 个/27,.0/.32/.64/.96/.128/.160/.192/.224,各 30 台可用。 - ★ 聚合锚点:4 个连续
/24→200.10.0.0/22;条件:块数 + 起始对齐。 - ★ 最长前缀匹配:取最长,
/0兜底。 - VLSM:按需求从大到小切,各子网掩码可不同。
- 全 0 / 全 1 子网号现代标准允许——题目未特别说明就不减。
下一篇:NAT 与 ICMP
评论(0)
当前浏览器不允许本地存储,评论无法保存。
还没有评论,来说两句。