前端看后端 16:什么是端口?

1 阅读11分钟

专栏第十六篇,网络与协议篇第九章。上一篇我们聊了 DNS,搞懂了浏览器是怎么把 google.com 翻译成 142.250.72.206 这个 IP 的。但问题来了——数据包千辛万苦传到服务器,服务器上同时跑着 Nginx、Node.js、MySQL、Redis、SSH……这包到底该交给谁?总不能让所有程序抢着处理吧?今天的主角 Port(端口),就是操作系统用来区分"这台机器上不同程序"的编号。它是网络通信里最容易被忽略、却又天天打交道的概念——30005173808033066379 这些数字背后,藏着一整套规则。


一、端口是什么?——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 权限才能绑定

端口服务说明
80HTTP网页(明文)
443HTTPS网页(加密)
22SSH远程登录
21FTP文件传输(控制通道)
25SMTP发邮件
53DNS域名解析

⚠️ 注意:MySQL(3306)、Redis(6379)、PostgreSQL(5432)这些虽然"知名",但端口号 大于 1023,严格来说属于"注册端口",不是知名端口。

2️⃣ 注册端口(Registered Ports):1024 ~ 49151

分配给特定的应用程序或服务,由 IANA 登记管理。开发者自己写的服务,通常也在这段里选端口。

端口服务
3306MySQL
5432PostgreSQL
6379Redis
27017MongoDB
5672RabbitMQ
8080HTTP 替代端口(Tomcat 默认)
8443HTTPS 替代端口
9090Prometheus
9200Elasticsearch

3️⃣ 动态/私有端口(Dynamic/Private Ports):49152 ~ 65535

客户端临时使用的端口,也叫"临时端口"(ephemeral port)。

  • 当你访问 google.com:443 时,操作系统会从 49152~65535 中随机挑一个端口作为源端口
  • 服务器响应时,数据就发回这个端口
  • 连接关闭后,端口释放,可被下次复用

📌 你在 netstat 里看到的那些 localhost:51023localhost: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 相关

端口用途
80HTTP(浏览器默认)
443HTTPS(浏览器默认)
3000Node.js 开发服务器(Next.js、React、Create-React-App 默认)
5173Vite 开发服务器默认端口
8080HTTP 备用(Java/Tomcat、Nginx 代理常用)
8000Python/Django、FastAPI 开发
5000Flask 默认
9000SonarQube、php-fpm

🗄️ 数据库与缓存

端口数据库
3306MySQL
5432PostgreSQL
6379Redis
27017MongoDB
1521Oracle
1433SQL Server
7001Cassandra
9042Cassandra(CQL 原生协议)

🔧 运维与基础设施

端口服务
22SSH
3389RDP(Windows 远程桌面)
9090Prometheus
30000 ~ 32767Kubernetes 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:80
  • https://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 的整数,用于区分一台机器上的不同服务
端口 + IPIP 定位机器,端口定位程序
80/443HTTP/HTTPS 默认端口,浏览器自动补全
3000/5173/8080开发服务器常用端口
3306/6379MySQL/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 的完整链路。下一篇我们讲代理,“中间人”位于客户端和服务器之间,转发请求和响应。

如果这个系列对你有帮助,欢迎点赞、关注、收藏三连,我们下篇见 👋