← 全部笔记

前置知识 · 计算机网络

IP、端口与 NAT

IP 让包到达目标网络接口,端口让 TCP 找到接收连接的套接字;NAT 会改写途中包的地址字段。

约 15 分钟 · 截至 2026-11-03,Docker Engine 29.x · Linux 实验已实测,Docker 实验未实测 · 本页聚焦 IPv4 / TCP

目的 IP 和端口一起定位监听端点 客户端发送到 TCP 192.168.1.20:8080,包到达服务器的接口,再匹配应用的监听套接字。 客户端 → TCP 192.168.1.20:8080 目的 IP 定位接口 192.168.1.20 TCP :8080 服务器接口 应用监听
图 1 目的 IP 与 TCP 端口共同决定连接交给哪里;地址为教学设定。

可先复习 TCP 三次握手;容器概念见 容器与虚拟机。

地址范围

前缀划定子网

IP 地址配置在网络接口上。接口是系统收发网络包的入口;同一台机器可以有多个接口,也可以有多个 IP 地址。

IPv4 地址有 32 个二进制位,通常写成四段十进制数。子网是一组共享地址前缀的地址;/24 表示前 24 位是网络部分,剩下 8 位可变化。这种“地址/前缀长度”写法叫 CIDR(无类别域间路由)表示法。[1]

把一个 /24 拆成两个 /25 192.168.1.0/24 包含末段 0 至 255,拆成 /25 后分成 0 至 127 和 128 至 255。末段 20 和 200 分属不同 /25。 192.168.1.0/24:256 个地址 192.168.1. 前 24 位固定,最后 8 位变化 增加 1 位前缀 .0/25.128/25 0–127 · 含 .20 128–255 · 含 .200
图 2 前缀越长,范围越小;两段的宽度分别表示 128 个地址,合计 256 个。

在普通 /24 子网里,192.168.1.0 是网络地址,192.168.1.255 是广播地址,通常不分给主机。判断同子网时,必须带上前缀长度,不能只看“前三段像不像”。

两台主机是 192.168.1.20 和 192.168.1.200,配置相同前缀。请填“是”或“否”。

用 /24 时同子网:;用 /25 时同子网:。

/24 覆盖末段 0–255,所以同子网。/25 把范围一分为二,20 落在 0–127,200 落在 128–255,所以不同子网。

深入:同子网就一定能连通吗?

同子网只说明地址如何划分。还要有可用的链路、正确配置,以及允许通信的防火墙。跨子网的包按路由表转发;路由表是一组“目标地址范围 → 下一跳”的规则,默认网关是没有更具体路由时使用的下一跳路由器。

普通广播子网常用 2^(32−前缀长度)−2 算可分配地址数;/31 点对点链路与 /32 单地址有特殊用途,不套这个减二公式。

接收范围

监听决定接收范围

端口是 TCP、UDP 等传输协议里的数字字段。TCP 与 UDP 是两种传输协议,端口空间分别计算;本页只跟踪 TCP。套接字(socket)是应用向操作系统收发数据的接口;TCP 服务绑定本地地址和端口,再进入监听(LISTEN)状态,等待新连接。[2]

127.0.0.1 是回环地址,包在当前网络环境内返回自己;0.0.0.0 用于监听时,表示当前网络环境的所有本地 IPv4 地址。客户端应连接真实目标地址,不应把它当成服务器地址。

只绑定回环地址监听 127.0.0.1:18080 时,目的地址 127.0.0.1 匹配,目的地址 192.168.1.20 不匹配。 监听 127.0.0.1:18080 目的 127.0.0.1 匹配 目的192.168.1.20 不匹配
图 3 绑定回环地址的监听,不接收发往局域网接口地址的连接。
绑定所有本地 IPv4 地址监听 0.0.0.0:18080 时,目的地址为本机回环或局域网接口地址都可以匹配,实际连通仍受路由和防火墙影响。 监听 0.0.0.0:18080 目的 127.0.0.1 匹配 目的192.168.1.20 匹配
图 4 绑定所有本地 IPv4 地址扩大匹配范围;图中均为 TCP 端口 18080。

图中假定 192.168.1.20 配置在本机。只绑定这个具体地址时,仅匹配发往它的连接。匹配监听不等于一定可达,还要检查路由与防火墙。

