如果你写过任何一个 Linux 网络相关的小程序——哪怕是课上那个最简单的 tcp_server——你一定会遇到这样的时刻:程序明明编译过了、进程也启动了,可客户端就是连不上。这时候你手边最需要的不是多写两行代码,而是一套能"看清网络到底发生了什么"的命令行工具。
今天的这堂课,就是给你配齐这样一套"听诊器"。我们不空谈理论,而是把 Linux 下最常用的网络排查命令——ping、traceroute、ifconfig(以及它的现代替代 ip)、netstat(以及它的替代 ss)、route、nslookup/dig、curl、tcpdump——挨个掰开揉碎:它们各自解决什么问题、参数怎么配、典型输出怎么读、有哪些坑要绕开。学完这堂加餐,你"程序跑不起来"的网络问题,基本都能靠它们定位到具体一层。
你会需要的知识准备
在往下走之前,先摆两块地基,你会发现所有命令的输出都建立在它们之上:
- 网络分层:数据在网络上传输要经过一层层"打包—解包"。常见的说法是 OSI 七层,但日常排障我们简化成:链路层(网卡、MAC 地址)、网络层(IP 地址、路由,数据包从这里"跳")、传输层(端口、TCP/UDP 连接)、应用层(HTTP、DNS 等服务)。大多数命令只关心其中的某一层,这是它们分工不重叠的根源。
- IP 地址与端口:IP 地址是设备在网络上的"门牌号"(IPv4 是 32 位的,形如
192.168.1.100);端口是设备内不同服务的"门牌号"(0~65535,如 80 是 HTTP、443 是 HTTPS、22 是 SSH、3306 是 MySQL)。一个完整的"网络连接"通常用"四元组"描述:源 IP、源端口、目标 IP、目标端口。
如果你对这两块只有模糊印象,也没关系——每讲到具体命令时,我都会把相关概念再点一遍。现在开始。
ping:先确认"到底通不通"
排障的第一步永远是 ping。它的作用是测试两台主机之间能不能"对上话"以及对话快不快——你给目标发一个"在吗"的探测包,目标如果存活且网络通畅,就回一个"在"。这个"在吗/在"的问答,走的是 ICMP(Internet Control Message Protocol,互联网控制消息协议) 的两个报文:Echo Request(回显请求) 和 Echo Reply(回显应答)。ICMP 跑在网络层,它没有端口的概念,所以 ping 只能证明"IP 层通了",测不了某个端口、也证明不了某个服务是好的——这一句是后面一系列"坑"的总开关。
Linux 下的 ping 默认会无限次地发包,直到你按 Ctrl + C 打断(这和 Windows 默认只发 4 次不同)。所以基本每条结果里 ttl= 后面那个初始 TTL 在不同系统上的默认值也不同。来看它的标准用法:
# 持续 ping 目标(内网 IP 或域名都行),按 Ctrl+C 停止
$ ping www.qq.com
# 限制只发 5 次就自动结束,适合脚本和快速测试(Windows 下对应参数换成 -n)
$ ping -c 5 www.qq.com-c 5 表示 count(次数)为 5,这是 Linux 上最常用的收尾参数,避免手忙脚乱按打断键。下面是一份 ping -c 5 www.qq.com 的典型输出,我们逐段读懂它:
PING ins-r23tsuuf.ias.tencent-cloud.net (121.14.77.221) 56(84) bytes of data.
# ↑ 第一行:先做 DNS 解析,把 www.qq.com 解析成 IP 121.14.77.221,
# 括号里 56(84) 表示:数据区 56 字节,整包(含 ICMP 头 8 字节 + IP 头 20 字节)84 字节
64 bytes from 121.14.77.221 (121.14.77.221): icmp_seq=1 ttl=48 time=35.1 ms
64 bytes from 121.14.77.221 (121.14.77.221): icmp_seq=2 ttl=48 time=35.1 ms
64 bytes from 121.14.77.221 (121.14.77.221): icmp_seq=3 ttl=48 time=35.1 ms
64 bytes from 121.14.77.221 (121.14.77.221): icmp_seq=4 ttl=48 time=35.1 ms
64 bytes from 121.14.77.221 (121.14.77.221): icmp_seq=5 ttl=48 time=35.1 ms
# ↑ 每一行对应一个成功回应的包:
# - 64 bytes:收到的回包大小,即"56 数据 + 8 ICMP 头"
# - icmp_seq=序号:这是第几个探测包(从 1 递增),若中间缺号,说明那个包丢了或延迟过大
# - ttl=48:TTL(Time to Live,生存时间)。稍后单独讲,跳数就藏在这里
# - time=35.1 ms:往返时延 RTT(Round-Trip Time),从发出到收到经历的时间
--- ins-r23tsuuf.ias.tencent-cloud.net ping statistics ---
# ↑ 统计分隔线
5 packets transmitted, 5 received, 0% packet loss, time 4005ms
# ↑ 发了 5 个、收到 5 个、丢包率 0%——这行决定"通不通"
rtt min/avg/max/mdev = 35.100/35.178/35.250/0.052 ms
# ↑ 往返时延的最小值/平均值/最大值/标准偏差(波动情况)什么时候看哪一行? 看"通不通"看中间的 icmp_seq 有没有缺号、看统计行的 0% packet loss(丢包 0% 意味着"通且稳");看"快不快"看 time= 那列:同一局域网内通常 <1ms,跨运营商几十毫秒很正常,超 200ms 就要留意了。
TTL:藏在「跳数」里的信息
TTL 是 IP 头里的一个字段,每经过一台路由器就减 1,减到 0 时路由器会把包丢弃,并给源主机回一个"超时"的 ICMP 报文。它的作用是防止数据包在网络里兜圈子永远死循环——想象一个没有 TTL 的包进了路由环回,就会永远转下去。
不同操作系统给数据包设置的初始 TTL 不同:
| 系统/设备 | 默认初始 TTL |
|---|---|
| Linux / macOS / Android / iOS | 64 |
| Windows | 128 |
| Cisco 路由器等网络设备 | 255 |
所以看到回包里的 ttl=48,你可以估算:路径初始 TTL 大概率是 64,那 64 - 48 = 16,说明这趟数据包经历了约 16 跳(路由器)。(严格说,回包里显示的 TTL 是对端主机回包那一刻的剩余值,假设对端初始也是 64,估算才有意义——它反映的是"回程"的跳数,与假设的初始值共同决定。)一个常用的粗判法:ttl 接近 64 说明目标很可能是 Linux/macOS 且较近;接近 128 说明目标很可能是 Windows。TTL 现在是 48("接近 64"且相去 16),说明这是一台 Linux 主机、离你大概 16 跳。
两个必须牢记的坑:
ping通 ≠ 服务通,ping不通 ≠ 服务挂。 因为很多服务器会主动禁用 ICMP(防火墙丢弃 Echo 请求),此时ping显示超时/丢包,但它的 HTTP、SSH 服务明明好好的。反过来,ping能通只能证明 IP 层可达,你连的 8888 端口上到底有没有进程在监听,ping是完全不知道的——这是它和netstat/curl分工的本质区别。- 域名解析失败时会报
unknown host。 如果www.qq.com解不出 IP,ping会直接提示解析失败,这提示你问题出在 DNS 这一层,该请出后面的nslookup/dig了。
顺带一提,常见报错还有一种:Destination Host Unreachable 表示路由表里根本没有到目标的路径(多半是本机网关配错了);Request timeout 表示发了包但没人应答(网络被防火墙拦、目标离线、或链路太差都可能导致)。
traceroute:看数据包到底走了哪条路
ping 能告诉你"通不通、多快",但你只得到一个总结果——中间那些岔路你一概不知。traceroute(Windows 下叫 tracert)补上这一块:它把到目标的一路上每跳路由器都"点名"给你看。它赖以工作的,正是前面 TTL 递减的机制。
原理一句话:它先发一个 TTL=1 的探测包——第一个路由器收到后把 TTL 减到 0,于是弃包并回一个"超时"报文,这个报文的来源地址,就是第一跳路由器;再发 TTL=2 的包,到第二跳才被丢弃,从而得到第二跳……如此递推,TTL 每加 1 就多暴露一跳,直到包到达真正的目标主机(目标不再递减丢包,而是正常回应),路径就完整拼出来了。
# 追踪到目标可能经过的每一跳路由器
$ traceroute www.baidu.com
# 关键坑:某些网络/主机禁用 UDP 探针或 ICMP,可选 -I 改用 ICMP(更接近 ping;需 root)
# $ sudo traceroute -I www.baidu.com典型输出(跳数列 TTL 值、域名或 IP 是那一跳的路由器,ms 是到达该跳的时延,通常会测 3 次):
traceroute to www.baidu.com (110.242.68.66), 30 hops max, 60 byte packets
# ↑ 目标已解析成 IP,最多探测 30 跳,每个探测包 60 字节
1 10.0.2.2 (10.0.2.2) 0.504 ms 0.403 ms 0.382 ms
2 192.168.1.1 (192.168.1.1) 2.101 ms 2.098 ms 2.104 ms
3 100.64.0.1 (100.64.0.1) 3.214 ms 3.231 ms 3.188 ms
...
7 116.113.30.213 (116.113.30.213) 15.402 ms 15.418 ms 15.429 ms
8 * * * # 该跳不回应(防火墙防泄露或网络策略),用 * 代替
9 110.242.68.66 (110.242.68.66) 35.100 ms 35.090 ms 35.120 ms读法:第一列是跳数(TTL),接着是那一跳路由器的 IP(能反解出域名就显示域名),后面三列是 3 次探测各自到达该跳的往返时延。如果某跳出现 * * *,一般是那台设备出于安全策略不回 ICMP 超时报文,不代表网络断了——判断要结合前后跳是否连贯。
排障用法:如果 ping 不通某个外网,用 traceroute 一看,若中间某跳之后全是 *,问题就定位在"卡在那一跳附近"(可能是运营商骨干网问题、跨国链路丢包、或是被防火墙拦截);若前面几跳都正常、只差最后一跳不通,那多半是目标主机自己的防火墙把探测包挡了——服务未必挂了。
一个小误区:traceroute 默认用 UDP 探测(发送到高端口),很多网络会丢弃 UDP 探针导致后半段全是 *,此时改用 -I(ICMP)往往能看通。这属于"工具误报"而非真断网,别急着下结论。
ifconfig 与 ip:看清自己的"门面"
排障要从自身开始——你得以最快的速度确认本机有几张网卡、各自 IP、网卡是否正常。这就是 ifconfig(和它的现代替代 ip)的用武之地。
ifconfig 来自 net-tools 包,是传统的老牌命令。它有个特点:默认只显示"已启用(UP)"的网卡,且不支持 IPv6 的现代高级特性,官方早已标记为 deprecated(弃用),许多新发行版(如 Ubuntu 20.04+、CentOS 8+)默认不预装。它的接班人 ip 来自 iproute2 包,是现代 Linux 内置、官方主推的命令,功能覆盖并超越了 ifconfig(还能管路由、ARP、虚拟网卡、网络命名空间等),而且会显示所有网卡(含禁用状态的)。结论:新项目一律优先用 ip,ifconfig 只在老系统应急。
# 查看本机所有网卡及 IP 信息(ifconfig 版)
$ ifconfig
# 现代推荐:ip 命令查看网卡与 IP(可简写为 ip a)
$ ip addr先看老派的 ifconfig 输出长什么样(只看,不推荐新写脚本用它):
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
# ↑ eth0 是网卡名;UP=已启用,BROADCAST=支持广播,RUNNING=链路在工作,MULTICAST=支持组播
inet 192.168.1.100 netmask 255.255.255.0 broadcast 192.168.1.255
# ↑ inet 是本机 IPv4 地址;netmask 是子网掩码 255.255.255.0(即 /24);
# broadcast 是广播地址
inet6 fe80::a00:27ff:fe12:3456 prefixlen 64 scopeid 0x20<link>
# ↑ inet6 是 IPv6 地址(fe80:: 开头是链路本地地址)
ether 08:00:27:12:34:56 txqueuelen 1000 (Ethernet)
# ↑ ether 是网卡的 MAC 地址(物理地址),出厂烧录、全球唯一
RX packets 1234 bytes 567890 (567.8 KB) # 本机收到的包/字节数
TX packets 789 bytes 456789 (456.7 KB) # 本机发出去的包/字节数
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
# ↑ lo 是回环接口(loopback),IP 固定 127.0.0.1,也就是本机访问自己,
# 后续我们写客户端时连 127.0.0.1 就是走它再看现代 ip addr 的输出,信息更结构化:
$ ip addr
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP ...
# ↑ eth0 网卡,<> 里的 UP 表示已启用,state UP 说明链路状态正常
link/ether 08:00:27:12:34:56 brd ff:ff:ff:ff:ff:ff # MAC 地址
inet 192.168.1.100/24 brd 192.168.1.255 scope global eth0
# ↑ 192.168.1.100/24 :IP/前缀长度(/24 等价于掩码 255.255.255.0)
inet6 fe80::a00:27ff:fe12:3456/64 scope link # IPv6 地址ip 家族还有两个很有用的兄弟:
# 查看网卡的"开关"状态和链路(物理层)信息
$ ip link
# 查看路由表与默认网关(下面 route 一节也会讲)
$ ip route动手注意:改网卡配置(ifconfig eth0 up/down、ip addr add/delete)一般需要 root 权限且是临时生效,重启/网络服务重启后恢复——别拿临时配置当持久配置用。遇到"程序 bind 不上某个 IP",先 ip addr 确认那个 IP 到底在不在这台机器上、网卡是不是 DOWN 状态,这是最常见的低级错误。
netstat 与 ss:看端口谁在听、连接谁连着
这是对"我的 tcp_server 起来了没"最直接的回答。netstat(net-tools 老三样里的重头戏)用来查看网络状态——它能看到系统里所有端口、连接及其所属进程。它的现代接班人是 ss(来自 iproute2,更快、更省资源,读 /proc/net 的实时信息)。日常排查优先用 ss;但鉴于很多旧教程和旧系统仍是 netstat,两个我们都要会读——反正输出字段几乎通用。
先看 netstat 的核心参数(这些习惯至今照搬到 ss 上也成立):
-n:拒绝解析别名,把能显示成数字的一律显示成数字(IP、端口、不走域名反解)。速度变快、信息更"硬"。排障强烈建议加-n,否则它会浪费时间做 DNS 反解。-l:只列出正在 Listen(监听) 的服务/端口。-p:显示建立连接对应的进程名和 PID(需要 root 才能看到别人/所有进程的)。-t:只看 TCP 相关。-u:只看 UDP 相关。-a:显示所有选项(默认不显示 LISTEN 状态的,加上-a才显示监听中的)。
把它们拼一起,最常用的组合就出来了:
# 查看本机所有"正在监听"的 TCP 端口,带端口号和进程(root 才能看全进程)
$ netstat -nltp
# 查看本机所有 UDP 监听端口,带端口号和进程
$ netstat -nlup
# 查看当前所有网络连接(TCP+UDP,含已建立的),带进程
$ netstat -anutp配协议解析:-n(数字)、-l(监听)、-t(TCP)、-p(进程)。我们把"监听"和"已建立"这两类最重要的连接状态读透:
$ netstat -nltp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:8888 0.0.0.0:* LISTEN 2958285/tcp_server
# ↑ tcp 协议;
# Local Address 0.0.0.0:8888 = 监听所有网卡的 8888 端口;
# Foreign Address 0.0.0.0:* = 还没人连上来(* 表示任意外部端点);
# State LISTEN = 正在监听(等待连接)!这是"我的服务器起来没"的标志;
# PID/Program name 2958285/tcp_server = 这个端口的进程信息
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN 1234/mysqld
# ↑ 只监听本机 127.0.0.1 的 3306(MySQL)——外部访问不了,只能本机连再看"已有连接"长什么样(去掉 -l、保留 -a):
$ netstat -anutp | head # 实际看全部,这里留给连接给演示
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 192.168.1.100:8888 192.168.1.50:45231 ESTABLISHED 2958285/tcp_server
# ↑ 本机 8888 端口上有一台 192.168.1.50 的客户端(端口 45231)连着,
# State ESTABLISHED = 连接已建立且正在工作——这正是"有客户端连上了"的证据为什么要看连接状态(State)? 这是整张表信息量最大的字段,TCP 连接是有生命周期的,每个阶段一个状态:
- LISTEN:进程在监听某个端口,等待别人来连。
- ESTABLISHED:连接已建立,双方正在通信(正常使用的连接就是这个状态)。
- SYN_SENT:本机想连但还没收到对方回应(对方可能在很远、被防火墙丢包)。
- SYN_RECV:收到对方的连接请求但还没完成协商。
- TIME_WAIT / CLOSE_WAIT:连接关闭过程中后出现的短暂状态。TIME_WAIT 大量堆积通常伴随高并发短连接;CLOSE_WAIT 大量堆积往往是你代码里没正确
close套接字导致的"连接泄漏"——这是个非常典型的服务端 bug 信号。
ss 的语法与字段和 netstat 惊人地一致,只是源自 iproute2、更快:ss -nltp、ss -anutp 就对应上面的命令。实际上两者能读到同样的东西,ss 是"官方推荐"的现代选择。
还有一个来自课件、netstat -p 的好搭档——pidof:通过进程名反查 PID,在你想确认"自己写的服务到底起了没有、PID 是多少"时方便极了:
# 按进程名查 PID(对比:ps 是"按状态列出进程",pidof 是按名字直接打听 IDs)
# 顺便看一眼它的"老办法":
$ ps axj | head -1 && ps ajx | grep tcp_server
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
2958169 2958285 2958285 2958169 pts/2 2958285 S+ 1002 0:00 ./tcp_server 8888
# ↑ 用 ps 手工 grep,也能看到 PID 是 2958285、正跑着 ./tcp_server 8888
# 而 pidof 一句话就拿到 PID(比 ps 又数列又 grep 干净得多):
$ pidof tcp_server
2958285route:数据包往哪个门走
ping 通了说明网络层在干活,但你可能还想知道:这台机器默认把不认识的流量交给谁? 这就是路由表回答的问题。路由表就是一张"出货清单":每个目标网段对应该走哪条路、从哪个网卡出去;表中没有的、也没匹配到更多细则的,就交给 default(默认路由 / 默认网关)。
# 老工具查路由表(-n 不反解,快且清晰)
$ route -n
# 现代工具查路由表
$ ip route # 或 ip rroute -n 的典型输出:
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.1.1 0.0.0.0 UG 0 0 0 eth0
# ↑ 0.0.0.0 是"默认"路由:去任何没匹配到的目标都走网关 192.168.1.1(即你的路由器),
# U=这条路由 in Use(有效),G=经过 Gateway(有网关)
192.168.1.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
# ↑ 目标 192.168.1.0/24 网段内的地址,直接走 eth0(同网段不经过网关,0.0.0.0 表示直连)ip route 输出更紧凑,等价的信息往往缩成几行:
$ ip route
default via 192.168.1.1 dev eth0 # 默认路由:都从 eth0 出,经 192.168.1.1
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.100
# ↑ 本网段 192.168.1.0/24 直接挂 eth0,本机 IP 是 192.168.1.100读法就两条:看有没有 default(默认网关)、网关是不是你期望的路由器。一个很常见的"有 IP 却能直连、却上不了外网"的病根,就是默认网关丢了或网关 IP 配错——ip route 一眼就能看出来没有 default via ... 这一行。同理,ping 192.168.1.50(同网段)通、ping 114.114.114.114(外网)不通且报 Network is unreachable,十有八九是默认路由缺失。
nslookup 与 dig:域名到底解析成了什么
我们在 C/S 程序里经常写主机名(比如连 www.qq.com),但网络上真正寻路靠的是 IP。把"域名 → IP"翻译出来的过程叫 DNS(Domain Name System,域名系统)解析,负责应答的是 DNS 服务器。当 ping 报 unknown host、curl 报解析失败时,就该请出 DNS 排查命令了。
nslookup(老牌、Windows/Linux 都有)和 dig(Linux 上更权威、输出更结构化)都是查询 DNS 的工具。nslookup 简单够用,dig 信息更全、是排障首选。
# 查询 www.qq.com 解析成什么 IP(用一个指定的 DNS 服务器,比如 223.5.5.5 阿里 DNS)
$ nslookup www.qq.com 223.5.5.5
# dig 查询,输出更详细(含完整记录类型、TTL 缓存时间)
$ dig www.qq.comdig 的输出里,核心看"ANSWER SECTION"这一段:
;; ANSWER SECTION:
www.qq.com. 91 IN CNAME ins-r23tsuuf.ias.tencent-cloud.net.
ins-r23tsuuf.ias.tencent-cloud.net. 91 IN A 121.14.77.221
# ↑ 这两行就是答案:
# 第一行:www.qq.com 是一个 CNAME(别名),真正指向 ins-r23tsuuf...这个真实主机名
# 第二行:这个真实主机名有一条 A 记录(IPv4 地址)= 121.14.77.221
# "91" 是这一条记录的缓存 TTL(还有 91 秒),IN 是 Internet 类型,A/CNAME 是记录类型对照一下前面 ping www.qq.com 第一行解析出的 121.14.77.221——你看,同一个域名用 dig 能查到它其实先经过一层 CNAME 别名才落到 A 记录的 IP,这就是 ping 和 dig 分工的差异:ping 只是"顺便解析",dig 是"专门审问 DNS"。常见的解析问题:域名根本没配置 A 记录(dig 查无 ANSWER)、被错误引导到某个 CDN/反代节点(返回的 IP 不是你预期的)、/etc/resolv.conf 里 DNS 服务器配错了。
curl:跟 HTTP 服务"直接对话"
前面的命令都在 IP/传输层兜圈子。如果你的服务是 Web 服务(HTTP),排障到应用层就该请出 curl。curl 是一个用 URL 语法和远端的服务器交互的命令行工具,可以发 HTTP 请求、看响应头、看请求/响应的全过程,是"我的 Web 接口到底返回了什么"的最直接验证。
# 发起一个正常 GET 请求,只打印响应正体
$ curl http://127.0.0.1:8888/
# 想看响应头(HTTP Header)也一起打出来,加 -i
$ curl -i http://127.0.0.1:8888/
# 只想看响应头、不关心正文,用 -I(等价于 HEAD 请求)
$ curl -I http://127.0.0.1:8888/
# 排障最爱:-v(verbose,输出全过程——DNS 解析、TCP 连接、发起、响应头都给你看)
$ curl -v http://127.0.0.1:8888/curl -v 是课堂排障的"圣物",它把每一步都摊开给你看:
$ curl -v http://127.0.0.1:8888/
* Trying 127.0.0.1:8888...
* Connected to 127.0.0.1 (127.0.0.1) port 8888
# ↑ 这两行说明 TCP 连接成功了——对应 netstat 里会出现一条 ESTABLISHED
> GET / HTTP/1.1
> Host: 127.0.0.1:8888
> User-Agent: curl/...
# ↑ 以 > 开头的行是"我们发出去的请求"
< HTTP/1.1 200 OK
< Content-Length: 11
# ↑ 以 < 开头的行是"服务器返回的响应头"。HTTP/1.1 200 OK 是状态行:200 表示成功
hello world
# ↑ 这就是响应正体连不上时它说的话能直接帮你分层定位:Trying 127.0.0.1:8888... 之后一直没 Connected,可能是端口没监听(进程没起、别忘看 netstat -nltp);如果报 Connection refused,说明端口上没有进程在等(LISTEN 状态不存在);如果报 Connection timed out,说明包发出去没人理会(多半被防火墙拦或目标不可达)。HTTP 状态码也是排障语言:200 成功、301/302 重定向、403 拒绝访问、404 资源不存在、500 服务器内部错误。
顺带几个高频参数:-X POST 指定方法、-d '{"a":1}' 提交 POST 数据、-H 'Content-Type: application/json' 自定义头、-o 文件 把响应存成文件。curl 还包括 FTP、TELNET 等协议,但对我们排查 HTTP 服务,上面这些已经足够。
tcpdump:让包说出"真话"
如果前面几个命令层层下去还没定论,或者你怀疑"人家说的和实际发的根本不是一回事",那就只能抓包了。tcpdump 是 Linux 下最经典的抓包工具——它把网卡上流过的数据包(原始字节)+ 它的解析结果打到屏幕上,让你亲眼看见某个连接到底发没发、发了啥。很多问题(比如连接被 RST 重置、重传风暴、数据被分段),只有抓包才能一锤定音。
几个必备常识与参数:
- 权限:抓包需要 root(或授权),因为要读取网卡原始帧。非 root 直接跑会报
You don't have permission to capture on that device,用sudo即可。 -i 接口:抓哪张网卡的包(any表示所有网卡)。不指定通常是默认网卡,课堂环境常用lo(回环)抓本机服务间的流量。-nn:不反解 IP、不解析端口号,直接显示数字(又快又准)。-c 个数:抓到 N 个包就停(配合测试不会刷屏)。-A:同时把包内容按 ASCII 显示(看应用层明文很有用)。-w 文件/-r 文件:把原始包存成 pcap 文件 / 回放该文件(存完可以拿 Wireshark 慢慢看)。
# 抓本机 8888 端口(不管 TCP/UDP,也不管进出方向)的包,抓到 20 个就停
$ sudo tcpdump -nn -i lo -c 20 port 8888
# 抓源或目标是这台机器上某个 IP 的所有 tcp 连接
$ sudo tcpdump -nn tcp host 192.168.1.100
# 抓 80 端口且是"发出到外头"的 HTTP 明文(-A 显示 ASCII)
$ sudo tcpdump -nn -A 'tcp port 80 and not src 192.168.1.50'过滤表达式是 tcpdump 的灵魂,它是门小语言,用布尔运算符(and/or/not)把"通讯方向、协议、端口、IP"组合起来。上面的 port 8888(只看这个端口)、tcp(只看 TCP)、host 某IP(只看与该 IP 相关)、and src .../not dst ...(方向和排除)是最常用的积木。抓包务必先想清楚过滤条件,否则整块网卡的包会把你淹没——这是新手最常吃的亏。
一段典型的 TCP 往返抓包(每行就是一个包,层级是源码从上到下越嵌越深):
$ sudo tcpdump -nn -i lo port 8888
tcpdump: listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
# ↑ 开始监听 lo 网卡,抓端口 8888
14:02:41.123456 IP 127.0.0.1.8888 > 127.0.0.1.45231: Flags [S.], seq 12345, ack 45678, win 64240, ...
# ↑ 一个 TCP 包。字段拆解:
# 时间戳 → 源IP.端口(127.0.0.1.8888) > 目标IP.端口(127.0.0.1.45231);
# Flags [S.]:S=SYN(请求建立连接), . =ACK(确认);
# seq/ack:序号与确认号,TCP 可靠传输靠这对数字衔接;
# win 64240:接收窗口(一次能收多少字节)
14:02:41.123512 IP 127.0.0.1.45231 > 127.0.0.1.8888: Flags [.], ack 12346, ...
# ↑ 回的方向:Flags [.] = 纯 ACK,对方确认收到了 12345 这个序号的内容读包时的几个信号:Flags [S] 是三次握手的"第一次握手"(发起连接);[S.] 是"第二次握手"(应答+也发 SYN);[.] 是"第三次握手"及正常传输的纯确认;[F.] 是正常关闭(FIN);[R] 是 RST(连接被强行重置)——通常代表"端口没有进程在 LISTEN"或"非法状态";大量重复相同序号且 [.] 是重传,说明网络丢包严重。这套"看得懂包"的能力,就是抓包训练的回报。
一套贯穿的排障思路
把这条命令链串起来,就是一个标准的分层排查流程,从低到高、一层一层排除:
- 本机门面:
ip addr(ifconfig)看 IP/网卡是否正常; - 有没有监听:
ss -nltp(netstat -nltp)看端口是否在 LISTEN、进程在不在(配合pidof反查进程); - 连不连得上:
ping测 IP 层可达;traceroute找断点; - 路由对不对:
ip route(route -n)检查默认网关; - 域名解不解:
nslookup/dig验证 DNS; - 服务返不返:
curl -v直达应用层看 HTTP 状态码; - 包说不说谎:
tcpdump抓包定住最后一层真相。
课后你随手写个 tcp_server,就可以用这条流水线自我体检一遍:pidof tcp_server 看进程 → netstat -nltp | grep 8888 看 LISTEN → curl 或客户端连一下 → 连不上再开 tcpdump 看握手。跑通一遍,这些命令才算真正长到你自己身上。
思考题精讲
思考题 1:ping -c 5 www.qq.com 的输出里出现 ttl=48,而你的目标是公网主机。请估算数据包大概经过了多少跳,并说明这个估算的假设前提。如果三次连续 ping 分别返回 ttl=48、ttl=48、ttl=47,可能说明什么?
详解答案:ttl=48 接近 Linux/macOS 常见的初始 TTL 64,按 64 - 48 = 16 估算,数据包大概经历了 16 跳。估算的假设前提有两条:一是目标主机发送回包的初始 TTL 取 64(即认为对端是 Linux/macOS 类系统,而非 Windows 的 128、Cisco 的 255);二是回包里显示的 ttl 是回程剩余值,反映的是"目标→本机"这条回程路径的跳数。如果改成 128 起算,那 128-48=80 跳,明显不合常理,所以取 64 更合理。三次分别 48、48、47,正常情况下 TTL 在固定路径上应稳定不变;突然变成 47、多减了 1,最可能是路由发生了漂移(某次回程多绕了一台路由器),属于网络路径不稳定的信号,值得结合 traceroute 追踪确认。
思考题 2:你的 tcp_server 启动后没有报错,但客户端总是"连接失败"。你依次执行了 ps axj | grep tcp_server,确认进程在;netstat -nltp 却没看到 8888 端口处于 LISTEN。请分析可能出现的原因,并给出下一步排查命令。
详解答案:进程在但端口没进入 LISTEN,核心矛盾是"进程活着却没成功绑定并开始监听"。可能的原因有几类:
- 端口被占用:8888 已被别的进程占用,
bind()返回Address already in use,但你的程序可能吞掉了错误(没写失败分支),于是进程继续跑却没在 8888 上监听。下一步:netstat -nltp | grep 8888或ss -nltp '( sport = :8888 )',看这个端口到底被谁占了、State 是不是 LISTEN。 - 绑错了地址/端口:代码里
bind()用的端口或 IP 与你想的不一致(比如写死成别的端口),导致监听的其实是另一个端口。下一步:netstat -nltp | grep tcp_server(按进程名找它到底监听在哪个端口)。 - bind/Listen 失败但被忽略:程序没检查
bind/listen的返回值,错误被吞掉,进程空跑。下一步:看程序启动时的标准输出/日志,或临时在代码里对bind/listen的返回值做强校验。 - 权限问题:若端口 <1024(如 80、443),非 root 绑定会直接失败(
EACCES)。下一步:确认端口号是否为特权端口。
思考题 3:netstat -ano(这里 -a 所有、-n 数字、-o 等价的进程信息)显示某端口既有 ESTABLISHED 状态又有大量 CLOSE_WAIT 状态。CLOSE_WAIT 意味着什么?大量堆积说明你的服务端代码多半有什么问题?怎么缓解?
详解答案:CLOSE_WAIT 表示"对方已经主动关闭了连接,但本机这一端还没有调用 close() 来关闭自己这边的套接字"。TCP 四次挥手里,收到对方的 FIN 后,本机进入 CLOSE_WAIT,此时本机应尽快处理完手头数据并调用 close() 发送自己的 FIN,进入 LAST_ACK 直至关闭。大量 CLOSE_WAIT 堆积的根本原因,几乎总是程序没有正确、及时地关闭对端已关闭的连接——典型是这类代码:服务进程收到数据后一直在等待,或在出错分支、超时分支里漏了 close(fd),导致每个"外部主动断开"的连接都留下一个永远不会自动消失的套接字描述符,最终把文件描述符/内存耗尽,服务表现为"越来越慢、最终不可用"。缓解与根治方向:一是代码审查,确保所有读结束(recv 返回 0 或错误)、对端关闭的场景都调用了 close/close_sock;二是合理设置读超时,超时即关闭无人接管的空闲/卡死连接;三是限制连接数(backlog、描述符上限 ulimit),并监控 ss -nltp/ss -anutp 观察 CLOSE_WAIT 数量随时间的变化趋势来验证修复是否生效。
思考题 4:你在内网机器上 ping 内网同网段主机 通、ping 外网 IP(如 114.114.114.114) 报 Network is unreachable。请指出问题大概率出在哪一层、用哪条命令确认。
详解答案:同网段(直连网段)能通,说明链路层、网卡、IP、同网段转发都正常;唯独访问"外网"(不属于任何直连网段的目标)时报 Network is unreachable,这个报错的直接含义是本机路由表里没有能匹配到该目标的路径,也没有默认路由兜底。也就是说问题出在**路由(网络层转发决策)**这一层——本机"不知道"该把去往未知网络的包交到哪个网关。用 ip route(或 route -n)确认即可:正常情况下应能看到一行
default via 192.168.1.1 dev eth0如果这行缺失,就印证了判断——补配默认网关(临时 ip route add default via 192.168.1.1,持久配置需改系统网络配置文件),问题即可解除。这题也示范了"路由表缺失"与"网络不通"的区分:前者是 Network is unreachable(本机拍板没路),后者是 Destination Host Unreachable 或超时(本机有路但下一跳不可达)。
思考题 5:traceroute 追踪某外网主机时,中间若干跳全是 * * *,但目的主机最终能够到达。请解释可能的两种原因,并给出让路径"显形"的改进做法。
详解答案:中间跳出现 * * *,不代表网络断了,两种常见原因:一是那些路由器出于安全策略/负载考虑不回应"Time Exceeded"或相应探测的 ICMP 报文,也不理会 traceroute 默认使用的 UDP 探针;二是这些中间设备恰好处于一条默认不回包的链路上,于是 tcpdump/my 检测手段收不到它们的回包,就显示为 *。改进做法:traceroute 默认用 UDP 高端口探测,很多网络会丢弃——可改用 -I(基于 ICMP 探测,行为更接近 ping)再跑一遍,往往能看到原本是 * 的跳显形;也可试试 -T/指定端口(基于 TCP 探测),对"只放行常见协议"的网络更有效。但若换了多种探测方式该跳依旧 *,而后面跳又能通,那大概率就是那台设备刻意不回包,不必视为故障。总之判断故障要看首尾连贯性与最终目标能否到达,不能只凭某个 * 跳下结论。
到这堂加餐结束,你应该已经有了一双"看得见网络"的眼睛:ping 探连通、traceroute 画路径、ifconfig/ip 管门面、netstat/ss 看端口与连接状态、route 盯默认网关、nslookup/dig 审 DNS、curl 直接对话 HTTP、tcpdump 揪住包的真相,还有 pidof 兜底查进程。它们不是八个孤立的咒语,而是一套从网络层一路排到应用层的阶梯。
下一次当你那个不起眼的小服务"怎么都连不上"的时候,别再对着屏幕干瞪眼。按那套流程往下走:先确认自己在哪、再看有没有人坐在 8888 的门里等、再 ping、再看网关、再查解析、再用 curl 敲门,实在不行就抓包。每一步都在告诉你"问题在不在这一层"。把这套流程印进肌肉记忆,你就真正配得上"会用 Linux 网络排查工具"这几个字了。
还没有评论 — 第一条由你来留。