【计算基础|网络14】网络层:IP、ICMP 与 ping

【计算基础|网络14】网络层:IP、ICMP 与 ping

你能 ping 通 www.baidu.com,但你说得出这个"通"字背后发生了什么吗?数据包为什么知道下一跳往哪走?TTL 是干嘛的?为什么断网了 ping 127.0.0.1 照样通?为什么面试爱问"ICMP 封装在哪个协议里"?

这篇把网络层的主干一次讲透:IP 负责寻址和路由,ICMP 负责报错和探测,ping/traceroute 是它们的具体应用。不讲虚的,每个字段对着真实报文讲,每个原理都有本机实测输出。

环境:macOS(BSD 用户态),ping、traceroute、netstat -rn、ifconfig 均为系统自带;Python 3.10+ 标准库 ipaddress、socket。Linux 对应命令为 ip addr、ip route、route -n。


一、IP 协议:只负责"把信送到",不保证送到

1.1 IP 的三个性格

结论先给:IP 是不可靠、无连接、尽最大努力交付的协议。

性格 含义 推论
无连接 收发双方不先建立连接,每个数据报独立路由 后发的包可能比先发的先到
不可靠 不确认、不重传、不保证顺序 丢了就是丢了,可靠性靠上层(TCP)补
尽最大努力 路由器不随便丢包,但拥堵/出错就丢 丢包是常态,不是 bug

说白了,IP 就像普通邮递:信封上写着收发件人地址(IP 首部),邮局尽力投递,丢了不赔,包裹顺序不管。保证可靠、按序、不丢,是 TCP 的活,不是 IP 的活------这也是为什么 UDP 直接跑在 IP 上就是不可靠的。

IP 层干两件事:寻址 (靠 IP 地址标识每台主机)和路由(决定每个包下一跳交给谁)。

1.2 IPv4 首部逐字段

一个 IPv4 数据报 = 首部 + 数据(payload)。普通首部 20 字节,最长 60 字节(带选项)。逐字段拆解:

字段 长度 作用 你需要知道的
版本 Version 4 bit 协议版本 IPv4 = 4,IPv6 = 6
首部长度 IHL 4 bit 首部长度,单位 4 字节 普通=5(20字节),最大=15(60字节)
服务类型 TOS/DSCP 8 bit QoS 标记,区分服务 现代主要用 DSCP 做流量分级,开发很少碰
总长度 Total Length 16 bit 整个 IP 数据报长度(首部+数据) 最大 65535 字节
标识 Identification 16 bit 同一个数据报的分片共用同一标识 重组时靠它认亲
标志 Flags 3 bit DF / MF DF=1 禁止分片;MF=1 后面还有分片
片偏移 Fragment Offset 13 bit 分片在原数据报中的位置,单位 8 字节 所以分片长度必须是 8 的倍数
生存时间 TTL 8 bit 每过一个路由器减 1,到 0 丢弃 防止包在环路里无限打转;traceroute 靠它
协议 Protocol 8 bit 上层协议号 ICMP=1,TCP=6,UDP=17
首部校验和 Header Checksum 16 bit 只校验首部 每跳重算(因为 TTL 变了)
源 IP 地址 32 bit 发送方地址
目的 IP 地址 32 bit 接收方地址
选项 Options 可变 时间戳、记录路由等 很少用,安全上还常被过滤

🔴 必须背下来的三个协议号:ICMP=1、TCP=6、UDP=17。这是 IP 把 payload 交给谁的"投递标签"------网卡驱动收到 IP 包后,看这一字段决定交给 ICMP 模块、TCP 模块还是 UDP 模块。

1.3 IPv4 地址与特殊地址

IPv4 地址 32 位,点分十进制表示(如 192.168.1.130),由网络号 + 主机号两部分组成。开发中你会反复遇到下面这张特殊地址表:

地址段 名称 用途
0.0.0.0 本网络/任意地址 服务端绑定它表示监听本机所有网卡
127.0.0.0/8 环回地址 本机协议栈内部回环,不经过网卡 ,常见 127.0.0.1
10.0.0.0/8 私有地址 内网,不可在公网路由
172.16.0.0/12 私有地址 内网
192.168.0.0/16 私有地址 家里路由器最常见
169.254.0.0/16 链路本地 DHCP 失败时自动分配,插网线却没网时常见
224.0.0.0/4 组播 一对多,如 mDNS、视频直播
255.255.255.255 广播 局域网内所有人收

