3. Linux 系统排查:端口占用的五大法宝
端口被占了,服务起不来。这事儿我碰过太多次了。
刚入行那会儿,一遇到端口问题就慌。后来发现,Linux 下查端口就那么几个工具——netstat、ss、lsof、fuser,还有 /proc 文件系统。把它们用熟了,端口问题基本都能搞定。
这一章,我把这几个工具的用法和实战经验掰开揉碎了讲。你跟着走一遍,以后排查端口问题,心里就有底了。
核心思路:端口占用的本质是某个进程在监听或连接某个端口。我们的目标就是找到「哪个进程占用了哪个端口」,然后决定是杀掉它,还是换个端口。
3.1 netstat 命令详解
netstat 是老牌工具了。我刚开始学 Linux 时,用的就是它。虽然现在 ss 更快,但很多老系统上还是得靠 netstat。
常用参数
| 参数 | 说明 | 我的用法 |
|---|---|---|
-t |
显示 TCP 端口 | 查 TCP 服务必加 |
-u |
显示 UDP 端口 | DNS、NTP 这类用 UDP |
-l |
只显示监听中的端口 | 我最常用的组合 |
-n |
以数字形式显示地址和端口 | 不加的话会去解析主机名,慢 |
-p |
显示进程 PID 和名称 | 查谁占端口,靠它了 |
-a |
显示所有连接 | 信息太多,一般配合 grep |
我个人最常用的组合是:
# 查看所有监听中的 TCP 端口,显示进程信息
netstat -tlnp
# 查看某个端口是否被占用
netstat -tlnp | grep :8080
小技巧:netstat -tlnp 这个组合我几乎天天用。-t 只看 TCP,-l 只看监听,-n 不解析名字,-p 显示进程。四个参数一组合,干净利落。
输出解读
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1234/sshd
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN 5678/mysqld
看第二行:127.0.0.1:3306 表示 MySQL 只监听本地回环地址,外网访问不了。我曾经遇到过一个问题,数据库连不上,一查发现就是绑定了 127.0.0.1,改成 0.0.0.0 就好了。
注意:netstat 在较新的 Linux 发行版中可能默认没安装。如果提示命令找不到,用 yum install net-tools 或 apt install net-tools 装上就行。
3.2 ss 命令详解
ss 是 netstat 的现代替代品。说白了,它直接从内核获取 socket 信息,速度比 netstat 快得多。我现在的习惯是:能用 ss 就不用 netstat。
常用参数
| 参数 | 说明 | 等效 netstat |
|---|---|---|
-t |
显示 TCP socket | -t |
-u |
显示 UDP socket | -u |
-l |
只显示监听中的 socket | -l |
-n |
不解析服务名 | -n |
-p |
显示进程信息 | -p |
-s |
显示统计信息 | 无直接等效 |
实战中我这样用:
# 查看所有监听中的 TCP 端口(推荐)
ss -tlnp
# 查看某个端口
ss -tlnp | grep :443
# 查看 socket 统计信息
ss -s
为什么推荐 ss? 在高并发服务器上,netstat 可能会卡住,而 ss 几乎瞬间出结果。我曾在一次生产事故中,靠 ss 快速定位了端口冲突,避免了服务中断。
输出解读
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=1234,fd=3))
LISTEN 0 128 127.0.0.1:3306 0.0.0.0:* users:(("mysqld",pid=5678,fd=16))
嗯,这里要注意:ss 的进程信息格式和 netstat 不太一样,但核心信息是一样的——PID、进程名、文件描述符。
3.3 lsof 命令详解
lsof 的全称是 List Open Files。Linux 下一切皆文件,socket 也是文件。所以 lsof 也能查端口。
我个人觉得 lsof 最方便的地方是:可以直接指定端口号,不用先列出来再 grep。
# 查看 8080 端口被谁占用
lsof -i :8080
# 查看所有网络连接
lsof -i
# 查看某个进程打开了哪些端口
lsof -i -p 1234
输出解读
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 12345 root 30u IPv4 123456 0t0 TCP *:8080 (LISTEN)
看这一行:PID 是 12345,进程名是 java,监听在 8080 端口。我曾经用这个命令抓出过一个隐藏的 Java 进程,它占用了 8080 端口,但 ps aux 一时没找到——因为进程名被改了。
小技巧:lsof -i :端口号 是我排查端口问题时第一个敲的命令。简单直接,不用记那么多参数。
3.4 fuser 命令使用
fuser 这个工具,说实话,平时用得不多。但一旦需要「杀掉占用某个端口的进程」,它比前面几个都方便。
# 查看 80 端口被哪个进程占用
fuser 80/tcp
# 杀掉占用 80 端口的进程
fuser -k 80/tcp
# 查看详细信息(显示 PID)
fuser -v 80/tcp
小心使用:fuser -k 会直接杀掉进程。我曾经在生产环境上不小心用 fuser -k 8080/tcp 杀掉了 Tomcat,导致服务中断了 5 分钟。所以,用之前一定确认清楚。
输出示例
# fuser 80/tcp
80/tcp: 12345
# fuser -v 80/tcp
USER PID ACCESS COMMAND
80/tcp: root 12345 F.... nginx
3.5 /proc 文件系统查看
前面几个工具,说到底都是从 /proc 文件系统读取信息的。如果你在极端环境下——比如系统被精简到只有 cat 和 ls——那 /proc 就是你最后的武器。
查看进程的 socket 信息
# 查看 PID 为 1234 的进程打开了哪些文件描述符
ls -l /proc/1234/fd/
# 查看进程的网络连接信息
cat /proc/1234/net/tcp
/proc/net/tcp 解读
/proc/net/tcp 里的内容比较原始,但信息很全:
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
0: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12345 1 0000000000000000 100 0 0 10 0
这里 00000000:0016 表示 0.0.0.0:22(22 的十六进制是 16)。0A 表示 LISTEN 状态。
实战场景:有一次我登录一个 Docker 容器,里面连 netstat 和 ss 都没有。我直接用 cat /proc/net/tcp 查到了端口占用情况。这就是 /proc 的威力——它永远在那里。
状态码对照表
| 十六进制 | TCP 状态 |
|---|---|
| 0A | LISTEN |
| 01 | ESTABLISHED |
| 02 | SYN_SENT |
| 03 | SYN_RECV |
| 04 | FIN_WAIT1 |
| 05 | FIN_WAIT2 |
| 06 | TIME_WAIT |
3.6 实战对比:什么时候用哪个?
| 场景 | 推荐工具 | 理由 |
|---|---|---|
| 快速查看监听端口 | ss -tlnp |
速度快,信息全 |
| 查某个具体端口 | lsof -i :端口 |
直接指定,不用 grep |
| 杀掉占用端口的进程 | fuser -k 端口/tcp |
一行搞定 |
| 老系统或最小化环境 | netstat -tlnp |
兼容性好 |
| 极端环境(无工具) | /proc/net/tcp |
终极手段 |
我的建议:把这五个工具都记在脑子里。平时用 ss 和 lsof 就够了,但关键时刻,多一个工具就多一条活路。