理解端口号

理解端口号

什么是端口号?

在网络通信中,我们都是类似于通过构建一条通信通道来实现信息的传输。要建立这样一条通道,就需要唯一确定信息的发送者和信息的接收者。IP 地址可以用于在网络中唯一标识一台设备,比如101.22.xxx.xx。我们通过发送方和接收方的 IP 地址,就可以实现两台计算机之间的数据包的收发。然而,在同一台计算机上,可能运行着多个网络程序,比如浏览器、邮件客户端、数据库服务器等等,仅靠 IP 地址将数据包发送到了目标计算机后,还无法确定到底应该是哪个程序要接收数据。比如我们一边在 SSH 远程登陆服务器,一边下载邮件,仅靠 IP 地址系统也没有办法区分 SSH 和邮件数据。

为了解决这个问题,人类引入了端口号的概念,作为 IP 地址的扩展信息以唯一标识同一台计算机上的不同网络应用程序。比如在101.22.xxx.xx:8080中,8080就是端口号。这样,通过 IP 地址+端口号组合,就可以唯一将数据包发送给相应的应用程序。实际上在互联网通信协议中(TCP/IP),通过5个参数就可以完全地确定一条通信通道:

1
(源 IP,目标 IP,源端口,目标端口,协议号)
  • 源 IP 就是通信的发送方 IP,比如我们的电脑 192.168.1.100
  • 目标 IP 就是通信的接收方 IP,比如远程网站 93.184.216.34
  • 源端口就是发送方端口,如 52345
  • 目标端口就是远程服务器监听接收信息的端口,比如 443
  • 协议号指代所采用的通信协议,其中 TCP 的协议号为 6,UDP 的协议号为 17。

这里的 TCP 和 UDP 都是传输层的协议,他们类似于同一目标——在网络上发送数据的不同实现方法,由于侧重点不同所以采取了不同的方案。TCP 的目标是可靠传输数据,确保数据不丢失、不乱序,因此有三次握手等的严格确认机制,需要建立 TCP 连接才能发送数据。文件传输、网页加载等对可靠性要求高的场合一般会采用 TCP 协议。而 UDP 的目标是快速传输数据,没有严格控制机制,也无需提前建立链接,丢包乱序都无所谓。在实时性要求高的场合,比如视频直播和游戏会采用 UDP 协议。一般情况下,我们使用的主要都是 TCP 协议。

上面是一些典型的互联网协议,其中 TCP 和 UDP 属于传输层,负责如何可靠地传输数据,它们不关心传输的内容是什么。HTTP/SMTP/FTP 这些属于应用层,定义具体的数据如何被解析和使用(邮件、远程登陆、网页等),而它们不关注数据如何传输。上面的图里除了 TCP 和 UDP,剩下的协议都属于应用层,都是建立在 TCP 之上的。

协议 作用 默认端口
WebSocket 长连接、全双工通信 80(ws://),443(wss://)
HTTP 网页数据传输(明文) 80
HTTPS 加密网页数据传输(基于 TLS/SSL) 443
SMTP 发送邮件 25, 587
FTP 文件传输 21, 20

WebSocket、HTTP、HTTPS、SMTP、FTP 都属于应用层协议,它们依赖传输层协议(TCP)来进行数据传输,定义了如何解析和使用数据。应用层协议是"数据的格式和解析",传输层(TCP/UDP)是"数据如何可靠/快速传输"。

如何确定端口号?

在建立一条通信通道时,我们需要发送方的端口号和接收方的端口号。接收方一般都是服务器,服务器监听某个固定端口,执行相应的操作。接收方的端口号可以分成两类。第一类是保留端口(0-1023),这些端口通常都是由操作系统和标准协议使用的,每一个端口号都唯一确定一个服务。这些由 IANA(互联网号码分配局)统一管理,普通用户无法使用这些端口。访问指定端口时,必须遵循该端口对应协议的规则,就像调用 API 必须遵循 API 规范一样。比如对于 HTTPS 的 443 端口,客户端发送请求时连接就会自动使用 TLS 加密,发送的方式和 HTTP 的 80 端口一样,不需要我们手动处理;而对于邮件发送的 SMTP 协议的 587 端口,就需要客户端手动启动 starttls()来切换到 TLS 模式。总的来说,访问指定端口时,必须按照该端口的协议规则进行通信,否则连接会失败,就像 Flask API 需要按照定义的接口调用一样。

协议 端口号 说明 部分连接规则
HTTP 80 网页服务(非加密) 直接发送明文 HTTP 请求
HTTPS 443 加密网页服务(SSL/TLS) 必须使用 TLS 加密
FTP 21 文件传输协议 需使用 FTP 协议(可能有明文或加密模式)
SSH 22 远程登录 必须使用 SSH 协议(加密认证)
SMTP 25 邮件服务器之间传输 可能明文,可能 starttls()
SMTP 587 现代邮件客户端发送 starttls() 切换到加密
DNS 53 域名解析 UDP 默认查询,TCP 处理大数据或 DNSSEC

第二类是用户端口(1024-49151),这些端口可以自由使用,但有些端口是流行应用的默认端口:

端口号 常见应用
8000 Django 开发服务器
8080 常用于 HTTP 备用端口
8888 Jupyter Notebook
3306 MySQL 数据库
5432 PostgreSQL 数据库

我们自己启动一个服务的时候,比如启动一个 Flask 应用,我们可以让服务自由监听某个空闲端口:

1
2
3
4
5
6
7
8
9
10
from flask import Flask

app = Flask(__name__)

@app.route('/')
def home():
return "Hello, Flask!"

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000) # 监听 5000 端口