私有地址三段(10/8、172.16/12、192.168/16)合称 RFC 1918 地址,需要 NAT 才能上公网------这也是为什么你家手机电脑看到的都是 192.168.x.x,而不是公网 IP。


二、子网划分与 CIDR:把大网切成小网

2.1 为什么要划分子网

一个大 /24(254 台主机)全在一个广播域里,问题有三:广播包到处飞、IP 浪费、安全和管理难隔离。划分子网就是把主机位借几位当网络位,把一个大网切成多个小网。

2.2 子网掩码与 CIDR 记法

  • 子网掩码 :网络位全 1、主机位全 0。/24 = 255.255.255.0。
  • CIDR(无类别域间路由) :直接写 IP/前缀长度,如 192.168.1.0/24,比以前按类(A/B/C 类)灵活得多。

2.3 手工计算:给定 IP 和掩码,求四件套

口诀:IP 和掩码做按位与,得到网络地址;主机位全 1,得到广播地址;中间是可用主机;可用主机数 = 2^(主机位数) - 2(减网络地址和广播地址)。

拿题目要求的例子手算 192.168.1.130/26:

  • /26 = 掩码 255.255.255.192,即前 26 位是网络位,后 6 位是主机位。
  • 第四段 130 = 1000 0010,与 192(1100 0000)按位与 = 1000 0000 = 128 → 网络地址 192.168.1.128。
  • 主机位(后 6 位)全 1:第四段变成 1011 1111 = 191 → 广播地址 192.168.1.191。
  • 可用主机:192.168.1.129 ~ 192.168.1.190。
  • 主机数:2^6 - 2 = 62 台。

2.4 用 Python 验证(别手算了,用库)

工程上别手算,标准库 ipaddress 直接出答案。我本机实跑:

python 复制代码
import ipaddress

net = ipaddress.ip_network("192.168.1.130/26", strict=False)
print("网络地址:", net.network_address)
print("广播地址:", net.broadcast_address)
print("掩码:", net.netmask, " 前缀长度:", net.prefixlen)
hosts = list(net.hosts())
print("可用主机范围:", hosts[0], "-", hosts[-1])
print("可用主机数:", net.num_addresses - 2)

n24 = ipaddress.ip_network("10.0.0.0/24")
print("10.0.0.0/24 ->", "网络:", n24.network_address, "广播:", n24.broadcast_address, "可用主机数:", n24.num_addresses - 2)

print("192.168.1.200 in 192.168.1.128/26 ?", ipaddress.ip_address("192.168.1.200") in net)
print("192.168.1.150 in 192.168.1.128/26 ?", ipaddress.ip_address("192.168.1.150") in net)

实测输出:

text 复制代码
网络地址: 192.168.1.128
广播地址: 192.168.1.191
掩码: 255.255.255.192  前缀长度: 26
可用主机范围: 192.168.1.129 - 192.168.1.190
可用主机数: 62

10.0.0.0/24 -> 网络: 10.0.0.0 广播: 10.0.0.255 可用主机数: 254
192.168.1.200 in 192.168.1.128/26 ? False
192.168.1.150 in 192.168.1.128/26 ? True

⚠️ strict=False 表示"这个 IP 不一定是网络地址也接受",否则 ip_network("192.168.1.130/26") 会直接报错------130 不是该网段的网络地址。


三、IP 分片与重组:包太大怎么办

3.1 MTU 与分片

MTU(最大传输单元) 是一条链路层帧能装下的最大 IP 数据报,以太网默认 1500 字节。当 IP 数据报长度超过下一跳 MTU 时:

  • DF=0(允许分片):路由器把它切成几个小片,每个片独立走路由,在目的主机重组。
  • DF=1(禁止分片):路由器直接丢弃,并回一个 ICMP"需要分片但 DF 置位"(类型 3 代码 4)。

分片靠三个字段协同:

