查监听用 ss -tulnp,定位进程用 lsof 和 fuser;netstat 已不预装,可退回 /proc/net/tcp。均经 WSL2 实测。
一、第一顺位 ss -tulnp:监听端口和进程一把看全
ss -tulnp # TCP+UDP、只听监听、数字端口、带进程,一行四合一
ss -tlnp # 只要 TCP;日常查端口占用就用这条
ss -ulpn # 单独看 UDP
实测一条 LISTEN 输出(被测服务是 18123 端口的 Python HTTP):tcp LISTEN 0 5 0.0.0.0:18123 0.0.0.0:* users:(("python3",pid=403,fd=3))。各列含义:状态、Recv-Q、Send-Q、本地地址:端口、对端、进程;对 LISTEN 套接字而言 Recv-Q 是 accept 积压、Send-Q 是监听 backlog(上面那行的 5 就是 backlog,不是异常)[1]。UDP 行状态显示 UNCONN 而不是 LISTEN(实测),因为 UDP 无连接。
一个劝人先上 root 的实测:nobody 用户跑 ss -tlnp,套接字行都在、Process 列全空;跑 lsof 则整条无输出、退出码 1。查服务的端口,直接 sudo 重来,别先怀疑命令坏了。
二、按端口精确过滤:别拿 grep 凑合
ss -tnp 'sport = :18123' # 本地端口是 18123 的 TCP 套接字
ss -tnp state established '( sport = :18123 )' # 只要已建立连接
ss -tan state time-wait 'sport = :18123' # 该端口的 TIME_WAIT 残留
ss -tnp 'dport = :18123' # 出站方向:连到 18123 的
手册的过滤语法:dport 或 sport 后接比较运算符(=、!=、>= 都可以)再写端口,端口前还能加协议族限定;state 后接状态标识(established、time-wait、listening 等)组合出任意状态集 [1]。为什么不 grep:实测对 ss 全量输出 grep :18123,会把对端端口恰为 18123、甚至数字列含这串数字的行一起带出来;sport 过滤精确到套接字本身。两个实测细节:过滤器带空格必须整体加单引号;用 state 过滤后输出表头会省掉 State 列,不是版本坏了。
三、从端口落到进程,再反查服务
lsof -nP -iTCP:18123 -sTCP:LISTEN # 手册同款组合:TCP、只听 LISTEN
fuser -v 18123/tcp # 表格列出 USER、PID、ACCESS、COMMAND
systemctl status <PID> # 由进程号反查它属于哪个服务
cat /proc/<PID>/cgroup # 出现 system.slice/xxx.service 即单位名
lsof 的 -i 选择器按”协议 + 可选 @主机 + :端口”拼写;-P 跳过端口号到服务名、-N 跳过主机名的反解析,手册明说跳过解析能让 lsof 跑快些 [2]。实测输出:python3 397 root 3u IPv4 26867 0t0 TCP *:18123 (LISTEN)。fuser 的 18123/tcp 是”协议 端口”简写,裸跑只往 stdout 吐 PID,脚本里取值方便(fuser(1) 本机手册)。
确认归属用 systemctl status 最直接:拿 cron 的 PID 160 实测,输出第一行就是单位名 cron.service、并标注 Main PID;/proc/160/cgroup 给出 0::/system.slice/cron.service,两处相互印证。
四、没有 netstat 也没有 ss:/proc/net/tcp 兜底
printf '%04X
' 18123 # 46CB:端口先转成十六进制
head -1 /proc/net/tcp # 列名:sl local_address rem_address st ...
grep ':46CB ' /proc/net/tcp # 命中行状态列 0A,即 LISTEN
grep ':46CC ' /proc/net/udp # UDP 在另一文件,状态 07 即 UNCONN
实测 Ubuntu 24.04 的 WSL 镜像默认没有 netstat;装上后 dpkg -L net-tools 显示 /bin/netstat 与 /sbin/ifconfig、/sbin/route 同属 net-tools 包 [3]。现代 Linux 环境可优先使用 iproute2 的 ip(8) 命令族;其手册页列出了网络对象和常用操作入口 [4],netstat 同族工具在新环境中通常直接换成 ss。ss、lsof 都没有的精简容器里读 /proc:IP 按字节小端写成十六进制(0100007F 就是 127.0.0.1),端口十六进制;状态列是十六进制码,与 ss 对照实测 0A=LISTEN、06=TIME_WAIT、07=UNCONN。行尾的 inode 号与 /proc/PID/fd 下的 socket: 加 inode 号 对应,套接字 inode 可用来在 /proc/net 下互查信息 [5]。
五、端口报占用但没监听者:TIME_WAIT 实测
ss -tan 'sport = :18999'
# TIME-WAIT 0 0 127.0.0.1:18999 127.0.0.1:60236
python3 -c 'import socket;s=socket.socket();s.bind(("127.0.0.1",18999))'
# OSError: [Errno 98] Address already in use
复现:服务端先关连接后,TIME_WAIT 落在服务端口自己身上——ss -l 查不到监听者,但 bind 同端口直接 EADDRINUSE(bind(2) 手册:The given address is already in use,本机 man-pages 6.7)。ip(7) 手册的解释是:地址关闭后一段时间不可用,除非开 SO_REUSEADDR,并提醒该标志会让 TCP 可靠性变差、慎用了再上。实测带 SO_REUSEADDR 重绑同端口即成功。所以”起服务报 address already in use、ss -l 却没人监听”,先 ss -tan state time-wait 看一眼,坐实了就等它自己过期,别乱杀进程。SO_REUSEPORT 是另一回事:它允许多进程各自 LISTEN 同一端口(nginx reuseport 类多实例玩法),ss 同端口出多行属正常(文档口径,未实测)。
六、撤销与安全:查询全是只读,动手只有三招
ss、lsof、fuser(不带 -k)、读 /proc 都不改状态,无需回滚。真要”让位”只有三招:systemctl restart <单位> 重启服务;systemctl stop <单位> 先停再启新实例;一次性进程才 kill
| 现象 | 原因 | 处理 | | — | — | — | | ss -p 的 Process 列全空 | 非 root 看不到别人进程 | sudo 重跑(实测) | | grep :8080 带出一堆无关行 | 对端端口、数字列也会命中 | 换 sport = :8080 过滤(实测) | | 端口报占用但 ss -l 查无 | TIME_WAIT 残留,不是监听 | ss -tan state time-wait 确认(实测,errno 98) | | netstat: command not found | 系统未预装 net-tools | 用 ss;容器里读 /proc/net/tcp(实测) | | lsof 卡住半天 | -i 默认反查 DNS 与端口名 | 加 -P -N,或 -sTCP:LISTEN 收窄(手册口径) | | 同一端口多行进程 | SO_REUSEPORT 多实例 | 正常现象(文档,未实测) | | 只见一行 *:80 没有 v4/v6 两行 | 绑 IPv6 通配、双栈同时服务 | curl -4、-6 各验一次(实测) |
**常见提问**
局限性:全部实测在一台 WSL2(Ubuntu 24.04.4、NAT 网络)完成;RPM 系发行版的包名、systemd 网络组件差异未复测;tcp_tw_reuse 等内核参数交互、SO_REUSEPORT 与跨命名空间场景仅文档口径。
**实测记录**
参考资料
- [1] ss(8) – Linux manual page (man7.org)(ss(8) 手册页)
- [2] lsof(8) – Ubuntu 24.04 noble manual page(Ubuntu lsof(8))
- [3] net-tools — Debian bookworm 包页(Debian net-tools 包)
- [4] ip(8) – Linux manual page (man7.org)(ip(8) 手册页)
- [5] proc(5) – Debian bookworm 手册页(Debian proc(5))
文中主要命令已在 WSL Ubuntu(24.04)实测并核对其在 Debian 系与通用发行版的文档口径。