ss、lsof、netstat、fuser 四件套实战
文章题图
1
读前导读
1.1
这篇文章讲什么
这是一篇关于"端口占用排查"的完整实战指南。表面上看,"Address already in use"只是一个很普通的报错;但真正去追的时候,会发现它背后牵涉到 socket、内核、systemd、容器、namespace 多种层次。这篇文章会从最基础的命令讲到复杂的"幽灵进程"场景,再讲到 systemd 和游离进程共存的情况。读完,你将能够应对 99% 的端口占用问题。
1.2
你会学到什么
- 四个最常用的端口排查工具:ss、lsof、netstat、fuser
- 它们的适用场景、差异、坑点
- 进程"找不到"时的 /proc 终极排查
- TIME_WAIT、幽灵进程、systemd restart loop、容器 namespace 四大疑难场景
- 一份"端口占用一键排查"脚本
1.3
典型场景流程图
先说结论
“Address already in use”——这是每一个运维工程师都会遇到的经典报错。无论是启动一个 Web 服务、起一个数据库、还是部署一个 Gateway,只要端口被占用,错误信息就会无情地弹出来。然后你就会陷入一个熟悉的循环:启动服务 → 报错端口被占用 → 用 netstat 查 → 找到一个 PID → 用 ps 查 PID → 找不到对应进程 → 怀疑人生 → 重启服务器 → 问题依然存在。
这个剧本我演了无数遍。这篇文章把这几年积累的所有经验都整理出来。文章会从最基础的命令讲到复杂的"幽灵进程"场景,再讲到 systemd 和游离进程共存的情况。
2
问题背景
2.1
典型场景
假设你要启动一个 Gateway 服务,监听在 18789 端口。你运行启动命令,结果报错:
Error: listen tcp 0.0.0.0:18789: bind: address already in use服务启动失败。你需要找出:
- 谁在占用 18789 端口
- 这个占用者是什么进程
- 这个进程能不能安全杀掉
- 杀掉之后如何让服务正常启动
这就是典型的"端口占用排查"场景。
2.2
排查工具一览
| 工具 | 主要用途 | 优点 | 缺点 |
|---|---|---|---|
| ss | 查看端口监听 | 最快、现代、默认安装 | 输出信息较少 |
| netstat | 查看端口监听 | 经典、输出友好 | 已被 ss 取代 |
| lsof | 查看进程打开的文件 | 信息最全 | 需要 root 权限 |
| fuser | 查看使用文件/端口的进程 | 可以直接杀进程 | 信息不如 lsof 全 |
| /proc | 直接读取内核信息 | 最权威 | 操作复杂 |
四件套工具对比
3
第一招:使用 ss 命令
ss(Socket Statistics)是netstat的现代替代品,读取/proc/net直接获取信息,速度比netstat快几个数量级。
3.1
基础用法
# 查看所有 TCP 端口的监听状态ss -tlnp# 只查看特定端口ss -tlnp | grep 18789# 查看所有状态的连接(包括 ESTABLISHED、TIME_WAIT 等)ss -tnp# 查看 UDP 端口ss -ulnp参数说明:
-t:TCP-u:UDP-l:只显示 LISTEN 状态-n:不解析服务名(显示数字端口)-p:显示进程信息
3.2
输出示例
LISTEN 0 128 0.0.0.0:18789 0.0.0.0:* users:(("openclaw-gatewa",pid=281750,fd=7))从输出中你可以读到:
- 协议:LISTEN(监听状态)
- 地址:
0.0.0.0:18789(IPv4 所有地址的 18789 端口) - 进程:
openclaw-gatewa(pid 281750) - 文件描述符:fd 7
3.3
进阶用法
# 查看 IPv4 + IPv6 监听ss -tlnp -A inet# 按进程名过滤ss -tlnp 'sport = :18789'# 查看进程所属的网络命名空间ss -tlnp -n netns# 显示 socket 详细信息(包括内存、计时器等)ss -tlnepi实战建议:在生产环境中,ss -tlnp是你应该第一反应使用的命令。它速度快、输出清晰、大部分 Linux 发行版都默认安装。
ss 命令速查
4
第二招:使用 lsof
lsof(List Open Files)可以列出进程打开的所有文件,包括网络连接。它是排查端口问题的"瑞士军刀"。
4.1
基础用法
# 查看占用特定端口的进程lsof -i :18789# 查看 IPv4 端口lsof -i4 :18789# 查看 IPv6 端口lsof -i6 :18789# 同时查看协议类型lsof -i tcp:18789lsof -i udp:187894.2
输出示例
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAMEopenclaw-g 281750 root 7u IPv4 1234567 0t0 TCP *:18789 (LISTEN)openclaw-g 281750 root 8u IPv6 1234568 0t0 TCP *:18789 (LISTEN)字段解读:
- COMMAND:命令名
- PID:进程 ID
- USER:进程所属用户
- FD:文件描述符(
7u表示 7 号 fd,u 表示可读写) - TYPE:socket 类型(IPv4/IPv6)
- DEVICE:设备号
- NODE:协议和地址
- NAME:连接信息
4.3
进阶用法
# 查看进程打开的所有网络连接lsof -p 281750# 查看进程打开的所有文件lsof -p 281750 | head -30# 查看用户打开的网络连接lsof -u root -i# 反向查询:根据 inode 找进程lsof -i @127.0.0.1# 持续监控lsof -i :18789 -r 5 # 每 5 秒刷新一次实战建议:lsof是排查"找不到进程"问题的神器。当ss显示有进程占用但ps找不到时,lsof往往能给你答案。
lsof 字段解读
5
第三招:使用 netstat
虽然ss是新欢,但netstat在很多老系统上仍然是默认工具。掌握它也很有必要。
5.1
基础用法
# 查看所有监听端口netstat -tlnp# 查看特定端口netstat -tlnp | grep 18789# 查看所有连接(包括非监听)netstat -anp# 查看 UNIX 域套接字netstat -xlnp参数说明:
-t:TCP-u:UDP-l:LISTEN 状态-n:数字端口-p:进程-a:所有连接
5.2
输出示例
tcp 0 0 0.0.0.0:18789 0.0.0.0:* LISTEN 281750/openclaw-gatcp 0 0 [::]:18789 [::]:* LISTEN 281750/openclaw-gw5.3
注意事项
# 如果 netstat 命令不存在,需要安装# CentOS/RHEL:yum install -y net-tools# Ubuntu/Debian:apt-get install -y net-tools实战建议:在写脚本或者文档时,建议优先使用ss。但在 SSH 到老系统时,先试试netstat也无妨。
6
第四招:使用 fuser
fuser可以显示"使用某个文件/端口的所有进程",并支持直接发送信号。
6.1
基础用法
# 查看占用 18789 端口的进程fuser 18789/tcp# 查看详细信息fuser -v 18789/tcp# 直接杀死占用进程(慎用!)fuser -k 18789/tcp# 发送特定信号fuser -k -TERM 18789/tcpfuser -k -KILL 18789/tcp # 等同于 -96.2
输出示例
18789/tcp: 281750只显示 PID,简单直接。
6.3
进阶用法
# 查看进程使用的所有文件fuser -v /var/log/openclaw/# 查看挂载点的占用fuser -mv /mnt/data# 命名空间级别fuser -m /mnt/data实战建议:fuser -k是"一键杀进程"工具,但慎用。在生产环境使用前,务必确认这个进程可以杀。
7
终极招:直接查 /proc
当所有命令都"找不到进程"时,那就只能直接读/proc了。
7.1
查看占用 18789 的进程
# 找到占用 18789 端口的 socket inodecat /proc/net/tcp | awk '$2 ~ /:4935$/ {print $10}'# 18789 的十六进制是 49357.2
反向查找
# 根据 inode 找到进程find /proc -path "*/fd/*" -exec ls -l {} \; 2>/dev/null | grep "socket:[1234567]"7.3
查看进程命令行
# 根据 PID 查看完整命令行cat /proc/281750/cmdline | tr '\0' ' '# 查看进程状态cat /proc/281750/status# 查看进程启动时间ps -p 281750 -o lstart=实战建议:直接查/proc是"核武器"级别的方法。一般情况下用ss和lsof就够了,但遇到诡异问题时,这是最后的退路。
8
常见疑难场景
8.1
场景:ss 显示有进程,ps 找不到
这是经典的"幽灵进程"问题。
症状:
ss -tlnp | grep 18789# 输出:users:(("openclaw-gatewa",pid=281750,fd=7))ps -p 281750# 输出:error: process ID list syntax error原因:
- 进程是 zombie 状态(
Z),已死但未释放资源 - 进程是 namespace 隔离的,主机 ps 看不到
- 进程在容器里,ps 只能看到容器内的
排查方法:
# 查看所有进程(不限于当前会话)ps auxf# 查看 zombie 进程ps -eo pid,ppid,stat,cmd | awk '$3 ~ /Z/'# 查看进程的父进程ps -o ppid= -p 281750解决方法:
- 如果是 zombie,找到父进程,重启父进程
- 如果是 namespace 问题,进入对应 namespace
- 如果是容器问题,进入容器查看
8.2
场景:端口被 TIME_WAIT 占用
TIME_WAIT 状态机
症状:
ss -tan | grep 18789# 输出:TIME-WAIT 状态的连接一大堆原因:
服务刚刚关闭,但 TCP 连接还在 TIME_WAIT 状态(等待 2MSL 时间,确保最后的 ACK 被对端收到)。这个状态下,端口虽然没人在用,但内核不允许新进程绑定。
解决方法:
# 方法 1:等待 1-2 分钟(推荐)# 让 TIME_WAIT 自然消失# 方法 2:开启 SO_REUSEADDR(推荐在代码中处理)# 允许绑定 TIME_WAIT 状态的地址# 方法 3:调整内核参数(不推荐)net.ipv4.tcp_tw_reuse = 1sysctl -p# 方法 4:使用 SO_REUSEPORT(应用层处理)# 允许多个进程绑定同一端口(负载均衡场景)8.3
场景:systemd 一直在 restart loop
systemd restart loop 排查
症状:
systemctl status openclaw-gateway# Active: activating (auto-restart) (Result: exit-code)# 状态: failed但实际上服务在用——某个游离进程占用了 18789 端口,systemd 启动失败但又不停重启。
排查方法:
# 1. 查看端口占用ss -tlnp | grep 18789# 2. 如果有游离进程,杀掉它kill <pid># 3. 等待端口释放ss -tlnp | grep 18789 # 确认无监听# 4. 重启 systemd 服务systemctl restart openclaw-gateway预防方法:
- 永远只通过 systemd 启动服务
- 禁止直接运行
xxx-daemon & - 定期巡检是否有游离进程
8.4
场景:容器内端口被占用的错觉
容器 namespace 隔离
症状:
- 主机上
ss -tlnp看不到占用 - 容器内
ss -tlnp看到端口被占用
原因:
容器有自己的网络命名空间,主机和容器的端口是隔离的。容器内占用不代表主机占用。
排查方法:
# 进入容器查看docker exec -it <container> ss -tlnp# 或者使用 nsenternsenter -t <container_pid> -n ss -tlnp# 查看容器端口映射docker port <container>9
一键排查脚本
把上面的方法组合起来,写一个"端口占用一键排查"脚本:
#!/bin/bash# check_port.sh - 端口占用一键排查PORT=${1:-18789}echo "=== 端口 $PORT 占用情况排查 ==="echoecho "1. ss 命令结果:"ss -tlnp 2>/dev/null | grep ":$PORT " || echo " 无 LISTEN 状态占用"echoecho "2. lsof 命令结果:"lsof -i :$PORT 2>/dev/null || echo " lsof 不可用或无占用"echoecho "3. netstat 命令结果:"netstat -tlnp 2>/dev/null | grep ":$PORT " || echo " netstat 不可用或无占用"echoecho "4. fuser 命令结果:"fuser -v $PORT/tcp 2>&1 || echo " fuser 不可用或无占用"echoecho "5. /proc/net/tcp 原始数据:"HEX_PORT=$(printf "%04X" $PORT)echo " 端口 $PORT 的十六进制: $HEX_PORT"cat /proc/net/tcp 2>/dev/null | awk -v hp="$HEX_PORT" '$2 ~ ":"hp"$" || $3 ~ ":"hp"$" {print " "$0}' | head -5echoecho "6. TIME_WAIT 状态连接数:"ss -tan 2>/dev/null | grep ":$PORT " | grep TIME-WAIT | wc -lechoecho "=== 排查完成 ==="使用方法:
chmod +x check_port.sh./check_port.sh 18789一键排查脚本
10
常见问答
10.1
Q1:ss 和 netstat 应该用哪个?
A:优先用ss。ss速度更快、信息更全、是iproute2包的一部分。在 CentOS 7+、Ubuntu 16.04+、所有现代 Linux 发行版上都默认安装。
只在老系统或者脚本兼容性要求高时才用netstat。
10.2
Q2:lsof 在容器里查不到东西怎么办?
A:在容器里使用lsof需要特权模式,或者直接进入容器的网络命名空间:
# 方法 1:特权模式启动docker run --privileged ...# 方法 2:共享网络命名空间docker run --network host ...# 方法 3:使用 nsenterPID=$(docker inspect -f '{{.State.Pid}}' <container>)nsenter -t $PID -n lsof -i :1878910.3
Q3:杀进程后端口还是占用怎么办?
A:有以下几种可能:
- 进程没杀干净——可能存在父子进程,需要全部杀掉
- TIME_WAIT 状态——等待几分钟自然消失
- 内核还在回收——
cat /proc/net/sockstat查看 socket 状态 - 多个进程占用——
lsof -i :PORT看完整列表
10.4
Q4:如何避免端口被占用的问题?
A:
- 统一管理:所有服务都通过 systemd 或容器编排管理,禁止手动启动
- 配置端口白名单:在防火墙中只开放需要的端口
- 监控告警:对端口状态做监控,异常时及时告警
- 定期巡检:定期检查游离进程和端口占用情况
10.5
Q5:lsof 很慢,有更快的替代品吗?
A:ss已经是更快的替代品了。如果lsof还是慢,可以考虑:
fuser:速度更快,但信息少- 直接读
/proc/net/tcp:最快,但要自己解析 pidstat:看进程级别的网络活动
11
总结
端口占用排查是运维的基本功,看似简单,实则涉及很多细节。本文从ss、lsof、netstat、fuser四个工具入手,配合/proc底层分析,覆盖了:
- 基础端口查看
- 进程 PID 定位
- 幽灵进程排查
- TIME_WAIT 处理
- systemd restart loop
- 容器网络命名空间
- 一键排查脚本
核心要点:
- 首选 ss,信息全、速度快、现代化
- lsof 排查诡异问题,信息最全
- fuser 一键杀进程,但要慎用
- /proc 终极武器,所有命令失效时的最后退路
希望本文能成为大家排查端口占用问题的"案头手册"。下次再遇到"Address already in use",相信你不会再一头雾水了。