字段 作用
标识 Identification 同一数据报的所有分片标识相同,目的端靠它拼回去
MF(More Fragment) MF=1 表示后面还有分片;最后一个分片 MF=0
片偏移 该分片数据在原报文中的位置,单位 8 字节

#mermaid-svg-Jn7CmL1LLSAOhwuL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Jn7CmL1LLSAOhwuL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Jn7CmL1LLSAOhwuL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Jn7CmL1LLSAOhwuL .error-icon{fill:#552222;}#mermaid-svg-Jn7CmL1LLSAOhwuL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Jn7CmL1LLSAOhwuL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Jn7CmL1LLSAOhwuL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Jn7CmL1LLSAOhwuL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Jn7CmL1LLSAOhwuL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Jn7CmL1LLSAOhwuL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Jn7CmL1LLSAOhwuL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Jn7CmL1LLSAOhwuL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Jn7CmL1LLSAOhwuL .marker.cross{stroke:#333333;}#mermaid-svg-Jn7CmL1LLSAOhwuL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Jn7CmL1LLSAOhwuL p{margin:0;}#mermaid-svg-Jn7CmL1LLSAOhwuL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Jn7CmL1LLSAOhwuL .cluster-label text{fill:#333;}#mermaid-svg-Jn7CmL1LLSAOhwuL .cluster-label span{color:#333;}#mermaid-svg-Jn7CmL1LLSAOhwuL .cluster-label span p{background-color:transparent;}#mermaid-svg-Jn7CmL1LLSAOhwuL .label text,#mermaid-svg-Jn7CmL1LLSAOhwuL span{fill:#333;color:#333;}#mermaid-svg-Jn7CmL1LLSAOhwuL .node rect,#mermaid-svg-Jn7CmL1LLSAOhwuL .node circle,#mermaid-svg-Jn7CmL1LLSAOhwuL .node ellipse,#mermaid-svg-Jn7CmL1LLSAOhwuL .node polygon,#mermaid-svg-Jn7CmL1LLSAOhwuL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Jn7CmL1LLSAOhwuL .rough-node .label text,#mermaid-svg-Jn7CmL1LLSAOhwuL .node .label text,#mermaid-svg-Jn7CmL1LLSAOhwuL .image-shape .label,#mermaid-svg-Jn7CmL1LLSAOhwuL .icon-shape .label{text-anchor:middle;}#mermaid-svg-Jn7CmL1LLSAOhwuL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Jn7CmL1LLSAOhwuL .rough-node .label,#mermaid-svg-Jn7CmL1LLSAOhwuL .node .label,#mermaid-svg-Jn7CmL1LLSAOhwuL .image-shape .label,#mermaid-svg-Jn7CmL1LLSAOhwuL .icon-shape .label{text-align:center;}#mermaid-svg-Jn7CmL1LLSAOhwuL .node.clickable{cursor:pointer;}#mermaid-svg-Jn7CmL1LLSAOhwuL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Jn7CmL1LLSAOhwuL .arrowheadPath{fill:#333333;}#mermaid-svg-Jn7CmL1LLSAOhwuL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Jn7CmL1LLSAOhwuL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Jn7CmL1LLSAOhwuL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Jn7CmL1LLSAOhwuL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Jn7CmL1LLSAOhwuL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Jn7CmL1LLSAOhwuL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Jn7CmL1LLSAOhwuL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Jn7CmL1LLSAOhwuL .cluster text{fill:#333;}#mermaid-svg-Jn7CmL1LLSAOhwuL .cluster span{color:#333;}#mermaid-svg-Jn7CmL1LLSAOhwuL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Jn7CmL1LLSAOhwuL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Jn7CmL1LLSAOhwuL rect.text{fill:none;stroke-width:0;}#mermaid-svg-Jn7CmL1LLSAOhwuL .icon-shape,#mermaid-svg-Jn7CmL1LLSAOhwuL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Jn7CmL1LLSAOhwuL .icon-shape p,#mermaid-svg-Jn7CmL1LLSAOhwuL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Jn7CmL1LLSAOhwuL .icon-shape .label rect,#mermaid-svg-Jn7CmL1LLSAOhwuL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Jn7CmL1LLSAOhwuL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Jn7CmL1LLSAOhwuL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Jn7CmL1LLSAOhwuL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 原始数据报 4000 字节