服务只监听 127.0.0.1:18080。另一台机器连接这台服务器的 192.168.1.20:18080,把端口号写对就够了吗?为什么?

不够。目的端口相同,但目的 IP 不匹配监听地址。直接接收局域网连接,需要绑定局域网接口地址或所有本地 IPv4 地址;同时确认路由与防火墙允许。

要在当前网络环境接收发往所有本地 IPv4 地址的 TCP 18080 连接,应监听哪里?

回环监听只匹配当前网络环境里的回环目的地址。

这是单个本地接口地址,不代表其他本地 IPv4 地址。

0.0.0.0 在绑定监听时是通配地址。它覆盖当前网络环境的所有本地 IPv4 地址,但不会替你放行防火墙,也不会跨入另一个容器的网络环境。

深入:为什么两个服务都能用端口 80?

监听匹配要看协议、本地 IP 和端口,也要看所在网络命名空间。网络命名空间是 Linux 隔离接口、路由和套接字的机制;不同容器通常有各自的网络命名空间,所以可以分别监听自己的 TCP 80。同一环境、同一协议下,相同地址和端口的重复绑定通常会冲突;复用套接字的特殊配置超出本页范围。

IPv6 的回环地址是 ::1,通配监听地址是 ::。IPv6 监听是否同时接收 IPv4,受套接字选项和系统设置影响,不能只看一个“80”就下结论。

Linux 实验 · 约 3 分钟

实测地址与监听

在同一台 Linux 机器上开两个终端。需要 iproute2(提供 ip、ss)和 Python 3。以下在编写环境实测;普通用户即可完成。

先看地址。ip addr 会列出接口地址;下面加 -4 和 dev lo,只看 IPv4 回环接口。[3]

ip -4 addr show dev lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever

你应看到 inet 后的地址和前缀。接口序号、MTU 等字段可能不同;lo 是回环接口的名字。要找自己的局域网地址,运行 ip -4 addr,看其他接口的 inet 行,别照抄示意图的 IP。

终端 A:在一个可公开的空目录运行临时 HTTP 服务。它会把该目录的文件提供给请求方;此处只绑定回环,不提供给局域网。

python3 -m http.server 18080 --bind 127.0.0.1

终端 B:先运行 ss -lnt,再加过滤条件,只看本次实验。-l 只看监听,-n 保留数字地址与端口,-t 只看 TCP。[3]

ss -lnt '( sport = :18080 )'
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 5 127.0.0.1:18080 0.0.0.0:*

重点读 Local Address:Port。这里的 Peer Address:Port 为 0.0.0.0:*,表示监听套接字还没有固定对端,不能据此推断“对外监听”。队列列的数值可能因系统和程序版本而异。

可在终端 B 用浏览器访问 http://127.0.0.1:18080/,或运行下面的客户端;本环境实际输出为 200,表示 HTTP 请求成功。

python3 -c 'import urllib.request; print(urllib.request.urlopen("http://127.0.0.1:18080/").status)'
200

在终端 A 按 Ctrl + C 停止,再改为:

python3 -m http.server 18080 --bind 0.0.0.0
扩大可达范围

此命令会监听所有本地 IPv4 地址。请只在可信网络、空目录里短时测试;它不修改路由或防火墙。测完按 Ctrl+C 关闭,不要把敏感文件目录作为服务目录。

终端 B 再执行同一条 ss 命令。下面是第二次实测输出:

ss -lnt '( sport = :18080 )'
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 5 0.0.0.0:18080 0.0.0.0:*

两次输出的对端列相同。请解释:为什么第二次监听范围更大?你依据的是哪一列?

应看本地地址列。127.0.0.1 只绑定回环,0.0.0.0 绑定所有本地 IPv4 地址。对端列的通配符描述尚未固定的对端,不能替代本地绑定信息。

  • 指出 Local Address:Port
  • 区分回环绑定与通配绑定
  • 没有把对端通配符当作对外开放证据

若提示地址已占用,先停止自己先前的服务,或统一换成其他空闲端口。此实验只验证监听和回环 HTTP 请求,没有实测跨机器访问或 NAT。

出站与回包

NAT 改写包地址

私网地址可在各自网络内重复使用,不能作为全球唯一地址在公网路由。RFC 1918 保留了下面三段;172. 开头并不全是私网。[4]

