端口占用原理:进程与端口绑定机制
搞端口排查,你得先明白一件事——端口到底是怎么被占上的?
说白了,端口就是个数字标签。从 0 到 65535,一共 6 万多个。但真正能被你用的,也就前面那一段。
我刚开始做运维那会儿,遇到端口冲突就只会重启服务。后来被坑了几次,才老老实实把底层机制啃了一遍。嗯,今天咱们就把这块讲透。
进程怎么“占”住一个端口
一个进程想用网络收发数据,得先跟操作系统申请一个端口。这个申请动作,专业叫法就是 bind(绑定)。
流程其实很简单:
- 进程创建一个 socket(套接字)
- 调用 bind() 函数,告诉内核:“我要用端口 8080”
- 内核检查这个端口有没有被别的进程占着
- 没人占 → 绑定成功;有人占 → 返回 EADDRINUSE 错误
核心结论:一个端口同一时刻只能被一个进程绑定。这就是端口占用的本质。
我在项目中遇到过好几次这样的情况:明明服务停了,端口还是报占用。后来发现是 TIME_WAIT 状态没释放。这个后面细说。
端口的三六九等:分类与范围
端口不是随便用的。操作系统把端口分成了三个区间:
| 分类 | 范围 | 说明 | 典型例子 |
|---|---|---|---|
| Well-Known(知名端口) | 0 - 1023 | 系统保留,需要 root 权限才能绑定 | HTTP(80)、HTTPS(443)、SSH(22) |
| Registered(注册端口) | 1024 - 49151 | 用户程序常用,一般无权限限制 | MySQL(3306)、Redis(6379)、Tomcat(8080) |
| Dynamic/Private(动态/私有端口) | 49152 - 65535 | 临时端口,客户端发起连接时自动分配 | 浏览器访问网站时的临时端口 |
实战小技巧:排查端口占用时,先看端口号范围。如果是 1024 以下的端口报占用,大概率是系统服务或恶意进程。我曾经在客户服务器上抓到过挖矿程序,就是绑在 443 端口上伪装 HTTPS 流量。
端口状态详解:一张图看懂
你执行 netstat -an 或者 ss -an,会看到一堆状态。很多人只认识 LISTEN 和 ESTABLISHED,其实每个状态都有它的故事。
先看这张图,把 TCP 连接的生命周期串起来:
核心状态逐个拆解
LISTEN(监听状态)
这是服务端进程的典型状态。进程绑定了端口,等着客户端来连。你执行 ss -tlnp 看到的,基本都是这个状态。
我排查端口占用时,第一步就是看 LISTEN 状态。如果端口在 LISTEN,说明有进程在提供服务。这时候用 lsof -i :端口号 就能找到是谁。
ESTABLISHED(已建立连接)
连接已经建立,双方正在传输数据。这个状态最正常,但也最容易出问题——连接数太多,端口资源就耗尽了。
注意:ESTABLISHED 状态过多,不一定是坏事。但如果短时间内从几百飙到几万,八成是被攻击了。我曾经遇到过一台 Web 服务器,ESTABLISHED 连接数冲到 5 万,直接把端口池打爆了。
TIME_WAIT(等待关闭)
这个状态是端口占用的头号元凶。主动关闭连接的一方,在发送最后一个 ACK 后,会进入 TIME_WAIT 状态,默认等待 2 个 MSL(约 60 秒)。
为什么要等?说白了,就是为了确保对方收到了最后的 ACK。如果不等,万一 ACK 丢了,对方重发 FIN,这边已经关了,就收不到了。
但问题来了——高并发场景下,TIME_WAIT 会堆积。你想想看,每秒处理几千个请求,每个连接关闭后都要等 60 秒,端口很快就用光了。
避坑指南:我曾经在压测时遇到端口不够用,一查全是 TIME_WAIT。后来调整了 net.ipv4.tcp_tw_reuse 和 net.ipv4.tcp_tw_recycle 参数才解决。不过要注意,tcp_tw_recycle 在 NAT 环境下会出问题,这个后面专门讲。
CLOSE_WAIT(被动关闭等待)
这个状态比 TIME_WAIT 更危险。它表示对方已经关闭连接,但本端还没关。说白了,就是程序有 bug,没调用 close()。
CLOSE_WAIT 堆积,基本可以断定是代码问题。我在项目中见过最夸张的一次,CLOSE_WAIT 堆了 8000 多个,应用直接卡死。查了半天,发现是 HTTP 请求的 Response 流没关闭。
其他状态一览
| 状态 | 含义 | 常见场景 |
|---|---|---|
| SYN-SENT | 客户端发了 SYN,等待服务器回应 | 连接超时、防火墙拦截 |
| SYN-RECEIVED | 服务器收到 SYN,发了 SYN+ACK,等客户端 ACK | 半连接攻击(SYN Flood) |
| FIN-WAIT-1 | 主动关闭方发了 FIN,等待对方 ACK | 正常关闭过程 |
| FIN-WAIT-2 | 收到对方 ACK,等待对方 FIN | 对方不关连接时会卡住 |
| LAST-ACK | 被动关闭方发了 FIN,等待最后一个 ACK | 正常关闭过程 |
| CLOSING | 双方同时关闭,都发了 FIN | 极少见 |
端口占用的本质:一张表说清楚
你排查端口占用时,其实就是在回答三个问题:
- 谁占的? → 找到进程 PID
- 什么状态? → 判断是正常服务还是异常残留
- 能不能释放? → 决定下一步操作
我习惯把端口占用分成两类:
| 类型 | 典型状态 | 处理方式 |
|---|---|---|
| 正常占用 | LISTEN、ESTABLISHED | 确认是预期服务,不动它 |
| 异常占用 | TIME_WAIT 堆积、CLOSE_WAIT 堆积、僵尸进程 | 调优参数、修代码、杀进程 |
记住一句话:端口本身不会“坏”,它只是被占着。你排查的目标不是“清空端口”,而是“找到占着端口的那个家伙,判断它该不该在那”。
嗯,原理部分就到这。下一块咱们要动手了——用命令把上面这些状态一个个抓出来看。到时候你会发现,纸上谈兵和真刀真枪,还是有差距的。