DF=0, ID=777
下一跳 MTU=1500

每片数据≤1480
片1: 数据1480, MF=1, 偏移=0

ID=777
片2: 数据1480, MF=1, 偏移=185

(1480/8)
片3: 数据1040, MF=0, 偏移=370

(2960/8)
目的主机按 ID 收集

按偏移重组

3.2 分片的三个坑

🔴 任何一个分片丢了,整个数据报都得重传。目的端收不齐所有片就扔掉全部,TCP 只好重传整个大报文------这就是为什么分片多了性能掉得厉害。

  • 分片攻击:伪造大量重叠分片,让目的端重组时耗尽资源(古老的 Teardrop 攻击)。
  • PMTU 发现:发送方先按路径 MTU 发 DF=1 的包,收到"需要分片"的 ICMP 就把包调小,逐步探到路径上的最小 MTU,避免中间路由器被动分片。

四、路由:每个包怎么找下一跳

4.1 路由表与最长前缀匹配

路由器/主机查转发决策靠路由表 ,每条路由记录:目的网络、子网掩码、下一跳、出接口、度量值。查路由的核心规则是最长前缀匹配 ------多条路由都匹配时,选掩码最长(前缀最长)的那条。

我本机 netstat -rn(macOS)实测片段:

text 复制代码
Routing tables

Internet:
Destination        Gateway            Flags               Netif Expire
default            10.170.255.254     UGScg                 en0
10.170/16          link#15            UCS                   en0
10.170.0.41        9c:7b:ef:4e:9e:b2  UHLWI                 en0
...

解读:

  • default 10.170.255.254 UGScg en0:默认路由 (0.0.0.0/0),所有没被更精确匹配的包都扔给网关 10.170.255.254,从 en0 网卡出去。
  • 10.170/16:本机所在网段,直连,走 en0。
  • 最长前缀匹配举例:去 10.170.0.41 的包,既匹配 default(/0)又匹配 10.170/16(/16)还匹配主机路由(/32),选最长的 /32 主机路由。

本机 ifconfig en0:inet 10.170.2.152 netmask 0xffff0000 broadcast 10.170.255.255------0xffff0000 就是 /16,和路由表对上了。

Linux 看路由用 ip route 或 route -n;-n 表示不做域名反解,直接显示数字 IP,快。

4.2 traceroute:路径上每一跳是谁

traceroute 靠 TTL 把戏把路径上的路由器一个个"钓"出来,下一节和 ICMP 一起讲。


五、ICMP:网络层的报错与探测信令

5.1 ICMP 是什么

ICMP(互联网控制报文协议)是 IP 层的辅助协议,用来传递差错报告和网络探测信息 。它不传输应用数据 ,纯粹是网络层的"控制信令"。封装上,ICMP 报文装进 IP 数据报,IP 首部协议号 = 1。

注意一个常被绕晕的点:ICMP 虽然是网络层协议,但它是封装在 IP 里的------就像 TCP/UDP 封装在 IP 里一样。IP 是信封,ICMP/TCP/UDP 是信的内容,靠协议号区分。

5.2 ICMP 报文类型对照表

类型 名称 方向 代码举例 用途
0 回显应答 Echo Reply 应答 0 ping 的 pong
3 目的不可达 Destination Unreachable 差错 0=网络不可达,1=主机,3=端口,4=需要分片但DF置位 报错
5 重定向 Redirect 差错 0/1/2/3 告诉发送方换个更好的网关
8 回显请求 Echo Request 请求 0 ping 的 ping
11 超时 Time Exceeded 差错 0=TTL=0 traceroute 的核心
12 参数问题 差错 0/1 IP 首部字段错了
13/14 时间戳请求/应答 请求/应答 0 古老的对时,基本不用

两大类:差错报告 (3/5/11/12)和询问/应答(8/0、13/14)。ping 用的就是 8(请求)和 0(应答)这一对。


六、ping 原理:一次"你好,在吗"

6.1 工作流程