前缀完整范围
10.0.0.0/810.0.0.0–10.255.255.255
172.16.0.0/12172.16.0.0–172.31.255.255
192.168.0.0/16192.168.0.0–192.168.255.255

网络地址转换(Network Address Translation,NAT)在转发时改写地址。多台私网主机共享一个外部 IP 时,常同时转换 TCP/UDP 端口,称 NAPT(网络地址端口转换)。下面展示这种常见模型。[5]

图中把“IP:端口”称为端点。源端点表示谁发来,目的端点表示发给谁。公网角色采用专供文档的地址,不能拿来测试实际联网。[6]

出站 NAT 与回包还原的六步图 私网客户端 192.168.1.20:40000 访问 198.51.100.20:80。网关把源端点改成 203.0.113.10:62001,记录映射,回包时把目的端点还原为客户端。所有数值均为教学设定。 内网客户端 → NAT 网关 → 外部服务器 以下均为 TCP · 数字为教学设定 ① 内网请求 · 源 192.168.1.20:40000 目的 198.51.100.20:80 ② 网关记表 · 192.168.1.20:40000 映射为 203.0.113.10:62001 ③ 外网请求 · 源 203.0.113.10:62001 目的仍是 198.51.100.20:80 ④ 服务器回包 · 源 198.51.100.20:80 目的 203.0.113.10:62001 ⑤ 网关查原映射 · 还原回包目的端点 ⑥ 交回内网 · 目的 192.168.1.20:40000
  1. 客户端发起连接,源端口 40000 由教学场景设定。先猜:离开网关时,源与目的哪个端点会变?
  2. 网关建立转换记录,把内网源端点映射为外部端点;62001 是示意分配值。
  3. 发向服务器的包改了源 IP 和源端口,目的端点保持不变;服务器看到的是网关的外部端点。
  4. 服务器把回包发给 203.0.113.10:62001。先猜:网关凭什么知道回给内网哪台机器?
  5. 网关根据这条连接的转换记录,把回包目的端点还原;回包的源仍是服务器。
  6. 包交回 192.168.1.20:40000,这次请求与响应形成闭环。
图 5 出站改源、回包还原目的;网关保存转换关系,才能把响应交回发起者。

图中包含端口改写,但 NAT 不保证每次都换端口。只要不冲突,转换设备可能保留原端口。不能把图中的 62001 当成 Docker 或路由器的固定分配规则。

深入:NAT 和防火墙分别负责什么?

NAT 负责地址或端口改写,防火墙负责按规则允许或丢弃流量。没有现成映射时,外部新连接通常无法仅凭一个共享外部 IP 确定内网接收者;静态映射可以提供明确入口,但仍需防火墙允许。NAT 不等于访问控制,也不等于加密。

本图省略链路层、校验和与具体连接跟踪实现。路由负责选择下一跳,NAT 负责改写字段;跨子网转发并不必然需要 NAT。

衔接模块 5

映射提供明确入口

要让外部主动访问内网服务,可以预先指定映射。目的地址转换(Destination NAT,DNAT)将到达入口的包改写为发往内部服务。普通路由转发可以保留目的 IP;下面的映射会改写目的端点。

到达宿主机 · TCP · 教学设定

源 192.168.1.50:40000

目的 192.168.1.20:8080

↓ 按映射规则改写目的 IP 与端口

转发给容器 · TCP

源 192.168.1.50:40000

目的 172.18.0.2:80

图 6 入口 8080 与内部服务 80 可以不同;本图只展示 Linux bridge 默认 NAT 场景的入站目的改写。

Docker 的 -p 8080:80 表示“宿主机端口 8080 → 容器 TCP 端口 80”,默认向宿主机所有地址发布。Docker 的 bridge(桥接)网络默认使用 NAT;容器出站常使用宿主机地址。[7]

这条映射不会启动应用,也不会修改应用的监听地址。容器内应用若只监听自己的 127.0.0.1:80,转发到容器接口地址 172.18.0.2:80 的包仍不匹配该监听。

按图 6 的规则,客户端应连接宿主机端口 ;经过目的改写后,包的目的端口是 。

客户端知道宿主机入口 8080。Docker 把它映射到容器端口 80,应用要在容器可接收该包的地址上监听 80。这两个端口分别属于两侧端点。

可复制的 Docker 小实验

