首页 > Sysadmin > 正文

Linux 查看端口占用与对应进程:ss 与 lsof 实测

huyuanwen 2026-10-29 Sysadmin

查监听用 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 。fuser -k 克制使用:本机手册(fuser(1))写明默认 SIGKILL、不可捕获不可阻塞,-i 会对每个待杀进程逐个确认,可 -k -TERM 先换成可处理信号。动手前用 systemctl status 坐实归属,是服务的优先 restart 而不是 kill。另外容器各有网络命名空间,默认视图互不可见,端口在容器里的话进对应容器再跑上面的 ss(文档口径,未实测)。

| 现象 | 原因 | 处理 | | — | — | — | | 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 各验一次(实测) |

**常见提问**

  • ss 和 netstat 到底差多少?装 net-tools 2.10 对照实测:netstat -tulnp 对同一服务报出相同端口、相同进程(该列写作 PID/程序名),信息量一致,只是排版与解析机制不同;新环境不必再补装 net-tools。
  • TIME_WAIT 能不能手动清掉让位?它是内核状态,任何工具都删不了;正路是等它自然过期,或由应用自己开 SO_REUSEADDR 重绑(实测可行),ip(7) 已提示可靠性代价。
  • UDP 为什么查不出 LISTEN?UDP 无连接,ss -l 下列其绑定套接字、状态 UNCONN(实测),确认端口被占即可。
  • ss 只见 *:18125,是不是没监听 IPv4?实测绑 IPv6 通配的服务只有一行 *:18125,curl -4 与 -6 都返回 200,IPv4 被双栈覆盖,不用补一个 v4 监听。
  • 端口明明被占,ss 却查不到?先分清命名空间:容器、netns 里的套接字宿主默认看不到,docker exec 或 nsenter 进目标环境再跑 ss(文档口径,未实测)。
  • 局限性:全部实测在一台 WSL2(Ubuntu 24.04.4、NAT 网络)完成;RPM 系发行版的包名、systemd 网络组件差异未复测;tcp_tw_reuse 等内核参数交互、SO_REUSEPORT 与跨命名空间场景仅文档口径。

    **实测记录**

  • 测试机:WSL2 Ubuntu 24.04.4 LTS、内核 6.6.114.1-microsoft-standard-WSL2;ss iproute2-6.1.0-1ubuntu6.2、lsof 4.95.0、fuser PSmisc 23.7;2026-10-28 root 执行,测试进程与端口已全部关闭。
  • 监听与过滤:18123 的 LISTEN 行 backlog 列显 5、带 users 进程信息;UDP 18124 显 UNCONN;ESTAB 行的进程是监听进程本身、fd 换为 accept 出来的 4;state 过滤后表头无 State 列。
  • 权限:nobody 下 ss 有行无 Process 列;lsof 无输出且退出码 1;nobody 的 ss -ulpn 零条含 users。
  • lsof 与 fuser:-sTCP:LISTEN 精确命中一行;fuser -v 表格给出 root、397、F、python3,裸跑仅输出 397。
  • 服务归属:systemctl status 160 首行 cron.service 且标 Main PID: 160;/proc/160/cgroup 为 0::/system.slice/cron.service。
  • /proc/net:18123→46CB 状态 0A;18999→4A37 状态 06;UDP 46CC 状态 07;0100007F 对应 127.0.0.1。
  • TIME_WAIT:服务端先关连接后 ss 显 TIME-WAIT;不带 SO_REUSEADDR bind 报 errno 98,带则成功。
  • netstat:装前 command -v 为空;装后 dpkg -L 见 /bin/netstat,netstat -tulnp 对同一服务报 746/python3(net-tools 2.10);purge 加 autoremove 后 dpkg 记录清零。
  • 双栈:绑 :: 的服务仅一行 *:18125,curl -4、-6 均 200。
  • 未验证:SO_REUSEPORT 多实例、容器与跨命名空间排查、RPM 系发行版表现。

  • 参考资料

    文中主要命令已在 WSL Ubuntu(24.04)实测并核对其在 Debian 系与通用发行版的文档口径。