目标 本机 目标 本机 #mermaid-svg-LqjEmDsXyrhoiRme{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LqjEmDsXyrhoiRme .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LqjEmDsXyrhoiRme .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LqjEmDsXyrhoiRme .error-icon{fill:#552222;}#mermaid-svg-LqjEmDsXyrhoiRme .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LqjEmDsXyrhoiRme .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LqjEmDsXyrhoiRme .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LqjEmDsXyrhoiRme .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LqjEmDsXyrhoiRme .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LqjEmDsXyrhoiRme .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LqjEmDsXyrhoiRme .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LqjEmDsXyrhoiRme .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LqjEmDsXyrhoiRme .marker.cross{stroke:#333333;}#mermaid-svg-LqjEmDsXyrhoiRme svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LqjEmDsXyrhoiRme p{margin:0;}#mermaid-svg-LqjEmDsXyrhoiRme .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-LqjEmDsXyrhoiRme text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-LqjEmDsXyrhoiRme .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-LqjEmDsXyrhoiRme .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-LqjEmDsXyrhoiRme .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-LqjEmDsXyrhoiRme .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-LqjEmDsXyrhoiRme #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-LqjEmDsXyrhoiRme .sequenceNumber{fill:white;}#mermaid-svg-LqjEmDsXyrhoiRme #sequencenumber{fill:#333;}#mermaid-svg-LqjEmDsXyrhoiRme #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-LqjEmDsXyrhoiRme .messageText{fill:#333;stroke:none;}#mermaid-svg-LqjEmDsXyrhoiRme .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-LqjEmDsXyrhoiRme .labelText,#mermaid-svg-LqjEmDsXyrhoiRme .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-LqjEmDsXyrhoiRme .loopText,#mermaid-svg-LqjEmDsXyrhoiRme .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-LqjEmDsXyrhoiRme .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-LqjEmDsXyrhoiRme .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-LqjEmDsXyrhoiRme .noteText,#mermaid-svg-LqjEmDsXyrhoiRme .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-LqjEmDsXyrhoiRme .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-LqjEmDsXyrhoiRme .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-LqjEmDsXyrhoiRme .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-LqjEmDsXyrhoiRme .actorPopupMenu{position:absolute;}#mermaid-svg-LqjEmDsXyrhoiRme .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-LqjEmDsXyrhoiRme .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-LqjEmDsXyrhoiRme .actor-man circle,#mermaid-svg-LqjEmDsXyrhoiRme line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-LqjEmDsXyrhoiRme :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 收到后原样回显 收到应答, 用"现在-时间戳"算出 RTT ICMP 回显请求 (类型8, 标识PID, 序号seq, 带时间戳)ICMP 回显应答 (类型0, 同样的标识和序号)

ping 做的事:发一个 ICMP 类型 8 的回显请求,带标识符和递增序号,payload 里塞发送时刻;对方收到后原样回一个类型 0。本机用收到时刻减去发送时刻得到 RTT(往返时延)。

6.2 对着真实输出逐行读

本机 ping -c 4 www.baidu.com 实测:

text 复制代码
PING www.baidu.com (198.18.0.37): 56 data bytes
64 bytes from 198.18.0.37: icmp_seq=0 ttl=64 time=0.693 ms
64 bytes from 198.18.0.37: icmp_seq=1 ttl=64 time=0.367 ms
64 bytes from 198.18.0.37: icmp_seq=2 ttl=64 time=0.455 ms
64 bytes from 198.18.0.37: icmp_seq=3 ttl=64 time=0.492 ms

--- www.baidu.com ping statistics ---
4 packets transmitted, 4 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.367/0.502/0.693/0.119 ms
输出片段 含义
56 data bytes / 64 bytes from 请求带 56 字节数据,加上 8 字节 ICMP 头 = 64 字节
icmp_seq=0 序号,丢包/乱序靠它判断
ttl=64 应答包里的剩余 TTL(不是本机发出去的初值)
time=0.693 ms RTT 往返时延
0.0% packet loss 丢包率,0% 表示全通
min/avg/max/stddev 最小/平均/最大/抖动(mdev,标准差)

