深蓝黑背景封面:左侧大标题「一个 C 类网段,切成六份」,副标题「向主机位借 3 位,掩码 /27」;右侧原创示意图——一个标注 192.168.1.0/24 的大方块横向切成 8 等份,前 6 份高亮标 1~6,后 2 份灰色标「预留」,上方括号注明「借 3 位得 8 个子网,每个 30 台主机」;左上标注「面试经典 · 网络」,右上标注「Day 03 · 10-31」 图 1:本文封面,一个 /24 借 3 位变 8 个 /27。

先说明属性:这是一篇经典题解析,题目出自本地历史面试资料,与任何公司的当前招聘无关。这类题有两个经典考法:一是「一个 C 类地址分给 6 个子公司、最大的一家有 26 台机器,子网掩码设多少」,二是「给定一个地址和掩码,判断另一个地址是否同属一个网段」。两题共用一套计算,先把这套计算讲透。

第一题:借位法划分子网。

C 类地址默认掩码 /24,最后 8 位是主机位。划分子网就是从主机位里「借」若干位当子网位,借 b 位就得到 2 的 b 次方个子网,剩下的主机位决定每个子网能装多少台机器。

两条约束同时卡:

  • 子网数:2^b ≥ 6,b 最少取 3(2^2 = 4 不够,2^3 = 8 够了)。
  • 主机数:借 3 位后主机位剩 5 位,2^5 − 2 = 30 台——减去的 2 是每个子网里的网络号(全 0)和广播地址(全 1),不能分给主机。30 ≥ 26,满足。

于是掩码是 24 + 3 = /27,写成点分十进制就是 255.255.255.224(224 = 128 + 64 + 32,即高三位借走)。

有一个历史口径差异值得主动提一句:1985 年的 RFC 950 曾建议子网位全 0 和全 1 的两个子网不要分配使用。按这个老口径,借 3 位可用子网是 8 − 2 = 6 个,恰好满足 6 家子公司;借 2 位只剩 2 个可用,怎么都不够。所以新老口径下这题答案都是 /27——但如果借位数不同会得到不同答案的题目,要先问清面试官按哪个口径。

第二题:同网段判定。

规则只有一句话:两个地址分别与子网掩码做按位 AND,结果相等就在同一网段。AND 出来的结果就是网络号。

拿「10.110.12.29,掩码 255.255.255.224」演一遍:29 的二进制是 00011101,224 是 11100000,按位 AND 得 0,所以这个地址落在 10.110.12.0/27 这个子网里。该子网可用主机范围是 10.110.12.1 到 10.110.12.30,广播地址是 10.110.12.31。候选里凡是最后一段在 1~30 之间的就同网段,比如 10.110.12.30;而 10.110.12.33 落在下一个子网 10.110.12.32/27,就不算。

用代码把两道题都算一遍。

下面这个小程序实现借位规划与同网段判定,覆盖两道题的题面参数,用 Node.js 22.15.0 直接运行(node subnet.js):

'use strict';

// IP 字符串与 32 位整数互转
const toInt = (ip) => ip.split('.').reduce((acc, s) => (acc << 8) + Number(s), 0) >>> 0;
const toIp = (n) => [24, 16, 8, 0].map((sh) => (n >>> sh) & 255).join('.');

// 第一步:按需求选借位数(现代惯例:子网位全 0 全 1 也可用)
function planSubnets(needSubnets, needHosts, hostBitsTotal = 8) {
  for (let b = 1; b < hostBitsTotal - 1; b++) {
    const subnets = 2 ** b;
    const hosts = 2 ** (hostBitsTotal - b) - 2; // 去网络号与广播地址
    if (subnets >= needSubnets && hosts >= needHosts) {
      return { borrow: b, subnets, hosts, prefix: 32 - hostBitsTotal + b };
    }
  }
  throw new Error('需求超出该网段容量');
}

const plan = planSubnets(6, 26);
console.log(`借 ${plan.borrow} 位:可得 ${plan.subnets} 个子网(需 6 个),每个 ${plan.hosts} 台可用主机(需 26 台)`);
console.log(`子网掩码:/${plan.prefix} = ${toIp((0xffffffff << (32 - plan.prefix)) >>> 0)}`);

// 按老教材「子网位不能全 0 全 1」的口径对照
const usable = plan.subnets - 2;
console.log(`老口径可用子网数 = ${plan.subnets} - 2 = ${usable}(恰好满足 6 个)`);

// 第二步:同网段判定 = 两个地址分别与掩码按位 AND,结果相等即同网段
const mask = toInt('255.255.255.224');
const pairs = [
  ['10.110.12.29', '10.110.12.30'],
  ['10.110.12.29', '10.110.12.33'],
];
for (const [a, b] of pairs) {
  const na = toIp(toInt(a) & mask);
  const nb = toIp(toInt(b) & mask);
  console.log(`${a} 与 ${b}:网络号 ${na} vs ${nb} -> ${na === nb ? '同一网段' : '不同网段'}`);
}

// 10.110.12.29/27 所在子网的范围
const base = toInt('10.110.12.29') & mask;
console.log(`10.110.12.29/27 所在子网:网络号 ${toIp(base)},可用主机 ${toIp(base + 1)} ~ ${toIp(base + 30)},广播 ${toIp(base + 31)}`);

实测输出:

借 3 位:可得 8 个子网(需 6 个),每个 30 台可用主机(需 26 台)
子网掩码:/27 = 255.255.255.224
老口径可用子网数 = 8 - 2 = 6(恰好满足 6 个)
10.110.12.29 与 10.110.12.30:网络号 10.110.12.0 vs 10.110.12.0 -> 同一网段
10.110.12.29 与 10.110.12.33:网络号 10.110.12.0 vs 10.110.12.32 -> 不同网段
10.110.12.29/27 所在子网:网络号 10.110.12.0,可用主机 10.110.12.1 ~ 10.110.12.30,广播 10.110.12.31

上半部分借位演算:一个 /24 地址的最后 8 位画成 8 个比特格,前 3 格被染成「子网位」、后 5 格留作「主机位」;算式:借 3 位得 2^3=8 个子网 ≥ 6 个需求,主机位剩 5 位得 2^5−2=30 台可用主机,掩码为 255.255.255.224(/27)。下半部分同网段判定:两个地址分别与子网掩码做按位 AND,结果相等即同网段——示例 192.168.1.33 与 192.168.1.62 都落在 192.168.1.32/27,而 192.168.1.65 落在下一个子网。底部总结:划分先算「要几个子网、每个装几台」,取满足两者的最少借位;判定同网段只看 AND 结果 图 2:借位演算与同网段判定的完整计算过程。

可能的追问。

「为什么取满足条件的最少借位?」借得越多子网越碎、每个子网可用主机越少,地址浪费越大;够用即可,余下的主机位留给每家公司扩容。「换成 B 类地址思路变吗?」不变,只是主机位从 16 位起借,约束公式一模一样。「云上的 VPC 规划也是这样算吗?」本质相同:VPC 划一个大的 CIDR,再按业务切成若干子网,区别只是云厂商会额外保留每个子网的头几个地址给网关和管理组件,可用主机数要再多减几个。

不会算掩码,网段规划时就只能拍脑袋——拍小了地址不够分,拍大了浪费。回到面试现场,建议的答题顺序:先写两条约束不等式(子网数与主机数),解出最少借位,换算成点分十进制掩码,再补一句老口径差异;判定题则直接演一遍 AND 运算。你规划 VPC 或集群网段时,是怎么定 CIDR 大小的?有没有遇到过「地址不够分」要重新规划的情况?欢迎聊聊。