📚 第四章 IP协议
📄 一句话概括

IPv4 地址就是一个 32 位整数,写成「点分十进制」只是为了好看。 四段、每段 8 位、0 到 255——所以 256 一定打不出来。

📝 本节和前面几节的关系

4.2 讲完了「IP 怎么工作」,从这一节开始拆开地址本身。 这一节是全章最基础的一节,后面所有子网、聚合都建立在它上面。

一、一个整数,四种写法

同一个地址的几种表达:

写法 值 场合
点分十进制 192.168.3.152 日常配置
十进制整数 3232236440 数据库存储
十六进制 0xC0A80398 抓包、调试
二进制 11000000.… 算掩码时用

这四种写法在机器里是同一个数:所以「把 IP 存成整数」在数据库里很常见(省空间、 比字符串快)。而「把 IP 存成字符串」在配置里很常见(人能读)。 第二行那个 3232236440 值得自己算一遍: 192 乘 2 的 24 次方,加 168 乘 2 的 16 次方,加 3 乘 256,加 152。 这个式子说明「点分十进制」只是「按 8 位分组」的一种读法。

二、每段为什么是 0 到 255

8 位能表示 256 个值(0 到 255),所以每一段的上限就是 255。 「192.168.3.256」打不出来,不是软件限制,是数学限制。 顺带说清「为什么是 4 段」:32 位除 8 得 4。 如果是 IPv6,128 位除 16 得 8 段——所以 IPv6 是「八段十六进制」,每段 4 个十六进制位。 这个「按 8 位或 16 位分组」的规律,是同一条。 还有一个细节:前导零。 「192.168.003.152」在很多系统里会被解释成八进制(因为 0 开头), 结果变成 192.168.3.152 或者干脆报错。 所以「不要写前导零」这条规矩,是为了避免歧义,不是为了好看。

三、二进制那一段才是真面目

把 192.168.3.152 展开成二进制:

192 = 11000000
168 = 10101000
  3 = 00000011
152 = 10011000

注意「3」那一段是 00000011,前面补了六个零: 少了补零就会变成「11」,长度不对,整个数就错了。 所以「每一段固定 8 位」是硬规定。 这四段的二进制在算掩码时天天用: 「192.168.3.0/24」意味着「前 24 位是网络、后 8 位是主机」, 所以「152 = 10011000」整个都是主机部分,而「3 = 00000011」属于网络部分。 这个「数一数第几位」的动作,就是子网划分的全部内容。 一个心算技巧:记住 128、64、32、16、8、4、2、1 这八个权值, 看到 152 就想「128 加 16 加 8」,对应位是 10011000。比按除法算快得多,也不容易错。

四、动手 Demo:32 位地址怎么写成人能读的样子

把四个地址同时写成十进制、十六进制和二进制。 注意 10.0.0.1 那一段:三个 0 都要补满 8 位。

import ipaddress
for ip in ('192.168.3.152', '10.0.0.1', '0.0.0.0'):
    n = int(ipaddress.ip_address(ip))
    bits = '.'.join(format(int(x), '08b') for x in ip.split('.'))
    print('%s = %d = 0x%08X' % (ip, n, n))
    print('  ' + bits)

实跑输出:

--------------------------------------------------------------------
32 位地址怎么写成人能读的样子
--------------------------------------------------------------------
**IPv4 地址就是一个 32 位整数,写成「点分十进制」只是为了好看。**
**四段、每段 8 位、0 到 255,就这么简单。**

**192.168.3.152** = 3232236440(0xC0A80398)
  二进制:11000000.10101000.00000011.10011000
**10.0.0.1** = 167772161(0x0A000001)
  二进制:00001010.00000000.00000000.00000001
**255.255.255.255** = 4294967295(0xFFFFFFFF)
  二进制:11111111.11111111.11111111.11111111
**0.0.0.0** = 0(0x00000000)
  二进制:00000000.00000000.00000000.00000000

**注意每一段的二进制都是 8 位**:**少了要补零,因为「.1.」和「.00000001.」在机器里是同一个数。**
**所以 192.168.3.152 这个写法里,「152」占了整整 8 位**,换成 10011000 就是它的真面目。

**算一下「为什么每段最大 255」**:**8 位能表示 0 到 255 共 256 个值**,
**所以 256 就是越界——这也是「192.168.3.256 打不出来」的原因。**

format(x, '08b') 这个写法比手写 bin() 好:它能保证「至少 8 位、不足补零」, 而 bin(3) 只给 0b11,还要自己补。 写脚本处理地址时,这类「补零」的坑非常多。 「点分十进制」这个名字本身就是解释:点是分隔符,十进制是进制,四个数字是四组 8 位。 所以只要看到「四个 0 到 255 的数字用点连起来」,就是 IPv4。 而 IPv6 是「八组十六进制用冒号连起来」——冒号和点这个区别,在日志里一眼就能分出来。 最后一个实用点:排序。 如果 IP 存成字符串,排序会得到「10.0.0.1 排在 9.0.0.1 前面」这种结果, 因为字符串是逐字符比的。 所以「按 IP 排序」必须先转成整数——这是「存字符串还是存整数」这个问题最实际的答案。

30 秒实验

  1. 算一下:172.16.254.1 的十进制是多少?
  2. 心算一下:200 的二进制八位是什么?
  3. 想一下:为什么「192.168.003.152」可能出问题?

常见误区

  1. 以为「IP 地址是四段独立的数字」。 它是一个 32 位整数,四段只是分组读法。
  2. 以为「每段 0 到 255 是软件规定的」。 是 8 位的数学上限,不是谁写死的。
  3. 以为「IP 存成字符串更方便排序」。 字符串排序会错,要先转成整数。
自测三问(点开对答案) 1. 为什么每段最大是 255? —— **每段是 8 位,能表示 0 到 255 共 256 个值**。 2. 192.168.3.152 的二进制第二段是什么? —— **10101000**(168 等于 128 加 32 加 8)。 3. 为什么说「点分十进制只是为了好看」? —— **机器里存的是一个 32 位整数**,怎么写都是同一个数。

下一节把 32 位切成两半:网络部分和主机部分。

相关笔记