当客户端向 5000 端口发送请求时,就会连接到这个端口,然后服务器输出 Hello, Flask!

一般来说,只有在监听端口时,才需要手动指定端口号,而发送数据时,端口号通常是由操作系统动态分配的,不需要手动设置。动态端口的范围是(49152-65535),比如当浏览器访问 https://example.com 时,系统会自动选择一个未使用的端口:

1
(本地 IP, 52345) → (example.com, 443)

这种动态分配使得计算机同时运行多个客户端的时候,不会和已有的连接冲突,我们才能无痛地同时访问多个网页或者执行多项互联网操作。

1
2
3
(192.168.1.100, 52345)  →  (smtp.example.com, 25)  # 发送邮件
(192.168.1.100, 52346) → (google.com, 443) # 浏览 HTTPS 网页
(192.168.1.100, 52347) → (github.com, 22) # SSH 远程连接

邮件发送的小例子

image-20250303171810791

以邮件客户端的配置为例再巩固一下接口的相关知识。邮件的发送和接收是两个独立的过程,因此在邮件客户端中需要分别配置。在邮件的收发过程中存在两个服务器,一个是邮件发送服务器,一个是邮件接收服务器。A 向 B 发送一个邮件,实际上的流程是 A 先和邮件发送服务器建立链接,将邮件发送到发送服务器上。发送服务器再将邮件转发到接收服务器。之后,B 和邮件接收服务器建立连接,完成邮件的下载。发送邮件的协议一般是 SMTP,接收邮件的协议一般是 IMAP/POP3。整个电子邮件的发送和接收可以整理为:

  1. 邮件客户端(Outlook、Foxmail、Gmail Web端等)→ 发送邮件 → SMTP 服务器
  2. SMTP 服务器 → 通过 SMTP 转发邮件 → 收件人邮件服务器
  3. 收件人邮件客户端 → 连接 IMAP/POP3 服务器 → 下载邮件

SMTP(Simple Mail Transfer Protocol,简单邮件传输协议) 的工作流程如下:

  1. 邮件客户端(Outlook/Foxmail/Gmail)建立 TCP 连接到邮件服务器(如 smtp.example.com)。
  2. SMTP 服务器验证身份(用户名/密码 或 OAuth)。
  3. SMTP 服务器接收邮件,然后寻找收件人的邮件服务器(如 gmail.com)。
  4. SMTP 服务器通过 SMTP 转发邮件到目标服务器(如 smtp.gmail.com)。
  5. 收件人的邮件服务器存储邮件,等待收件人下载。
1
2
(本地 IP, 52345)  →  (smtp.example.com, 587)  # 发送邮件(加密)
(smtp.example.com, 52346) → (smtp.gmail.com, 25) # 转发邮件

SMTP 服务器支持多个端口,不同端口的用途和加密方式有所区别:

端口 用途 是否加密 加密方式
25 服务器之间的邮件转发(MTA-MTA) ❌ 可选 明文或 STARTTLS(部分支持)
465 客户端提交邮件(已废弃的 SMTPS) ✅ 必须 SSL/TLS(直接加密)
587 客户端提交邮件(现代推荐) ✅ 可选 STARTTLS(明文 + TLS)

SMTPS 不是一个新的协议,而是一种加密方式。它在 SSL/TLS 加密的 TCP 连接上直接运行 SMTP,即连接建立时就开启加密。类似 HTTPS(HTTP over SSL/TLS)这种方式要求所有 SMTP 服务器都支持 SMTPS,否则无法通信。而STARTTLS 不是一种独立的协议,而是 SMTP 标准中的一个扩展指令,用于在明文连接上动态启用 TLS 加密。SMTP 连接最初是明文的,但客户端可以发送 STARTTLS 指令,要求服务器切换到 TLS 加密模式。其兼容性更强,可以在支持和不支持加密的服务器之间灵活切换。SMTPS(465)类似于打电话前就先打开了 "加密模式"(TLS 连接先建立)。STARTTLS(587)则是先拨号,然后告诉对方 "我们加密对话吧"(明文连接后再切换 TLS)。对于用户来说,发送邮件到中转服务器的 465 或 587 端口没有区别,邮件客户端会自动处理连接方式。但是对于开发者来说,对不同的端口需要采用不同的连接方式。如果连接或者加密方式不正确,服务器就会拒绝请求。

