如果说数据链路层解决的问题是"在同一根线缆的两端,怎么把帧一个比特一个比特地搬过去",那么当我们跳出这一根网线、进入"整个世界互联"这种规模的网络时,问题就变成了另一副模样:面对错综复杂、成千上万条链路交织在一起的巨型网络,一个数据包到底该走哪条路才能到达目的地?

这就是网络层要回答的核心问题——在复杂的网络环境中,确定一条合适的路径。

在往下讲之前,先给你一张地图,让我们明确这次的旅程会经过哪些站点。本文是我们 Linux 网络编程系列里"网络层"这一章,我会从网络层的三个基本角色讲起,然后花大力气拆解 IP 数据报首部(也就是 IP 报文最前面那一段承载控制信息的头部),接着进入整章的重点——网段划分(网络号和主机号、分类编址、CIDR、子网掩码、私有地址),最后落到路由——数据包是怎么靠一张张路由表"问路问过去"的。全程我会用最土的比喻、真实的命令输出和能直接跑的例子,把每一个概念掰开揉碎。老规矩,文末有带详细讲解的自测题。

三个出场角色:主机、路由器、节点

在正式讨论 IP 协议之前,得先分清三个术语,它们后面会反复出现:

  • 主机(host):一个配有 IP 地址、同时也要进行路由控制的设备。举个例子,你的笔记本电脑、台式机、手机,甚至是服务器,在网络层的语境下都叫"主机"。注意,主机也要参与路由控制——它至少要能判断"这个包是不是给我的,如果是,就收下;如果不是给本地网络的,就得想办法丢给路由器"。所以"路由"不是路由器的专利,主机也在做最基本的路由决策。
  • 路由器(router):既配有 IP 地址,又能进行路由控制的设备。它就像一个"多路口"的交通枢纽,专门负责把数据包从一个网络导向另一个网络。
  • 节点(node):主机和路由器的统称。凡是参与网络层通信的实体,都可以叫节点。

一句话概括它们的区别:主机是"网络上的一台会思考谁给谁发信的设备",路由器是"专门替别人转信、而且有多条出口的枢纽",而"节点"就是把两者打包在一起的通用叫法。你只要记住:路由器一定是节点,但一台主机单独看也是一个节点。

好,角色清点完毕。接下来我们审视网络层最核心的一个"物件"——IP 数据报,尤其是它的首部。

IP 数据报首部:报文的"身份证和快递单"

网络层里最著名的协议就是 IP(Internet Protocol,互联网协议)。在 IPv4(Internet Protocol version 4,互联网协议第四版)体系下,一个在网络层传输的数据单元叫 IP 数据报(IP datagram)。封装在它最前面、用来告诉路由器"你是谁、要去哪、该怎么处理"的那一段控制信息,就叫** IP 数据报首部**(也叫 IP 头部)。

为什么首部这么重要?你把它想象成快递单:快递单上写着发件人、收件人、包裹重量、保鲜时效(比如"生鲜必须 24 小时内送到")、是否容易损坏……快递员看到这张单子,才知道该把这件包裹怎么安排。IP 数据报首部就是这张"快递单",数据链路层是在下面跑运输的运货车,而网络层靠这份首部在茫茫网络中把数据送对地方。

IPv4 首部是以 32 位(也就是 4 字节)为单位来组织的,这样的单位叫一个"字"(这里指的是 32 位的字,下面会反复用到"多少个 32bit"这种说法)。标准 IPv4 首部是20 字节,后面的选项字段最多还能再加 40 字节,所以首部总长最大 60 字节。

先上一整张结构图,把每一段的比特布局画给你看(这是无数教科书上的经典版本,我加了中文注释):

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|版本(4)| 首部长度(4) |       服务类型(8)        |   总长度(16)   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          标识identification(16)            |标志(3)|分片偏移(13)|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  生存时间TTL(8) | 协议protocol(8) |    首部校验和(16)          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                   源IP地址(32)                                 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                 目的IP地址(32)                                 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                选项(可选, 0~40字节)                  |  填充到整字 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

这一份 20 字节(不含选项)的首部,含金量极高。下面我们一格一格拆开讲。

4 位版本号(Version)

占 4 个比特,指定 IP 协议的版本。对于 IPv4 来说,这个值就是 4。为什么要存一个版本?因为同一条链路上可能出现 IP 协议的不同版本(比如 IPv4 和 IPv6),路由器收到一个数据报,先看清版本,才知道该用哪一套规则去解析后面的字段。这就像"中英文邮件"——先看是中文还是英文,才知道该怎么读这封信。

4 位首部长度(IHL, Internet Header Length)

占 4 个比特,表示这个首部占多少个 32 位字。注意单位不是字节,而是"多少个 4 字节"。为什么这么设计?因为你把 4 位长度值乘以 4,就得到了首部的字节数。由于标准首部是 20 字节 = 5 个 32 位字,所以最常见的首部长度字段值是 5。

这里有第一个"从数字反推物理意义"的边界,认真看:4 个比特能表示的最大值是 15(二进制 1111)。既然首部长度字段的单位是"32 位 = 4 字节",那么首部最大长度就是 15 × 4 = 60 字节。这就是"IP 首部最长 60 字节"这个数的由来——不是随便定的,是 4 位字段能表示的上限决定的。

8 位服务类型(Type of Service, ToS)

占 8 个比特。这一位的划分比较讲究,历史上是:

  • 前 3 位:优先权字段,用于给数据报标一个"重要性等级",不过现在已经弃用了;
  • 中间 4 位:TOS 字段,代表四种诉求;
  • 最后 1 位:保留字段,必须置为 0。