我这里 RTT 不到 1ms、TTL=64,是因为本机走了本地代理隧道(解析到 198.18.x.x),不是真实公网延迟;真实跨运营商 RTT 通常几十毫秒。

6.3 自己用 Python 写一个 ping

ping 的核心就是:构造 ICMP 报文 → 算校验和 → 发出去 → 等应答 → 算 RTT。下面这个版本在 macOS 上免 sudo 就能跑------关键是用了 BSD/macOS 支持的非特权 ICMP datagram socket (SOCK_DGRAM + IPPROTO_ICMP);Linux 传统上需要 SOCK_RAW + root。

python 复制代码
import socket, struct, time, select

def ones_comp_sum(data):
    """ICMP 校验和:16 位反码求和"""
    s = 0
    for i in range(0, len(data), 2):
        w = (data[i] << 8) + (data[i+1] if i+1 < len(data) else 0)
        s += w
        s = (s & 0xffff) + (s >> 16)
    return ~s & 0xffff

def ping(host="127.0.0.1", count=3):
    # macOS/BSD: SOCK_DGRAM + IPPROTO_ICMP 免 root;Linux 需 SOCK_RAW + sudo
    soc = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_ICMP)
    dest = socket.gethostbyname(host)
    pid = 1234
    for seq in range(1, count + 1):
        payload = b"blog-ping-0123456789"
        header = struct.pack("!BBHHH", 8, 0, 0, pid, seq)   # 类型8 代码0 校验和0 标识 序号
        chksum = ones_comp_sum(header + payload)
        packet = struct.pack("!BBHHH", 8, 0, chksum, pid, seq) + payload
        soc.sendto(packet, (dest, 0))
        t0 = time.time()
        if select.select([soc], [], [], 1)[0]:
            buf, _ = soc.recvfrom(1024)
            rtt = (time.time() - t0) * 1000
            icmp = buf[20:]   # 这种 socket 会把 20 字节 IPv4 首部一起返回,要剥掉
            print(f"64 bytes from {dest}: icmp_seq={seq} type={icmp[0]} code={icmp[1]} time={rtt:.3f} ms")
        else:
            print(f"icmp_seq={seq} request timed out")
    soc.close()

ping("127.0.0.1", 3)

本机实测输出(type=0 code=0 正是回显应答):

text 复制代码
64 bytes from 127.0.0.1: icmp_seq=1 type=0 code=0 time=0.074 ms
64 bytes from 127.0.0.1: icmp_seq=2 type=0 code=0 time=0.049 ms
64 bytes from 127.0.0.1: icmp_seq=3 type=0 code=0 time=0.042 ms

🔴 这段代码的两个细节很值钱:一是校验和必须先填 0 算一遍再填回去;二是这种 ICMP socket 收到的包带 IP 首部 ,要 buf[20:] 剥掉才读得到 ICMP 类型。


七、经典面试题:断网能 ping 通 127.0.0.1 吗?

能! 这是网络层最经典的送分题,也是最容易答错的题。

7.1 为什么断网也通

