专栏第十六篇,网络与协议篇第九章。上一篇我们聊了 DNS,搞懂了浏览器是怎么把
google.com翻译成142.250.72.206这个 IP 的。但问题来了——数据包千辛万苦传到服务器,服务器上同时跑着 Nginx、Node.js、MySQL、Redis、SSH……这包到底该交给谁?总不能让所有程序抢着处理吧?今天的主角 Port(端口),就是操作系统用来区分"这台机器上不同程序"的编号。它是网络通信里最容易被忽略、却又天天打交道的概念——3000、5173、8080、3306、6379这些数字背后,藏着一整套规则。
一、端口是什么?——IP 是酒店地址,端口是房间号
端口(Port)是一个 16 位整数,范围 0 ~ 65535,用于在一台主机上区分不同的应用程序或服务。
核心比喻
IP 地址 = 酒店地址(如 142.250.72.206)
端口 = 房间号(如 443)
访问 https://google.com
= 找到"142.250.72.206 这家酒店"的"443 号房间"
- IP 解决"哪台机器"(网络层)
- 端口解决"机器上的哪个程序"(传输层)
💡 一台服务器可以同时跑很多服务,全靠端口区分。没有端口,数据到了机器也不知道交给谁——就像快递只写了酒店地址没写房间号,前台只能干瞪眼。
为什么需要端口?
假设没有端口:
- 你的服务器同时跑着 Web 服务、数据库、SSH、FTP……
- 一个数据包从网络到达网卡
- 操作系统内核懵了:"这包是给谁的?"
端口就是操作系统做"多路复用"的依据:内核维护一张"端口 → 进程"的映射表,数据包到达时,根据目标端口,交给对应的进程处理。
二、端口的分类(0~65535 的三段划分)
IANA(互联网号码分配机构)把端口分成三段,每段用途不同。
1️⃣ 知名端口(Well-Known Ports):0 ~ 1023
分配给最核心的互联网服务,在 Linux/Unix 上需要 root 权限才能绑定。
| 端口 | 服务 | 说明 |
|---|---|---|
| 80 | HTTP | 网页(明文) |
| 443 | HTTPS | 网页(加密) |
| 22 | SSH | 远程登录 |
| 21 | FTP | 文件传输(控制通道) |
| 25 | SMTP | 发邮件 |
| 53 | DNS | 域名解析 |
⚠️ 注意:MySQL(3306)、Redis(6379)、PostgreSQL(5432)这些虽然"知名",但端口号 大于 1023,严格来说属于"注册端口",不是知名端口。
2️⃣ 注册端口(Registered Ports):1024 ~ 49151
分配给特定的应用程序或服务,由 IANA 登记管理。开发者自己写的服务,通常也在这段里选端口。
| 端口 | 服务 |
|---|---|
| 3306 | MySQL |
| 5432 | PostgreSQL |
| 6379 | Redis |
| 27017 | MongoDB |
| 5672 | RabbitMQ |
| 8080 | HTTP 替代端口(Tomcat 默认) |
| 8443 | HTTPS 替代端口 |
| 9090 | Prometheus |
| 9200 | Elasticsearch |
3️⃣ 动态/私有端口(Dynamic/Private Ports):49152 ~ 65535
客户端临时使用的端口,也叫"临时端口"(ephemeral port)。
- 当你访问
google.com:443时,操作系统会从 49152~65535 中随机挑一个端口作为源端口 - 服务器响应时,数据就发回这个端口
- 连接关闭后,端口释放,可被下次复用
📌 你在
netstat里看到的那些localhost:51023、localhost:52341,基本都是临时端口。
三、TCP 端口 vs UDP 端口
端口同时存在于 TCP 和 UDP 两个协议上,它们是独立的!
TCP:80 ≠ UDP:80
- TCP 端口:面向连接的服务(HTTP、HTTPS、SSH、MySQL)
- UDP 端口:无连接的服务(DNS 查询、DHCP、视频流、游戏)
所以一台机器上,TCP 80 和 UDP 80 可以同时被不同进程占用,互不影响。操作系统内部实际上维护了两张表:一张 TCP 的,一张 UDP 的。
四、常见端口速查表(开发者必备)
🌐 Web 相关
| 端口 | 用途 |
|---|---|
| 80 | HTTP(浏览器默认) |
| 443 | HTTPS(浏览器默认) |
| 3000 | Node.js 开发服务器(Next.js、React、Create-React-App 默认) |
| 5173 | Vite 开发服务器默认端口 |
| 8080 | HTTP 备用(Java/Tomcat、Nginx 代理常用) |
| 8000 | Python/Django、FastAPI 开发 |
| 5000 | Flask 默认 |
| 9000 | SonarQube、php-fpm |
🗄️ 数据库与缓存
| 端口 | 数据库 |
|---|---|
| 3306 | MySQL |
| 5432 | PostgreSQL |
| 6379 | Redis |
| 27017 | MongoDB |
| 1521 | Oracle |
| 1433 | SQL Server |
| 7001 | Cassandra |
| 9042 | Cassandra(CQL 原生协议) |
🔧 运维与基础设施
| 端口 | 服务 |
|---|---|
| 22 | SSH |
| 3389 | RDP(Windows 远程桌面) |
| 9090 | Prometheus |
| 30000 ~ 32767 | Kubernetes NodePort 默认范围 |
建议把这张表存下来,配环境、排查问题时反复用。
五、端口的六大应用场景
1️⃣ 开发调试
# 前端开发
npm run dev -- --port 3000 # Next.js
npm run dev -- --port 5173 # Vite
# 后端开发
node server.js # 默认 3000
python manage.py runserver 0.0.0.0:8000 # Django
浏览器访问 http://localhost:3000,本质就是告诉操作系统:"我要连本机的 3000 号端口。"
2️⃣ 生产环境部署
# Nginx 监听 80,转发到后端 3000
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
为什么生产用 80/443 而非 3000?
- 浏览器访问
http://example.com时默认连 80,https://example.com默认连 443 - 如果用 3000,用户必须输入
http://example.com:3000,体验差 - 最佳实践:Nginx 监听 80/443(需要 root),反向代理到内部服务的 3000 端口(非 root 运行更安全)
3️⃣ 端口转发与 SSH 隧道
# 把远程服务器的 3306 映射到本地 3306
ssh -L 3306:localhost:3306 user@remote-server
# 之后访问 localhost:3306,实际连的是远程 MySQL
这是本地开发连线上数据库最安全的方式——数据库端口不用暴露在公网。
4️⃣ 容器化部署(Docker)
# docker-compose.yml
services:
web:
ports:
- "8080:3000" # 宿主机 8080 → 容器 3000
访问 http://localhost:8080,实际访问容器内的 3000 端口。Docker 通过端口映射,让多个容器的 3000 可以共存,靠宿主机不同端口区分。
5️⃣ 微服务架构
API Gateway :80 → 按路径转发到:
├── user-service:3001
├── order-service:3002
└── payment-service:3003
每个微服务绑不同端口,网关根据 URL 路径做路由。
6️⃣ 网络扫描与安全排查
# 查看本机正在监听哪些端口
netstat -tlnp # Linux
lsof -iTCP -sTCP:LISTEN -n -P # macOS
Get-NetTCPConnection | Where-Object State -EQ Listen # Windows PowerShell
# 扫描远程主机开放了哪些端口
nmap example.com
nmap 是黑客和运维都爱用的神器——前者用来找攻击面,后者用来确认服务是否正常暴露。
六、端口相关的四个核心概念
1. 监听(Listen)
服务程序启动后,调用 bind() + listen(),在操作系统的端口上"竖起耳朵"等待连接。
// Node.js 监听 3000 端口
const http = require('http');
http.createServer((req, res) => {
res.end('Hello');
}).listen(3000, () => {
console.log('Server listening on port 3000');
});
2. 端口冲突
如果两个程序都想监听同一个端口,就会报错:
Error: listen EADDRINUSE: address already in use :::3000
解决方法:
# 杀掉占用 3000 端口的进程
lsof -ti:3000 | xargs kill -9
# 或者换一个端口启动
3. 防火墙与端口
- 入站规则:允许外部访问本机哪些端口
- 出站规则:允许本机访问外部哪些端口
云服务器(AWS、阿里云、腾讯云)的"安全组",本质就是端口级别的防火墙。很多新手部署完网站访问不了,十有八九是安全组没开 80/443。
4. 0.0.0.0 vs localhost
.listen(3000, 'localhost') // 只能本机访问
.listen(3000, '0.0.0.0') // 允许所有 IP 访问(容器/生产环境常用)
localhost/127.0.0.1:仅本机回环,外部访问不到0.0.0.0:绑定到所有网络接口,局域网和公网都能连
🐛 Docker 容器里的服务一定要监听
0.0.0.0而不是localhost,否则宿主机和外部都访问不到,这是初学者高频踩坑点。
七、端口与 URL 的关系
https://example.com:443/path?query=1
│ │ │
│ │ └─ 端口(省略时,https 默认 443,http 默认 80)
│ └─ 域名
└─ 协议
浏览器默认行为:
http://example.com→ 实际连接example.com:80https://example.com→ 实际连接example.com:443
如果服务跑在其他端口,必须在 URL 中显式指定:
http://example.com:3000
八、一个完整的请求旅程(串联整个系列)
把 DNS、TCP、端口、HTTP、Nginx 全串起来,看看一次 https://example.com/path 请求中端口到底起了什么作用:
1. 浏览器输入 https://example.com:443/path
↓
2. DNS 解析 example.com → 93.184.216.34(第 15 篇)
↓
3. TCP 三次握手:本地随机端口(如 51023) ↔ 93.184.216.34:443(第 08 篇)
↓
4. TLS 握手(HTTPS 加密协商)
↓
5. 发送 HTTP 请求:GET /path HTTP/1.1(第 09 篇)
↓
6. 服务器操作系统收到数据包,看目标端口是 443
↓
7. 查端口映射表:443 被 Nginx 监听
↓
8. 数据交给 Nginx 进程
↓
9. Nginx 反向代理:把请求转发给 127.0.0.1:3000(Node.js)
↓
10. Node.js 应用处理,返回响应
↓
11. 响应沿原路返回:3000 → 443 → 客户端临时端口 51023
↓
12. 浏览器收到响应,渲染页面
端口在每一步都起着"路由"作用:
- 客户端用临时端口标识自己
- 服务器用 443 标识 HTTPS 服务
- 内部用 3000 标识 Node.js 应用
九、安全最佳实践
1. 最小化开放端口
只开放必要的端口,其余全部防火墙挡掉。端口开得越多,攻击面越大。
2. 不要在公网暴露数据库端口
❌ MySQL 3306、Redis 6379 直接暴露公网
✅ 只允许内网访问,或通过 SSH 隧道连接
Redis 早年默认无密码且暴露公网,导致无数服务器被加密勒索,这个教训至今仍然有效。
3. 改掉默认端口(提高门槛)
SSH 22 → 改为 2222 或其他高位端口
这属于"安全靠隐蔽"(Security by Obscurity),不是真正的安全措施,但能挡住大部分自动化扫描和蠕虫攻击。
4. 使用非 root 用户运行服务
1024 以下的端口需要 root 权限才能绑定
生产环境用 1024 以上端口(如 3000、8080)跑业务
通过 Nginx 监听 80/443 做反向代理
这样即使业务进程被攻破,攻击者也拿不到 root 权限。
十、总结
| 概念 | 说明 |
|---|---|
| 端口是什么 | 0~65535 的整数,用于区分一台机器上的不同服务 |
| 端口 + IP | IP 定位机器,端口定位程序 |
| 80/443 | HTTP/HTTPS 默认端口,浏览器自动补全 |
| 3000/5173/8080 | 开发服务器常用端口 |
| 3306/6379 | MySQL/Redis 默认端口 |
| 三段分类 | 知名端口 (0-1023) / 注册端口 (1024-49151) / 动态端口 (49152-65535) |
| TCP vs UDP | 同一端口号在 TCP 和 UDP 上是独立的,互不影响 |
| 监听 | 程序通过 bind + listen 在端口上等待连接 |
| 端口冲突 | EADDRINUSE,杀掉占用进程或换端口 |
写在最后
端口是那种"看似简单、实则贯穿全局"的概念。开发时你敲 npm run dev 看到的 localhost:3000,部署时配的 Nginx listen 80,排查问题时用的 lsof -i:3306,云服务器上开的安全组规则——本质上都是在和端口打交道。
理解了端口,你才能真正搞懂这些常见问题:
- 为什么我的服务启动了但外面访问不到?(没监听
0.0.0.0,或防火墙没开端口) - 为什么 Docker 里要写
"8080:3000"?(宿主机端口和容器端口的映射) - 为什么说数据库端口不能暴露公网?(3306/6379 一旦被扫到,就是攻击入口)
- 为什么一个 IP 能同时跑网站、数据库、邮件?(不同端口分给不同进程)
至此,网络通信系列已经覆盖了从 DNS 到端口、从 TCP 到 HTTP 的完整链路。下一篇我们讲代理,“中间人”位于客户端和服务器之间,转发请求和响应。
如果这个系列对你有帮助,欢迎点赞、关注、收藏三连,我们下篇见 👋