理解端口号

理解端口号

什么是端口号?

在网络通信中,我们都是类似于通过构建一条通信通道来实现信息的传输。要建立这样一条通道,就需要唯一确定信息的发送者和信息的接收者。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)。