127.0.0.0/8 是环回地址 ,发往它的数据包根本不走出网卡------协议栈在内部就把它绕回来了。不管你拔网线、关 Wi-Fi、断外网,环回始终通,因为它压根没碰物理链路。
#mermaid-svg-Qq3XzbmLy6LHT8AQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Qq3XzbmLy6LHT8AQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .error-icon{fill:#552222;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .marker.cross{stroke:#333333;}#mermaid-svg-Qq3XzbmLy6LHT8AQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Qq3XzbmLy6LHT8AQ p{margin:0;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .cluster-label text{fill:#333;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .cluster-label span{color:#333;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .cluster-label span p{background-color:transparent;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .label text,#mermaid-svg-Qq3XzbmLy6LHT8AQ span{fill:#333;color:#333;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .node rect,#mermaid-svg-Qq3XzbmLy6LHT8AQ .node circle,#mermaid-svg-Qq3XzbmLy6LHT8AQ .node ellipse,#mermaid-svg-Qq3XzbmLy6LHT8AQ .node polygon,#mermaid-svg-Qq3XzbmLy6LHT8AQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .rough-node .label text,#mermaid-svg-Qq3XzbmLy6LHT8AQ .node .label text,#mermaid-svg-Qq3XzbmLy6LHT8AQ .image-shape .label,#mermaid-svg-Qq3XzbmLy6LHT8AQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .rough-node .label,#mermaid-svg-Qq3XzbmLy6LHT8AQ .node .label,#mermaid-svg-Qq3XzbmLy6LHT8AQ .image-shape .label,#mermaid-svg-Qq3XzbmLy6LHT8AQ .icon-shape .label{text-align:center;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .node.clickable{cursor:pointer;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .arrowheadPath{fill:#333333;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Qq3XzbmLy6LHT8AQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Qq3XzbmLy6LHT8AQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Qq3XzbmLy6LHT8AQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .cluster text{fill:#333;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .cluster span{color:#333;}#mermaid-svg-Qq3XzbmLy6LHT8AQ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Qq3XzbmLy6LHT8AQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .icon-shape,#mermaid-svg-Qq3XzbmLy6LHT8AQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .icon-shape p,#mermaid-svg-Qq3XzbmLy6LHT8AQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .icon-shape .label rect,#mermaid-svg-Qq3XzbmLy6LHT8AQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Qq3XzbmLy6LHT8AQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Qq3XzbmLy6LHT8AQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Qq3XzbmLy6LHT8AQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
应用进程
socket API
TCP/IP 协议栈

目的地址=127.0.0.1
目的是环回?
协议栈内部直接回环

不经过网卡/驱动

7.2 环回接口的三个实际作用

  1. 本机进程间通信 :Nginx 转发给本机 127.0.0.1:8000 的后端,走环回,快且安全。
  2. 测试协议栈:ping 127.0.0.1 通,说明本机 TCP/IP 协议栈没坏,问题在网卡或外部网络。
  3. 只允许本机访问 :服务绑定 127.0.0.1 而不是 0.0.0.0,外网就访问不到------这是最常用的"内网服务不暴露"手段。

我本机 ping -c 3 127.0.0.1 实测,RTT 只有 0.1ms 左右:

text 复制代码
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.102 ms
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.097 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.123 ms

--- 127.0.0.1 ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.097/0.107/0.123/0.011 ms

八、TTL 与 traceroute:把路径上的路由器钓出来

8.1 原理

前面说 TTL 每过一个路由器减 1,减到 0 就丢弃并回 ICMP 超时(类型 11)。traceroute 利用这点:

  1. 先发 TTL=1 的探测包,第一跳路由器把 TTL 减到 0,丢弃,回 ICMP 超时 → 这就知道了第一跳是谁。
  2. 再发 TTL=2,第二跳回超时 → 知道第二跳。
  3. 依次递增 TTL,直到包到达目的端(目的端收到后不回超时,而是根据包类型回别的------UDP 端口不可达,或 ICMP 回显应答),路径就完整了。

8.2 实测

本机 traceroute -m 3 -w 2 -q 1 10.170.255.254(网关):

text 复制代码
traceroute to 10.170.255.254 (10.170.255.254), 3 hops max, 40 byte packets
 1  10.170.255.254 (10.170.255.254)  15.661 ms

第一跳就是网关,和路由表对上了。

⚠️ 我 traceroute 外网(如 223.5.5.5)每跳都是 *:

text 复制代码
traceroute to 223.5.5.5 (223.5.5.5), 4 hops max, 40 byte packets
 1  *
 2  *
 3  *
 4  *

这不代表网络不通 (ping 是通的),而是中间路由器/网关禁止或过滤了 ICMP 超时报文 ------安全上运营商和企业网常这么干,traceroute 就只能显示星号。看到 * 要会判断:多半是中间设备不回 ICMP,而不是断网。


九、IPv6 简介:为什么要换

维度 IPv4 IPv6
地址长度 32 位(约 43 亿个) 128 位,地址近乎无限
首部 20~60 字节,字段多 固定 40 字节,首部简化,去掉校验和
广播 有(255.255.255.255) 无广播,用组播
配置 手动 / DHCP 支持自动配置(SLAAC)
安全性 IPsec 可选 IPsec 原生内置
分片 路由器和源都能分片 只在源端分片,路由器不分片

