2. 端口占用排查与解决实战:网络基础回顾 - TCP/IP协议栈与端口号
聊端口排查之前,咱们得先把底层的网络模型捋一遍。说实话,我见过不少同事排查端口问题时,连数据包怎么走的都说不清楚,结果折腾半天发现是协议栈配置错了。嗯,咱们别犯这个错。
2.1 TCP/IP协议栈:数据包的“快递系统”
TCP/IP协议栈,说白了就是互联网的快递系统。你发一个请求,数据得经过层层打包、拆包,才能从你的电脑送到服务器手里。
我个人习惯把协议栈分成四层来理解,每一层都有自己的职责:
| 层级 | 名称 | 核心职责 | 常见协议/工具 |
|---|---|---|---|
| 第4层 | 应用层 | 为用户提供网络服务接口 | HTTP、SSH、MySQL、Redis |
| 第3层 | 传输层 | 端到端通信、端口号管理 | TCP、UDP |
| 第2层 | 网络层 | 路由寻址、IP地址管理 | IP、ICMP、ARP |
| 第1层 | 网络接口层 | 物理介质传输、帧封装 | 以太网、Wi-Fi |
你想想看,排查端口占用时,我们主要跟第3层(传输层)打交道。但很多时候,问题出在第4层(应用层)的配置上。比如Nginx绑定了端口,但配置文件写错了IP,你查端口占用是查不到的。
netstat 或 ss,应用层的问题得看进程日志。别一上来就重启服务,先定位层级。
2.2 端口号:传输层的“门牌号”
端口号是什么?就是传输层给每个网络进程分配的门牌号。一台服务器上跑着几十个服务,没有端口号,数据包就不知道该送给谁。
端口号的范围是 0 到 65535,但实际使用中分成了三段:
| 范围 | 分类 | 说明 | 常见例子 |
|---|---|---|---|
| 0 - 1023 | 系统端口(Well-Known Ports) | 需要root权限才能绑定 | 80 (HTTP)、443 (HTTPS)、22 (SSH) |
| 1024 - 49151 | 注册端口(Registered Ports) | 普通用户可绑定,常用于应用服务 | 3306 (MySQL)、6379 (Redis)、8080 (Tomcat) |
| 49152 - 65535 | 动态/私有端口(Dynamic Ports) | 临时分配给客户端连接使用 | 系统自动分配的临时端口 |
这里有个坑,我曾经遇到过:某次部署Java应用,开发非要绑定端口 80,结果启动时报权限不足。嗯,这就是没搞清楚系统端口的限制。解决方案很简单:要么用root启动(不推荐),要么用iptables做端口转发。
2.3 TCP与UDP:两种不同的“快递方式”
端口号是门牌号,但快递怎么送,还得看用的是TCP还是UDP。这两个协议在传输层,但工作方式完全不同。
| 特性 | TCP | UDP |
|---|---|---|
| 连接方式 | 面向连接(三次握手) | 无连接 |
| 可靠性 | 可靠传输,有重传机制 | 不可靠,尽最大努力交付 |
| 数据顺序 | 保证数据按序到达 | 不保证顺序 |
| 速度 | 相对较慢 | 快,低延迟 |
| 典型应用 | HTTP、SSH、MySQL、FTP | DNS、DHCP、视频流、游戏 |
排查端口占用时,一定要区分TCP和UDP。同一个端口号,可以同时被TCP和UDP占用,互不冲突。比如DNS服务,既监听TCP 53又监听UDP 53,这是合法的。
我记得有一次排查一个视频流卡顿的问题,开发说端口没占用,但我用 ss -tuln 一看,UDP端口被另一个进程占了。这就是只看TCP没看UDP的教训。
2.4 端口状态机:从LISTEN到TIME_WAIT
TCP端口的状态变化,是排查端口占用问题的核心。你执行 netstat -an 看到一堆状态,每个状态都有含义。
常见的TCP状态:
- LISTEN:服务端正在监听,等待客户端连接。这是端口占用的典型状态。
- ESTABLISHED:连接已建立,正在通信。
- TIME_WAIT:主动关闭连接后,等待2MSL时间确保所有数据包消失。
- CLOSE_WAIT:被动关闭连接,等待应用程序调用close()。
- SYN_SENT:客户端发送SYN包,等待服务端回应。
这里重点说一下 TIME_WAIT。很多运维新手看到大量TIME_WAIT就慌了,以为端口泄漏。其实这是TCP正常关闭流程的一部分。但如果你在高并发场景下,TIME_WAIT过多确实会导致端口耗尽。
IP:端口:协议 三元组被占用。同一个端口,不同IP或不同协议(TCP/UDP)可以共存。排查时一定要用 -t、-u 参数区分协议。
2.5 端口占用排查的底层逻辑
理解了协议栈和端口号,排查端口占用就变得清晰了。核心逻辑就三步:
- 确认哪个端口被占用:用
ss -tuln | grep <端口号>或lsof -i :<端口号>。 - 找到占用端口的进程:用
lsof -i :<端口号>或fuser <端口号>/tcp。 - 决定如何处理:要么杀掉进程,要么改配置换端口。
但实际场景往往更复杂。比如端口被内核的某个模块占了,或者被Docker的端口映射占了。这时候就得结合协议栈的知识来分析了。
下面这张图,是我自己总结的端口占用排查流程,你可以照着走一遍:
2.6 实战中的几个关键点
最后,分享几个我在实战中总结的经验:
- 端口占用不等于进程活着:有时候进程挂了,但端口还被内核占用(比如TIME_WAIT状态)。这时候等一会儿或者用
setsockopt设置SO_REUSEADDR。 - IPv4和IPv6的端口是独立的:同一个端口,IPv4和IPv6可以分别占用。排查时记得加
-4或-6参数。 - Docker端口映射是iptables做的:Docker容器里的端口占用,本质是宿主机iptables的DNAT规则。用
iptables -t nat -L -n查看。 - 不要迷信netstat:netstat已经过时了,ss命令更快、信息更全。我建议你从现在开始只用ss。
ss -tlnp sport = :80 # 查看TCP 80端口ss -ulnp sport = :53 # 查看UDP 53端口加上
-p 参数才能看到进程信息,别忘了用root权限。