未实测 编写环境没有 Docker,以下提供命令和预期现象,不提供虚构输出。适用 Docker Engine 29.x 的默认 bridge / NAT 配置;需要 Docker、联网拉取镜像,以及空闲的本机 18081 端口。

在宿主机终端 A 运行。--rm 表示服务退出后自动删除容器;-p 127.0.0.1:18081:8000 把入口限制在宿主机回环地址。容器内 Python 监听 0.0.0.0:8000,匹配容器自己的接口地址。[7][8]

docker run --rm -p 127.0.0.1:18081:8000 python:3.13-alpine \
  python -m http.server 8000 --bind 0.0.0.0

宿主机终端 B 运行同样的 Python 客户端,但连接宿主机入口:

python3 -c 'import urllib.request; print(urllib.request.urlopen("http://127.0.0.1:18081/").status)'

预期现象(未实测):服务就绪后,客户端应打印 200(HTTP 成功状态码)。这是按 Python HTTP 服务行为推导的预期,维护者待补实际输出。容器服务的日志应出现本次请求。在终端 A 按 Ctrl+C 停止,容器随后自动删除;拉取的镜像会保留。

宿主机网络变化

默认 Linux bridge 模式下,Docker 会为容器准备虚拟网络连接,并为端口发布添加转发与防火墙规则;容器删除后清理相关资源。首次使用还可能初始化默认网桥。本实验无需 --privileged,也不要求你手工修改 iptables;不要改成 --network host。

深入:为什么 ss 不足以证明端口已映射?

ss 查看当前网络命名空间的套接字。Docker 端口发布可能通过内核转发规则实现,也可能涉及用户态代理;宿主机不一定有一个与你预想完全相同的监听套接字。是否发布可用 docker ps 看 PORTS,再用实际请求验证。

Docker Desktop 的 Linux 容器运行在虚拟机中,宿主机入口由 Desktop 的转发层接入;宿主机上的 ip/ss 不等同于 Linux Docker 主机内部视图。rootless(以非 root 运行的 Docker)也有额外转发机制。图 6 仅用于理解 Linux bridge 默认 NAT 的目的字段变化;routed 等网络模式可不使用 NAT,具体实现留到模块 5。参见 Desktop 网络说明与 rootless 模式说明。

回顾与迁移

沿端点逐层检查

图 5 的服务器回包到达网关时,目的端点是 203.0.113.10:62001。还原并发回内网后,哪个端点正确?

这是回包在网关外侧的目的端点,内网客户端不使用这个地址。

这是服务器端点;在回包里它是源,不是目的。

网关按转换记录,把外部端点对应回原发起者 192.168.1.20:40000。回包的目的端点还原了,源端点仍为服务器 198.51.100.20:80。

为模块 5 做准备:Docker 端口已经发布,容器内服务却只监听 127.0.0.1。请用“包的目的端点”和“监听地址”解释为什么仍可能访问失败。

端口发布把流量送到容器接口地址和目标端口。容器自己的回环地址只接收本环境的回环流量,无法匹配送到接口地址的连接。要检查应用绑定地址,并继续排查路由和防火墙。

  • 指出宿主机入口与容器目的端点不同
  • 指出回环监听不匹配容器接口地址
  • 没有把发布端口当作已经启动或重新配置应用

来源

核对日期:2026-11-03。Docker 部分按 Engine 29.x 的默认 bridge / NAT 场景讲解。Linux 输出来自本环境 iproute2 6.15.0 与 Python 服务实验;Docker 实验未实测。

  1. RFC 4632 §3.1:IPv4 前缀与 CIDR 表示法。
  2. Linux ip(7):地址、端口、回环与通配绑定语义。
  3. ip-address(8)、ss(8):地址显示、监听选项和过滤表达式。
  4. RFC 1918 §3:三段私有 IPv4 地址空间。
  5. RFC 3022 §2.2、§3:NAPT 与双向转换。
  6. RFC 5737 §3:文档示例地址,不用于真实公网访问。
  7. Docker:Port publishing、Bridge network driver:端口发布、回环绑定、默认 NAT 与其他网关模式。
  8. Docker:docker container run:--rm、-p 的语义。
  9. Python 3.13 http.server 文档:命令行端口、绑定地址与目录服务;Docker Official Images 的 Python 标签登记:实验镜像标签依据。