IPv6 地址用冒号分十六进制,如 2001:0db8::1,:: 表示一串连续的 0。开发上你现在要做的主要是:服务监听地址别写死 0.0.0.0,用 ::(IPv6 全地址)或双栈兼容;AF_INET 替换成 AF_INET6。


总结:你真正需要记住的 10 件事

  1. IP = 无连接、不可靠、尽最大努力;可靠性靠 TCP,不靠 IP。
  2. IP 首部三个必背协议号:ICMP=1、TCP=6、UDP=17。
  3. 分片三要素:标识认亲、MF 标记后续、片偏移按 8 字节 ,且在目的端重组。
  4. 子网计算口诀:IP 与掩码得网络地址,主机位全 1 得广播地址,可用主机 = 2^主机位 - 2;别手算,用 ipaddress。
  5. 路由查找遵循最长前缀匹配 ;default/0.0.0.0/0 是兜底默认路由。
  6. ICMP 是网络层的控制信令,封装在 IP 里(协议号 1);差错报告 + 询问应答两大类。
  7. ping = ICMP 类型 8 请求 / 类型 0 应答,RTT = 收到时刻 - 发送时刻。
  8. 断网也能 ping 通 127.0.0.1------环回包不经过网卡,协议栈内部回环。
  9. traceroute 靠 TTL 递增 + ICMP 超时(类型 11) 钓出每一跳;全星号通常是中间设备过滤 ICMP,不是断网。
  10. IPv6 = 128 位、首部简化、无广播用组播、支持自动配置、IPsec 内置。

验证清单

  • 能默写 IPv4 首部关键字段,并说出 TTL、协议号、标识/标志/片偏移的作用
  • 能手算 192.168.1.130/26 的网络地址、广播地址、可用主机范围和主机数,并用 ipaddress 验证
  • 能说出私有地址三段和环回地址,并解释为什么 127.0.0.1 断网也通
  • 能用 netstat -rn(或 ip route)找到默认路由和本机网段
  • 能逐行解释 ping 输出里的 icmp_seq、ttl、time、丢包率和 min/avg/max/mdev
  • 能说清 traceroute 的 TTL 原理,并解释为什么会看到 *
  • 能用 Python 标准库写出一个发送 ICMP 回显请求的小脚本
  • 能说出 IPv4 和 IPv6 在地址长度、广播、分片、安全性上的主要区别

参考资源


下一篇《【上手】Socket 网络编程:亲手写 TCP/UDP 程序》:网络层和传输层讲完了,这篇直接上手写代码------用 Python socket 标准库从零实现 TCP echo 服务端/客户端、UDP 通信,重点复现面试必考的 TCP 粘包/分包问题 并给出"长度头"解决方案,最后用 selectors 写出第一个并发服务器,为后面操作系统篇的 epoll 埋下伏笔。

相关推荐
OnlineProxy1 小时前
为 LangChain 和 CrewAI 构建自定义工具:将代理 IP 轮换封装至智能体函数中
网络协议·tcp/ip·langchain
半杯咖啡半行码3 小时前
Linux 驱动开发入门:内核模块编程基础
linux·服务器·网络
智能直播3 小时前
RK3528 V4L2 框架 UVC 接口自动获取 H.264 / MJPEG / YUYV 格式:从内核采集到硬解分发
java·网络·实时音视频·视频编解码·h.264
睿智の男孩4 小时前
MIG IP核对DDR3的输出时序分析记录
网络·网络协议·tcp/ip
艾芯微科技4 小时前
高充放电流锂电充放 SoC:ETA9885E10,便携储能、小移动电源 BOM 选型笔记
网络·单片机·嵌入式硬件·集成测试·51单片机
2401_868534784 小时前
数据交换方式
服务器·网络
艾芯微科技4 小时前
钰泰 ETA6973PQ3Y|QFN3×3 2A 同步降压 Buck,I2C 可配置多档输出,动态调压 PMIC
网络·单片机·嵌入式硬件·fpga开发·集成测试·51单片机
默|笙4 小时前
【Linux网络】_udp_socket(认识)
linux·网络·udp