SMTP 只负责发送邮件,但收件人通过 IMAP 和 POP3 协议完成邮件的接收。IMAP(Internet Message Access Protocol)允许多设备同步邮件(邮件存储在服务器上,不删除),适用于多设备办公(如手机、电脑同时收邮件),使用端口 143(明文)或 993(SSL/TLS 加密)。OP3(Post Office Protocol 3)的邮件下载到本地后,服务器上的邮件通常被删除,适用于单台设备管理邮件(比如只在一台电脑上收邮件),使用端口 110(明文)或 995(SSL/TLS 加密)。

1
2
(本地 IP, 52347)  →  (imap.example.com, 993)  # 加密邮件接收
(本地 IP, 52348) → (pop3.example.com, 995) # 加密邮件接收

端口安全

如果端口直接暴露在公网上,没有任何保护机制,那么就存在安全风险,这个暴露的端口就像围起来的城墙中出现的一个小洞一样。比如最近国家网络安全通报中心提到的,Ollama在本地部署大模型时,会在本地启动一个 Web 服务,默认开放 11434 端口且无任何鉴权机制。攻击者可以直接通过这个端口调用 AI 模型服务,窃取计算资源。而且可以获取已部署模型的相关信息,如 License、训练数据等。如果通过该端口删除或者上传恶意文件等,可以直接导致 AI 模型不可用。有很多种方式进行端口的安全管理,一个简单的方式就是使用 API Key,让只有拥有 API Key 的用户才有权限访问 API,大模型也官方采用了这种方式来进行身份认证。API Key 的基本使用方式就是在请求头部中加入一个 Authorization: Bearer <API_KEY> 字段,服务器验证后才会执行请求返回数据,API Key 还可以设置权限,调用频率等等。

1
2
3
4
5
6
7
8
9
# 使用 API Key 调用大模型

import requests

API_KEY = "sk-xxxxxxxxxxxxxxxxxxxxxxxx"
headers = {"Authorization": f"Bearer {API_KEY}"}

response = requests.get("https://api.openai.com/v1/models", headers=headers)
print(response.json())

在 Flask 应用里,我们同样可以进行 API Key 以及调用频率和权限控制的设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from flask import Flask, request, jsonify
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

app = Flask(__name__)

# ✅ 配置 API Key 列表(包含权限)
API_KEYS = {
"admin-key": {"role": "admin", "rate_limit": "10 per minute"},
"user-key": {"role": "user", "rate_limit": "5 per minute"},
"read-only-key": {"role": "read-only", "rate_limit": "3 per minute"}
}

# ✅ 限制 API 调用频率
limiter = Limiter(
key_func=lambda: request.headers.get("X-API-KEY"), # API Key 作为 Rate Limit 依据
app=app
)

# ✅ API Key 认证(全局拦截)
@app.before_request
def authenticate():
api_key = request.headers.get("X-API-KEY")
if api_key not in API_KEYS:
return jsonify({"error": "Unauthorized"}), 403

# ✅ 限制不同权限的 API Key 调用频率
def rate_limit():
api_key = request.headers.get("X-API-KEY")
return API_KEYS.get(api_key, {}).get("rate_limit", "2 per minute") # 默认限制 2 次/分钟

@app.route("/protected", methods=["GET"])
@limiter.limit(rate_limit) # 动态限制 API 频率
def protected_route():
api_key = request.headers.get("X-API-KEY")
return jsonify({"message": "Welcome!", "role": API_KEYS[api_key]["role"]})

# ✅ 仅限 Admin 访问的 API
@app.route("/admin", methods=["GET"])
@limiter.limit(rate_limit)
def admin_route():
api_key = request.headers.get("X-API-KEY")
if API_KEYS[api_key]["role"] != "admin":
return jsonify({"error": "Forbidden"}), 403
return jsonify({"message": "Welcome Admin!"})

if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)

比如在这里,使用 user-key 可以正常访问 /protected

1
curl -H "X-API-KEY: user-key" http://127.0.0.1:5000/protected

返回

1
{"message": "Welcome!", "role": "user"}

而访问 /admin (仅 Admin 可用),

1
curl -H "X-API-KEY: user-key" http://127.0.0.1:5000/admin

就会返回无权限:

1
{"error": "Forbidden"}

对于超出访问频率时,HTTP 返回码就会得到 429(Too Many Requests)。