中间 4 位 TOS 代表四种诉求,分别是:最小延时、最大吞吐量、最高可靠性、最小成本。划重点:这四个诉求是相互冲突的,"又要快又要稳又要便宜"物理上做不到,所以一个数据报只能选其中一个。

这四种诉求分别适合什么场景?给两张最常见的对照:

  • 对于 ssh/telnet 这类交互式应用,人敲一个字符,要立刻看到回显,最怕的是延迟,所以最小延时比较重要;
  • 对于 FTP(File Transfer Protocol,文件传输协议)这类大文件传输应用,用户在乎的不是单个字符快不快,而是整批数据能不能尽快搬完,所以最大吞吐量更重要。

说白了,ToS 是给路由器一个"偏好提示",告诉它"这个包需要被怎么对待"。要注意的是,现代 Internet 上 ToS 字段的处理相当粗糙,很多路由器根本不鸟它,它更像是一个"历史遗留的好心提醒"。

16 位总长度(Total Length)

占 16 个比特,表示这个 IP 数据报(首部 + 数据)整体占多少个字节。因为首部我们已经知道多长了,用总长度减去首部长度,就能算出数据部分有多长。

这里有个潜在边界:16 位能表示的最大值是 65535,所以一个 IP 数据报的理论最大长度就是 65535 字节。但实际在以太网里,数据链路层的一个帧(Frame,数据链路层的传输单元)最多只能装 MTU(Maximum Transmission Unit,最大传输单元)这么大,以太网默认 MTU 是 1500 字节。也就是说,网络层"想打包发一个 3KB 的数据报",但底下"运货车"一次只能拉 1500 字节,这时候就必须分片——这个问题我们马上在"标识、标志、分片偏移"三连里彻底讲清。

16 位标识(Identification)

占 16 个比特,用来唯一标识主机发送的一个报文。它存在的意义,恰恰和分片相关:如果一个 IP 数据报在链路层被分片了,那么切割出来的每一个"片"都共享同一个标识值。这样接收端看到一大堆"片",只要标识相同,就知道它们原本是同一封"信"拆开的,要凑回一起。

3 位标志字段(Flags)

占 3 个比特,三个位的职责分别是:

  • 第一位:保留位,现在不用,但是还没想好说不定以后要用到,所以必须置 0;
  • 第二位:禁止分片标志(DF, Don't Fragment)。这一位置为 1,表示"不许把本数据报切分"。这时候如果数据报长度超过 MTU,IP 模块不会去分片,而是直接丢弃这个数据报,并(在多数情况下)回一个 ICMP 错误给发送方。这个位很有用,常用于路径 MTU 探测。
  • 第三位:"更多分片"标志(MF, More Fragments)。如果发生了分片,那么最后一个分片这一位为 0,其他分片为 1。它就像一句话结尾的句号——"后面还有没有续集?有就置 1,没有了就置 0",接收端靠它知道什么时候该收尾组装了。

13 位分片偏移(Fragment Offset)

占 13 个比特,表示这个分片相对于原始 IP 数据报起始处的偏移。换句话说,它告诉我们"当前这一片在原报文里处于哪个位置"。

这里藏着一个极其容易踩坑的单位换算,请你务必记牢:这个字段存的不是"字节偏移",而是"多少个 8 字节单元"。真实偏移字节数 = 字段值 × 8。

为什么非得这样搞?因为 13 位只能表示最大 8191,如果直接以字节为单位,最多只能表达 8191 字节的偏移;但 IP 数据报最大可以有 65535 字节。把单位放大 8 倍后,8191 × 8 = 65528,刚好够覆盖整个 65535 字节的报文范围。这是空间换精度的经典操作。

由此产生一个必须严格遵守的规则:除了最后一个分片,其他所有分片的长度必须是 8 的整数倍(否则偏移除以 8 会得到小数,无法用 13 位字段表达,分片之间就不连续了)。

8 位生存时间(TTL, Time To Live)

占 8 个比特。**TTL(Time To Live,生存时间)**表示"这个数据报最多还能经过多少个路由器(也就是多少个'跳',hop)"。

最常见的字段值是 64(Linux 默认),Windows 常见是 128,不同操作系统不同。数据报每经过一个路由器,路由器的 IP 模块就执行一次 TTL -= 1;如果减到 0 还没到达目的地,路由器就直接丢弃这个数据报,并且(通常)会向发送方回一个 ICMP(Internet Control Message Protocol,互联网控制报文协议)的超时消息——我们在用 ping 或 traceroute 的时候,看到的正是这些消息。

TTL 存在的最大价值,是防止路由循环(routing loop)。设想一下:如果路由器配置有误,A 认为下一跳去 B、B 认为下一跳去 A,一个数据报就会在 A 和 B 之间无限来回兜圈,永远找不到出口,直到把链路和中间设备的资源耗尽。有了 TTL,这个数据报每转一圈 TTL 就减一,最多转几十次就被杀掉,环路就被遏制住了。所以那句"TTL 用来防路由循环"要刻进脑子里。

8 位协议(Protocol)

占 8 个比特,表示上层协议的类型。常见的取值:6 表示 TCP(Transmission Control Protocol,传输控制协议)、17 表示 UDP(User Datagram Protocol,用户数据报协议)、1 表示 ICMP。路由器虽然本身不关心上层是什么,但它需要把这个字段原样传给上层,让接收主机知道"拆掉 IP 头之后,这段数据应该交给哪一位大神去处理"。

16 位首部校验和(Header Checksum)

占 16 个比特,用一套基于二进制求和的算法来校验首部是否在传输过程中损坏。注意两个细节:

  • 它只校验首部,不校验数据部分。为什么不连数据一起校验?因为每一跳路由器都要修改 TTL(TTL 减一会导致首部内容变化,校验和必须重算),如果连数据一起校验,每一跳都得把整段数据重新算一遍,计算量太大。而数据正确性由上层(TCP/UDP)和链路层自己的校验来兜底。
  • 中文课件常把它说成"CRC 校验",其实更准确的机制是对首部按 16 位分片、求和后取其反码(one's complement)做校验,和严格意义上的 CRC(循环冗余校验)不是一回事。这里不太影响理解,但你若看到两份资料用词不同,不必纠结——本质都是"防首部损坏"。

32 位源地址与 32 位目的地址

各占 32 个比特,一个写发送端 IP 地址,一个写接收端 IP 地址。IP 地址(Internet Protocol Address,互联网协议地址,下文中我们简称 IP 地址)在这里正式登场——它是网络层用来标识"我是谁、我要找谁"的 32 位正整数,也是整个网段划分那一节的主角。

选项字段(Options)

占可变长度,最多 40 字节。设计初衷是想让 IP 支持一些额外的灵活性(比如记录路由、时间戳等)。但因为它安全性和实用性都堪忧,实际很少使用,现代实现也基本不去处理这份报文——课件里直接"略"了,我在这里给你一句结论即可:知道它存在、知道它最多 40 字节,考试够用,真正实战时几乎绕不开却也几乎用不上它。

讲完首部,我再用一张表把每个字段和"人话"对齐,方便你快速回忆:

字段位数中文含义一句话人话
版本 Version4IP 版本告诉我该按 IPv4 还是 IPv6 读
首部长度 IHL4首部占多少 32 位字值×4 = 首部字节数,最大 60
服务类型 ToS8四种诉求之一快 / 吞吐 / 稳 / 便宜,只能选一个
总长度 Total Length16整个数据报字节数首部 + 数据一共多长
标识 Identification16报文归属标识分片后靠它认出"来自同一封信"
标志 Flags3禁不分片 + 是否还有片DF 禁拆、MF 标记最后一篇
分片偏移 Frag Offset13本片在原报文的位置实际偏移 = 值 × 8 字节
生存时间 TTL8最多还能跳几跳防路由循环,减到 0 就丢
协议 Protocol8上层是谁TCP=6 / UDP=17 / ICMP=1
首部校验和 Checksum16首部完整性校验只校验头部,每跳重算
源 / 目的地址各 32收发双方身份网络层的"身份证"
选项 Options0~40 字节扩展功能记录路由等,实际极少用

网段划分:网络号和主机号(重点)

拆完首部,我们进入本章的重中之重——网段划分。

先建立最基础的世界观:IPv4 的 IP 地址是一个 32 位的正整数,它由两个部分组成——网络号和主机号。

  • 网络号(network number):用来保证相互连接的两个网段(一个网段就是一个子网,里面装着一批 IP 相近的主机)具有不同的标识。什么是网段?通俗讲,就是"网络号和子网掩码划分出来的、属于同一个广播域的一群主机"。属于同一个网段的主机,网络号相同。
  • 主机号(host number):用来在同一个网段内区分不同的主机。同一网段内,所有主机的网络号相同,但主机号必须各不相同。

把一句话翻译成大白话:不同的子网,其实就是把网络号相同的主机放到一起。你在一个子网里新加一台主机,它的网络号必须和这个子网一致,但主机号不能和子网里已有的任何一台重复。

通过合理设置网络号和主机号,就能保证在相互连接的整个网络中,每台主机的 IP 地址都不相同——这就解决了网络层"唯一标识"的根本问题。

那么问题来了:手动给子网内每台新增主机分配、管理 IP,是一个相当繁琐的事情。 于是出现了两类缓解办法:

第一类,DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)。它的作用是通过动态分配,自动给子网内新增的主机节点分配 IP 地址,省去人工管理的麻烦。一般路由器都内置 DHCP 功能,所以路由器也可以被看作一台 DHCP 服务器——这就是为什么你家路由器插上就能自动给手机、电脑发 IP,你几乎从没手动配过网段。

第二类,从"编址方案"本身下手,让地址利用得更合理。这就要从"分类编址"讲到"CIDR"了。

过去的分法:分类编址(Classful Addressing)

在 CIDR 出现之前,曾经有过一种按"板子大小"把 IP 地址划分为五类的方案。它用 IP 地址最前面的几个比特来区分类别:

  • A 类:0.0.0.0 到 127.255.255.255。最高位是 0,第一个数 0~127。一个 A 类网能容纳的主机数量极其庞大。
  • B 类:128.0.0.0 到 191.255.255.255。最高两位是 10,第一个数 128~191。
  • C 类:192.0.0.0 到 223.255.255.255。最高三位是 110,第一个数 192~223。
  • D 类:224.0.0.0 到 239.255.255.255。主要用于组播(multicast)。
  • E 类:240.0.0.0 到 247.255.255.255(有的资料会展到 255)。保留给实验和未来使用。

我把 ABCD 类对应关系整理成一张表:

类别范围最高位网络号位长单网主机数规模用途
A0.0.0.0 ~ 127.255.255.25508 位约 1677 万超大规模网络
B128.0.0.0 ~ 191.255.255.2551016 位约 6.5 万中大规模网络
C192.0.0.0 ~ 223.255.255.25511024 位约 254小规模网络
D224.0.0.0 ~ 239.255.255.2551110——组播
E240.0.0.0 ~ 247.255.255.2551111——实验保留

随着 Internet(互联网)飞速发展,这种"一刀切"的分类编址方案的局限性很快暴露出来。最直观的就是"两极分化":

  • 大多数组织实际只需要"几十到几百台主机",但申请时要么申请 C 类(约 254 台,勉强)、要么干脆申请 B 类(约 6.5 万台)。申请了 B 类,理论上一个子网能容纳 6 万 5 千多台主机,A 类更多,可是真实网络里根本不会有一个子网里有这么多主机。
  • 于是大量 B 类、A 类地址被"圈地"却被闲置浪费,而真正需要的 C 类地址又很快不够用——B 类地址很快就分配完了,A 类却浪费了大量地址。

这种"要么饿死、要么撑死"的分法显然不可持续。

现在的主流:CIDR 与子网掩码

针对分类编址的弊端,IETF 提出了全新的划分方案,叫 CIDR(Classless Inter-Domain Routing,无类别域间路由)。

CIDR 的核心思想是:放弃"IP 属于 A/B/C 哪一类"这种僵化分类,改用一个额外的【子网掩码】来精确区分网络号和主机号。

这里第一次正式引出 子网掩码(subnet mask)这个概念,务必记牢它的定义和作用:

  • 子网掩码也是一个 32 位的正整数;
  • 它通常用一串 1、然后以一串 0 结尾(比如 255.255.255.0,二进制就是 11111111.11111111.11111111.00000000);
  • 把 IP 地址和子网掩码做"按位与"(bitwise AND)运算,得到的最终结果就是网络号;
  • 网络号和主机号的界限,与这个 IP 地址本来是 A 类、B 类还是 C 类毫无关系——完全是掩码说了算。

这个"按位与"的操作,我要给你演算一遍,因为它太重要了。拿 140.252.20.68 举例(和下文的 CIDR 写法呼应),假设子网掩码是 255.255.255.0:

IP 地址    140.252.20.68     二进制掩码     255.255.255.0
展开成二进制:
  140 = 10001100        255 = 11111111
  252 = 11111100        255 = 11111111
   20 = 00010100        255 = 11111111
   68 = 01000100        000 = 00000000
按位与(同为 1 才是 1):
140 & 255 = 140
252 & 255 = 252
 20 & 255 = 20
 68 &   0 = 0
结果(网络号): 140.252.20.0

看到了吗?因为子网掩码前 24 位全是 1、后 8 位全是 0,所以"按位与"的作用就是把 IP 地址的后 8 位强制抹成 0。抹完 0 得到 140.252.20.0,这就是这个 IP 所属的网络号。而主机号则是 68——也就是被掩码的 0 挡住的那 8 位里的内容。

由此可以推出一个子网的地址范围:主机号从全 0 到全 1。对 140.252.20.0/24 这个子网来说,主机号取值范围是 0~255,也就是 140.252.20.0 ~ 140.252.20.255。前面那个"主机号从全 0 到全 1 就是子网地址范围",说的就是这句话。

IP/前缀:更简洁的写法

IP 地址和子网掩码还有一种广为使用的简洁表示方法,叫 CIDR 记法(也叫斜杠记法)。例如 140.252.20.68/24:

  • 它表示 IP 地址是 140.252.20.68;
  • /24 表示子网掩码的高 24 位是 1,也就是 255.255.255.0。

所以 192.168.1.5/24、192.168.1.5/255.255.255.0、以及"192.168.1.0/24 这个网段"这三种说法经常混用,其实表达的是同一件事。/24、/16、/8 这些数字,专业的叫法是前缀长度(prefix length),它就代表"子网掩码里连续的 1 有几位"。

特殊的 IP 地址

在网段里,有几个 IP 地址身份特殊,需要单独记住:

  • 网络地址:把 IP 地址的主机地址位全部设为 0,得到的就是网络号,它代表整个局域网(这个子网自己),不能分给任何单台主机。
  • 广播地址:把主机地址位全部设为 1,得到的就是广播地址,用于给同一个链路中所有主机发送数据包。所有主机都无条件接收发给广播地址的包。
  • 环回地址:127.* 开头的 IP 地址用于环回测试(loopback),通常特指 127.0.0.1,它代表"本机自己"。发给 127.0.0.1 的包不会离开本机,直接回到本机协议栈——这是因为数据链路层的环回接口(lo0)只在本机内部打转。

让我们用一个 /24 子网做一次完整的边界归位演示,把这个子网的所有特殊地址看清楚(假设子网 192.168.1.0/24):

网络地址(全0)    : 192.168.1.0     ← 标识这个子网本身,不可分配给主机
第一台主机       : 192.168.1.1     ← 常用作网关/路由器 LAN 口 IP
中间可用主机     : 192.168.1.2 ~ 192.168.1.254
广播地址(全1)    : 192.168.1.255   ← 发给这个子网里的所有主机

这里是最容易踩坑的地方:一个 /24 子网能表达的地址总数是 2^8 = 256 个,但真正能分配给主机用的只有 254 个——因为 192.168.1.0(网络地址)和 192.168.1.255(广播地址)被占用了。很多刚学的人一算"256 个"就高兴,结果一配就发现少了 2 个。这不是 bug,是协议语义。

IP 地址的数量限制:为什么 43 亿个还不够用

这里先把数量问题算清楚。IPv4 的 IP 地址是一个 4 字节 32 位的正整数,所以一共只有 2^32 个,大约是 43 亿左右。TCP/IP 协议规定每个主机都需要有一个 IP 地址——那是不是说,全世界最多只能有 43 亿台主机接入互联网?

答案是否定的,真实情况比这更惨,原因有三:

  1. 大量地址被特殊用途"吃掉":网络地址、广播地址、环回地址、D/E 类地址等等,都拿不出来当普通主机地址用,所以实际可用数量远不足 43 亿;
  2. IP 地址不是按"主机台数"配的,而是按"网卡"配的:每一块网卡都需要配置一个甚至多个 IP 地址(一台服务器可能有多块网卡),一台机器往往占用不止一个地址;
  3. 早期分类编址的浪费进一步雪上加霜。

CIDR 在一定程度上缓解了地址不够用的问题(它提高了利用率、减少了浪费),但请注意一个关键边界:它只优化了"怎么分"的效率,绝对数量上限并没有增加,43 亿这个天花板还在。于是业界又祭出三种手段:

  • 动态分配 IP 地址:只在设备接入网络时才临时分配一个 IP。同一个设备(按 MAC 地址识别,MAC 就是网卡的物理地址),每次接入互联网,拿到的 IP 地址不一定相同。DHCP 就是干这个的。
  • NAT 技术(Network Address Translation,网络地址转换):这个我们和"私有地址"一起讲,它是现阶段解决"地址不够"最功不可没的大将。
  • IPv6:需要特别说明,IPv6 并不是 IPv4 的简单升级版,两者是不相融的两个协议,彼此并不兼容。IPv6 用 16 字节(128 位)来表示一个 IP 地址,足够给地球上每一粒沙子编号,从根上解决了地址枯竭。但目前"下一代"还没完全普及,全世界仍然跑在 IPv4 和 IPv6 双栈并存的状态里。

私有 IP 地址和公网 IP 地址

前面铺垫了那么多,现在可以隆重介绍解决"地址不够用"最实际、最普及的一招了——私有地址 + NAT。

先建立世界观:如果一个组织只是内部组建局域网,IP 地址只用于局域网内部通信,不直接接到 Internet 上,那么理论上它用任意 IP 地址都可以——反正外部看不见、也用不到它。但为了避免不同组织内部选用的地址互相撞车(一旦两个网络最终互联,IP 冲突就灾难了),RFC 1918(Request for Comments 1918,互联网工程任务组发布的一份标准文档)明确规定了一组用于组建局域网的私有 IP 地址:

私有网段网络号位长地址数量
10.*前 8 位是网络号16,777,216 个
172.16.* 到 172.31.*前 12 位是网络号1,048,576 个
192.168.*前 16 位是网络号65,536 个

凡是落在这些范围内的,称为私有 IP;除此之外的,称为全局 IP(Global IP Address,也叫公网 IP)。私有 IP 没有公网合法性,不能在公网路由上随意出现;公网 IP 则是在全球范围内唯一、可被公网寻址的。

由此衍生出家喻户晓的"路由器双 IP"模型:

  • 一个家用路由器可以配置两个 IP:一个是 WAN 口 IP(Wide Area Network,广域网口,也就是"朝外"的口),一个是 LAN 口 IP(Local Area Network,局域网口,也就是"朝内"的口,也叫子网 IP)。
  • 路由器 LAN 口连接的主机,都从属于当前这个路由器的子网。它们的 IP 由路由器(充当 DHCP 服务器)分配,比如 192.168.1.2、192.168.1.3……
  • 有意思的是:不同的路由器,其 LAN 口的子网 IP 其实很可能一样(最常见的就是 192.168.1.1)。规则是——同一个子网内的主机 IP 不能重复,但不同子网之间的主机 IP 是可以重复的。你家 192.168.1.3、邻居家也是 192.168.1.3,完全可以,因为分别属于两个不同的私有网段,物理上彼此隔离。

那么,私有 IP 怎么和公网通信?这就引出了层层嵌套的模型:

  • 每一个家用路由器,其实又作为运营商路由器的子网中的一个节点。运营商路由器也可能有很多级,最外层的运营商路由器,其 WAN 口 IP 就是一个公网 IP 了。也就是说,从你家到公网,是一个"私网 → 运营商私网 → …… → 公网"的层层套娃。
  • 子网内的主机需要和外网通信时,路由器会把 IP 首部里的源 IP 地址替换成自己的 WAN 口 IP(同时还记住"谁发出的、发到哪里去了"这条映射),这样逐级替换,最终数据包的源 IP 就变成了一个公网 IP。这种"替换/翻译地址"的技术,就叫 NAT(Network Address Translation,网络地址转换)。

请一定把这条逻辑想通:正因为有了私有地址 + NAT,几十亿台设备才能共享官方根本发不完的公网 IPv4 地址。你家里的手机、电脑、电视、门铃全挂在路由器的 192.168.1.x 私有网段里,对外露出的永远只有那一个公网 WAN 口 IP。这就是"58 亿 YouTube 访问量都出自一个 192.168 地址"这类段子的真相。

对开发者还有一个现实告诫:如果你希望自己实现的服务器程序能被公网访问到,就必须把程序部署在一台具有公网 IP 的服务器上——这类服务器可以在阿里云、腾讯云等云厂商处购买。你本机的 127.0.0.1 和局域网里的客户端电脑,别人在公网是摸不到的(除非做端口映射/NAT 穿透)。

如果你对"公网到底长什么样"感到好奇,可以去看一眼真实世界的 IP 分布地图(比如 ipshu 这类网站的 country-list 页面),你会看到 IP 是按地区、按运营商分配和管理的——这一切的顶层管理者是 ICANN(互联网名称与数字地址分配机构),往下按亚太、北美、欧洲等区域划分,各区域由区域互联网注册管理机构(如 APNIC、RIPE NCC)管理,再由国家 ISP 代理到千家万户。真实公网的拓扑极其复杂,我们这里把它简化成"层层嵌套、逐级外包",理解到这一步就够用了。

让我用一句话把这一段收束住:私有地址是为了不浪费公网地址,NAT 是为了让私有地址能"借"公网 IP 出门,两者是一对完美搭档。

路由:数据包是怎么"问路"到目的地的

讲完"地址怎么编号、怎么划分网段",最后一个大问题是:一个数据包在复杂网络里,怎么找到那条通往终点的路线? 这个"找路线"的过程,就叫路由(routing)。

逐跳问路:唐僧问路模型

用一个中国人耳熟能详的比喻来理解——唐僧取经路上的问路。唐僧不知道去西天的完整路线,但他每到一个地方,就向当地的土地、或当地的驿站打听:"从这里去西天该走哪个方向?" 对方告诉他"往东去某个城",他到了那个城再问一次……这样一段一段地,最终摸到西天。

路由的过程,正是这样一跳(Hop)一跳地问路的过程。这里的关键概念"一跳",是一个数据链路层的度量单位:具体在以太网里,指从源 MAC 地址到目的 MAC 地址之间的帧传输区间。换句话说,路由器每把一个数据包从一个物理端口送到下一个路由器,就算"跳"了一"跳"。数据包不可能一次跨越大半个地球——每次都只解决"这一跳该往哪走"。前面讲的 TTL,"每跳减一",正好对应这个"跳"的单位。

把唐僧问路映射到 IP 数据包的传输:

  • 当 IP 数据包到达一个路由器时,路由器先查看目的 IP;
  • 路由器据此决定:这个数据包是能直接发送给目标主机(说明目的地就在它下面的某个子网里),还是需要发送给下一个路由器(说明还得继续往下传);
  • 如此反复,一级一级转发,直到这个数据包被送到目标 IP 地址所在的那个网络。

那么路由器凭什么判断"该发到哪去"?答案就是:每个节点内部都维护着一张【路由表】。

路由表:节点的"问路指南"

路由表(routing table)是一张记录着"去哪、走哪"规则的表,它存在路由器(乃至每台主机)内部,是路由决策的依据。

在 Linux 上,你可以用 route 命令直接查看本机的路由表。完整的规则其实分两步:

  • 如果目的 IP 命中了路由表(网络号和某条目的匹配上了),就直接按这条规则转发即可;
  • 如果目的地址和路由表里其它行都匹配不上,就走最后一行"缺省路由"——按缺省路由条目规定的接口,把数据包发给它的下一跳地址。可以这样记:缺省路由就是"万不得已的最后退路"。

为了把转发过程讲得清清楚楚,我们虚构一台主机,它有多个网络接口,路由表配置如下(这是 Linux route -n 风格的输出):

Destination       Genmask          Gateway         Flags  Iface
192.168.10.0      255.255.255.0    0.0.0.0         U      eth0
192.168.56.0      255.255.255.0    0.0.0.0         U      eth1
default           0.0.0.0          192.168.10.1    UG     eth0

先解读表头——这会用到前面所有的知识:

  • Destination(目的网络地址):数据包该去的那个网段。注意这里存的是"网络地址"(主机位全 0),而不是某个具体主机。
  • Genmask(子网掩码):就是掩码,配合 Destination 一起用。
  • Gateway(网关 / 下一跳地址):要把数据包交给谁。0.0.0.0 表示"不需要经过路由器转发,目标就在本接口直连的网络上"。
  • Iface(发送接口):从哪块网卡把包发出去。
  • Flags(标志):U(Up)表示这一条目"有效"(管理员可以禁用某些条目,禁用后就失效);G(Gateway)表示这条目的下一跳地址是一个路由器的地址。没有 G 标志的条目,表示目的网络是与本机接口直接相连的,不必经路由器转发,直接发给目标主机就够了。

现在我们对着这张表,演算两个真实转发过程。

转发过程例 1:目的地址是 192.168.56.3

  1. 拿这个目的 IP 和第一行的子网掩码 255.255.255.0 做按位与:192.168.56.3 & 255.255.255.0 = 192.168.56.0。但第一行的 Destination 是 192.168.10.0,两者不符 → 不匹配,跳到下一行;
  2. 再跟第二行的子网掩码做按位与:192.168.56.3 & 255.255.255.0 = 192.168.56.0。这正好等于第二行的 Destination 192.168.56.0 → 匹配!于是从 eth1 接口发送出去;
  3. 再确认一下:192.168.56.0/24 正是与 eth1 接口直接相连的网络(没有 G 标志,Gateway 是 0.0.0.0),所以可以直接发到目标主机,不需要经路由器转发。

看,整个过程的判断工具,就是你剥好的那份"IP 与掩码按位与求网络号"的本事。之前练的网段计算,在这里直接投入使用。

转发过程例 2:目的地址是 202.10.1.2

  1. 依次拿它和前两行做按位与,发现都不匹配(202.10.1.2 & 255.255.255.0 = 202.10.1.0,既不等于 192.168.10.0,也不等于 192.168.56.0);
  2. 于是按缺省路由条目执行:从 eth0 接口发出去,发给它的下一跳 192.168.10.1(这正是第一行的 Gateway,也就是一块路由器 R1);
  3. 接下来,由 192.168.10.1 这台路由器根据它自己的路由表决定下一步的下一跳地址……如此接力,直到把数据包送上通往公网的路。

这两种例子,一个"直接送邻居",一个"甩给上级网关",恰好穷尽了主机路由决策的两大分支。

路由表生成算法(选学)

路由表的内容是从哪来的?总体分两类:

  • 静态路由:由网络管理员手动维护。优点是可预测、无额外开销;缺点是配置后不会自适应网络拓扑变化,一旦链路断了大佬就"装死"。
  • 动态路由:通过算法自动生成、自动更新。路由器之间会互相交换网络信息,路由表跟着拓扑实时变化,健壮性更强。

常见的生成算法,你可以课后自己调研:距离向量算法(如 RIP 所用,每台路由器只知道"到邻居多远",然后把信息层层广播)、链路状态算法(如 OSPF 所用,全网每台路由器都掌握完整拓扑图)、以及深挖起来最著名的 Dijkstra 算法(迪杰斯特拉算法,求单源最短路径的经典算法)。它们各有取舍,是分布式系统领域的关键一课,但属于"选学"范畴,这里点到为止。

IP 层的不可靠性:尽力而为

走到这里,值得停下来回答一个很多人会困惑的问题:IP 层到底可不可靠?答案不仅是否定的,而且是故意不可靠的——网络层遵循"尽力而为"(best effort)的设计哲学,它只负责"尽力把数据送出去",不做任何保证。

"不可靠"具体体现在三件事上:

  • 不保证送达:IP 只负责转发,不管目的地到底收到没有。数据包丢了,IP 不重传、不通知(除非被某台路由器主动丢弃时才会回一个 ICMP 错误,大多时候连信都不回)。
  • 不保证顺序:同一个报文的几个分片、或者同一客户端发出的若干数据报,走的路径可能不同,到达顺序可能被打乱。IP 不做排序,谁先到谁后到全凭运气。
  • 不保证不重复:当链路抖动、路由器短暂故障,可能出现超时重发的情形,接收端最终可能会收到重复的包。

那么"可靠"由谁来保证?答案是上一层。传输层的 TCP(Transmission Control Protocol,传输控制协议)就是专门补齐这三块短板的:它负责重传、排序、去重,并以此向上层提供一个"可靠的字节流"。而另一类传输层协议 UDP 则和 IP 一样"没心没肺",它本身也不做任何可靠性保证。换句话说,IP 的不可靠是一种刻意设计——把"可靠"这件事交给用得上的人(TCP 这种要可靠的)去自己做,让那些不需要可靠性(UDP,比如实时音视频)的场景不被多余的开销拖累。这就是我们下一讲讲传输层时的核心命题。

再顺带把 ARP 与 MAC 的关系讲清,因为它正好连接了网络层和链路层。注意你可能会疑惑:前面的路由决策都是"看目的 IP 决定下一跳",可下一跳最终总得在物理链路上发出去,而链路层认的是 MAC 地址(Media Access Control address,即网卡的物理地址),对 IP 毫无概念。那么"IP 决定去哪"和"MAC 决定怎么发"之间,就必须有一座"翻译"的桥——这座桥就是 ARP(Address Resolution Protocol,地址解析协议)。

一句话总结 ARP 的使命:它负责把"下一跳的 IP 地址"翻译成"目的网络里那块网卡的物理地址(MAC)"。 具体过程是:当一台主机要往同一链路内的某个 IP 发数据时,它会先在链路上广播一个"谁的 IP 是 X,请把你的 MAC 告诉我",拿到对方的 MAC 后,才能把网络层的数据报封进一个以对方 MAC 为目的的帧里送出去。而跨网段时,主机并不去解析目标主机的 MAC,而是去解析**网关(下一跳路由器)**的 MAC,把包先交给网关。理解了这一层,你就把"网络层靠 IP 定路线、数据链路层靠 MAC 跑最后一公里"这两层的关系彻底想通了。

字符串拼接:从"网段"到"路由表"到"现实"

到这一步,你可以把本讲所有核心概念串成一条完整的链路了:

网络号 + 主机号 = 一个 IP 地址
      ├─ 子网掩码决定网络号与主机号的边界
      ├─ CIDR 记法(如 /24) 简写掩码
      ├─ 私有网段(10/172.16-31/192.168) + NAT ≈ 缓解地址枯竭的功臣
      └─ 每个子网内有网络地址、可用主机、广播地址的边界规矩
                       ↓
每台设备(节点) 内部维护一张 路由表
      ├─ 用"目的IP 与 掩码"按位与 → 得到网络号 → 查表
      ├─ 命中 → 直接转发 / 发给下一跳
      └─ 兜底 → 走默认路由(缺省路由)
                       ↓
数据报每经过一个路由器(一跳) TTL 减一,防路由循环

这一条链路,就基本讲完了"网络层"的全部主干。

自测题(带详解)

好,正文到此。下面用一组思考题检验你这一课的成色,每道题我都给了完整推导,请先自己想,再对答案。

Q1. 一个 /16 的子网内,最多能分配给主机多少个 IP 地址?请写出计算过程,并特别强调最容易漏掉的那 2 个地址。

详解:

/16 表示子网掩码高 16 位是 1,也就是 255.255.0.0,网络号占 16 位,主机号占 32 − 16 = 16 位。主机号能表达的总地址数是 2^16 = 65536 个。

但其中有 2 个被协议占用:主机号全 0 的是网络地址(代表这个子网本身),主机号全 1 的是广播地址。所以可分配给主机的地址数为:

65536 − 2 = 65534 个。

最容易漏掉的,正是"网络地址"和"广播地址"这两个边界——同理,/24 是 256 − 2 = 254,/8 是 16777216 − 2 = 16777214。任何子网算可用主机数,都必须减去这两个,一条缝都不许少。

Q2. 子网掩码 255.255.255.128 对应的前缀长度是多少?这个子网里主机号占几位?可分配主机地址有多少个?

详解:

255.255.255.128 的二进制是 11111111.11111111.11111111.10000000。数一数前面的连续 1:前三个都是 8 个 1,加第四个的前 1 个 1,共 8 + 8 + 8 + 1 = 25 位。所以前缀长度是 /25。

主机号位数 = 32 − 25 = 7 位,子网内地址总数 2^7 = 128 个。

可用主机数:128 − 2 = 126 个(再扣掉网络地址与广播地址)。注意:如果这个子网的地址范围是比如 192.168.1.0 ~ 192.168.1.127,则 192.168.1.0 是网络地址、192.168.1.127 是广播地址,中间可用的确切范围就是 192.168.1.1 ~ 192.168.1.126。

Q3. 为什么 IP 首部的分片偏移要以"8 字节"为单位,而不是直接记录"字节偏移"?结合字段位数解释。

详解:

分片偏移字段只占 13 位,它能表示的最大值是 2^13 − 1 = 8191。如果以字节为单元记录偏移,最多只能表达 8191 字节的偏移;但 IP 数据报最长可达 2^16 − 1 = 65535 字节(总长度字段是 16 位),超出 13 位能覆盖的范围。把单位放大到 8 字节后,最大可表达 8191 × 8 = 65528 字节的偏移,勉强覆盖住 65535 字节的报文总范围。这是"13 位精度够不够"的数学必然。

由此又推出一条硬约束:既然偏移必须是 8 的整数倍(否则除以 8 会得到小数、无法存储),那么除最后一个分片外的每个分片,长度都必须是 8 的整数倍,否则分片之间就对不齐、无法正确重组。

Q4. TTL 字段的作用是什么?为什么说它"防路由循环"?如果一个数据报从发送到到达共经过 10 个路由器,发送时 TTL 设为 64,那到达时 TTL 还剩下多少?

详解:

TTL(Time To Live,生存时间)表示数据报最多还能经过多少个路由器(多少跳),每经过一个路由器,IP 模块执行 TTL -= 1,减到 0 还没到达就被丢弃。

"防路由循环"的原因:如果路由配置出错,两个路由器互相把对方当作下一跳,数据报就会在两者之间无限往返。如果没有 TTL,这个数据报会永远兜圈,把链路带宽和 CPU 资源耗尽。有了 TTL,每转一圈减一,最多几十次后归零被丢弃,环路被自动掐断。

跳数计算:数据报从发送方出发,到到达目标主机,中途经过 10 个路由器。注意——发送方主机不是路由器,TTL 在出发时不做减法;逐跳递减发生在"每经过一个路由器"时。所以到达目的地时已减去 10,剩余 64 − 10 = 54。(若目光更严谨:严格是"中途路由器减,最终目标主机每个也会减一",但题意默认到目标主机整个路径就是减了经过的总跳数。)

Q5. 私有地址和公网地址有什么区别?NAT 在"公网访问私网服务器"这件事上起了什么作用?

详解:

私有地址是 RFC 1918 指定的局域网专用网段(10.0.0.0/8、172.16.0.0/12、192.168.0.0/16),不具公网唯一性,不可在全球公网路由上直接寻址;公网地址是在全球唯一、可被互联网直接寻址的合法地址。

NAT 的作用:当私有网的主机要访问公网时,路由器把出站数据包的源 IP 换成自己的公网 WAN 口 IP,并记录映射关系;公网回的包再用 公网 IP + 端口 → 私有 IP + 端口 的映射反查,转发给原主机。这样一台路由器下面的所有私网设备,对外都"共用"路由器那一个公网 IP——本质是用"IP + 端口"的多对一复用,大幅缓解公网 IPv4 枯竭。

一个现实的边界(也是开发者的坑):NAT 是"家里出、公网进"单向友好的。如果你希望公网主动访问你私网里的服务器,光有 NAT 还不够——还需要在路由器上做端口映射(把公网的某个端口专门转发到内网某台主机的某端口),或者借助 NAT 穿透等技术,否则外界的包根本不知道该交给内网里的谁。

Q6. 假设你手头要规划一个部门网络,一共需要 400 台主机,最少应该申请多大的地址块?请用 CIDR 计算。

详解:

400 台主机,需要主机号位长 n 满足:2^n ≥ 400 + 2(加上网络地址和广播地址,它们不分配给主机)。2^8 = 256 < 402,2^9 = 512 ≥ 402,所以主机号至少要 9 位。则前缀长度 = 32 − 9 = 23。因此最少应该申请 /23 的地址块,可用主机地址 512 − 2 = 510 台(510 ≥ 400,满足)。如果贪心申请 /22(2^10 = 1024)也算够但会浪费更多;申请 /24(256 可用)则铁定不够用——别忘了减 2 的规矩在这里又教了一课。

小结与下一站

到这儿,网络层的主干知识就收束完毕了。我们来一起温一遍这趟旅程的轨迹:先认识了主机、路由器、节点三个角色;接着把 IP 数据报首部从头到尾拆了一遍,从版本到总长度,再从标识、标志、分片偏移的"分片三件套"讲到 TTL 的防循环使命;然后进入重头戏网段划分——网络号和主机号、分类编址的先天缺陷、CIDR 与子网掩码的按位与、斜杠记法、特殊的网络/广播/环回地址,以及私有地址 + NAT 如何拯救 IP 枯竭;接着顺着路由表走完了"逐跳问路"到"缺省路由"的完整决策流程,点破了 IP"尽力而为"的不可靠本质,也借 ARP 看懂了"IP 定路线、MAC 跑最后一公里"的两层配合。

下一讲,我们会把目光从"网络层"上调一层,走进传输层——看看 TCP 和 UDP 是如何在 IP 这条"尽力而为"的链路上,为上面的应用提供"可靠传输"或者"省心快传"的。如果你把这一章的"网段计算"和"路由表查表"这两门手艺练熟了,那相当于已经拿到了理解整个 TCP/IP 栈的两把钥匙。我们在传输层见。