如果你打开过代理服务的用户面板,一定见过这样的画面:同一个地址、同一个用户名,旁边却有两个端口。一个标着 HTTP,另一个标着 SOCKS5。很多人要么随便选一个,要么凭习惯选。但实际上,这两行数字背后是两套完全不同的协议——它们在字节层面的构造不同,对你流量的可见程度不同,在各类客户端中的表现也大相径庭。本文将从字节级别拆解二者的区别,最后给出实用的选型对照表。

引言:为什么同一个代理会提供两种模式,这不是营销噱头

先正面回答标题里的问题。当 Proxeon 给你一个地址配两个端口时,它们背后是同一台机器、同一个出口 IP、同一套账号。区别不在于流量最终去哪里,而在于你的客户端与代理服务器之间——也就是从你的应用到中间人这一段——用的是什么「语言」。

HTTP 代理说的就是 HTTP 这门语言。它接收 HTTP 请求,读取内容,理解你想获取什么资源,然后自己去取。它会缓存、会添加请求头、会用状态码回复。对于所有非 HTTP 的东西,它有一个万能招数:CONNECT 方法,把自己变成一根「傻管子」。

SOCKS5根本不知道 HTTP 是什么。它是一个会话层协议:客户端说「帮我连到某个主机和端口」,代理建立 TCP 连接,从此就只是把字节来回搬运。里面装的是 TLS、IMAP、SSH、数据库协议还是你自己写的二进制格式,它一概不关心。

为什么不能只保留一种模式?因为它们的兼容性各不相同。一半的企业级和桌面软件只会通过系统设置使用 HTTP 代理。相当一部分网络库以及几乎所有非 Web 流量,在 SOCKS5 里更自在。在同一个 IP 上同时提供两种模式,意味着你可以根据客户端选工具,而不是让客户端去迁就工具。

你将从这篇文章中学到:

  • HTTP 代理请求在文本层面长什么样,它和直接请求网站有何不同;
  • 在明文 HTTP 模式下,代理究竟能看到什么、能改动什么;
  • CONNECT 方法如何工作,隧道建立后中间人还能看到什么;
  • SOCKS5 的字节级握手、认证方式以及三种 ATYP 地址类型;
  • 为什么在 SOCKS5 请求中选择域名还是 IP 会影响地理定位和 DNS 隐私;
  • 在非 HTTP 协议、UDP、开销、缓存和日志方面的对比;
  • 「任务-协议-原因」选型表,以及主流客户端和库的兼容性分析。

关于 UDP ASSOCIATE 命令的机制以及 curl、Python 中 socks5 与 socks5h 的差异,本文不做详细展开——Proxeon 博客中有专门的文章讲解,文内会在相应位置给出链接。

基础:代理在网络栈中的位置

要让协议讨论有意义,我们先约定几个术语。数量不多。

三个参与方和两条连接

任何代理方案里都有三方:客户端(你的浏览器、脚本、邮件程序)、代理服务器(中间人)和目标服务器(origin)。它们之间有两条独立的 TCP 连接。第一条:客户端到代理。第二条:代理到目标服务器。目标服务器只能看到第二条连接,因此也只能看到代理的 IP 地址。

本文讨论的一切,都只涉及第一条连接。HTTP 与 SOCKS5 的区别正存在于这里。第二条连接在两种情况下结构相同:从代理到目标主机的普通 TCP。

模型层次,以及为什么这件事重要

一个有用的类比:想象一家快递公司。明文模式下的 HTTP 代理像一个快递员,他会打开你的包裹,读地址和内容,必要时重新打包,如果仓库里有副本,甚至能自己回复你。SOCKS5 则像一个你只告诉他地址、包裹封好交给他的快递员。他不知道、也不想关心里面装了什么。

用网络模型的话说,HTTP 代理工作在应用层:它理解请求的语义。SOCKS5 工作在会话层:它只操作「主机」「端口」「连接」这些概念,不会往上层走。

域名解析发生在哪里

一个贯穿全文的核心概念:DNS 解析。当你访问 example.com 时,总得有人把域名变成 IP 地址。这可以由你的客户端在本地完成,也可以由代理在自己那一侧完成。这决定了你用的是谁的 DNS 基础设施、从 CDN 拿到的是哪个区域的响应、以及你访问的域名信息是否会泄漏到本地网络中。记住这一点,它在 HTTP 和 SOCKS5 两部分都会再次出现。

认证

HTTP 代理和 SOCKS5 都能验证用户名和密码,但方式不同。HTTP 使用 Proxy-Authorization 请求头和 407 状态码。SOCKS5 使用一个独立的子协议,方法编号为 0x02。还有第三种与协议无关的方案:根据客户端 IP 授权,代理放行所有来自预先许可地址的请求。Proxeon 两种都支持,选择哪种会影响你连接那些无法传递凭据的客户端的难易程度。

HTTP 代理:绝对 URI 请求,代理能看到什么、能改动什么

普通的 HTTP 请求长这样:

GET /catalog/items?page=2 HTTP/1.1
Host: shop.example

注意:请求行里只有路径。主机通过单独的请求头传递。这种格式叫 origin-form。当客户端知道自己是在跟代理而不是网站说话时,会把格式改成 absolute-form:请求行里放完整的 URI。

GET http://shop.example/catalog/items?page=2 HTTP/1.1
Host: shop.example
Proxy-Authorization: Basic dXNlcjpwYXNz
User-Agent: my-parser/1.0
Accept: text/html

为什么要重复主机名?历史上绝对形式比 Host 头出现得更早,是告诉代理去哪里的唯一方式。现代 HTTP/1.1 标准要求代理能够接受绝对形式,而要求访问代理的客户端使用绝对形式。Host 头仍然保留,既是为了兼容,也是因为代理转发给目标服务器时会用 origin-form,那里 Host 是必需的。

HTTP 代理能看到什么

在明文模式(客户端与目标网站之间没有加密)下,代理能看到请求和响应中的一切:

  • 方法、完整 URL,包括查询参数;
  • 所有请求头:User-Agent、Cookie、Authorization、Referer;
  • 完整的请求体:表单、JSON、上传的文件;
  • 响应状态码、响应头和响应体。

这不是副作用,而是架构的根本特征:要转发请求,代理就必须解析它。而一旦解析了,它就能对它做任何事。

HTTP 代理能改动什么

标准把请求头分为端到端(end-to-end)和逐跳(hop-by-hop)。逐跳头只与某一条具体连接相关,代理转发前必须删除。包括 Connection、Keep-Alive、Proxy-Authorization、Proxy-Authenticate、TE、Trailer、Transfer-Encoding、Upgrade。这也是为什么你的代理用户名密码不会飞到目标网站:按标准,Proxy-Authorization 头会在代理处被移除。

除了必须的清理,代理还可以:

  • 添加 Via 头,标明自己的名称和协议版本;标准有此要求,但实践中匿名代理会省略它;
  • 添加 X-Forwarded-For,带上客户端的真实 IP;企业代理会这么做,而你购买用于访问外部资源的代理绝不应该这么做;
  • 如果响应被标记为可缓存,直接从缓存返回响应,不访问目标服务器;
  • 改变正文编码、添加或移除压缩、重写 HTML 中的链接;
  • 用自己的响应拒绝请求,比如 403 或 407。

407 Proxy Authentication Required 值得单独一提。如果代理要求认证而客户端没有提供,代理会以 407 状态码和 Proxy-Authenticate 头响应,列出支持的认证方案。好的客户端会带上 Proxy-Authorization 重试请求。差的客户端则会向用户显示一个看不懂的错误。这就是配置时的典型问题:应用「看不到代理」,实际上只是它不会处理 407。

动手看一遍

最简单的方式是用 curl 加 -v 参数亲眼看看。

curl -v -x http://user:pass@gate.proxeon.net:8080 http://httpbin.org/get

输出中你会看到类似 GET http://httpbin.org/get HTTP/1.1 的一行,以及 Proxy-Authorization 头。这就是绝对形式。

同样的请求,用 Python 通过 socket 手工发出,不留任何神秘感:

import socket, base64

creds = base64.b64encode(b"user:pass").decode()
req = (
"GET http://httpbin.org/get HTTP/1.1\r\n"
"Host: httpbin.org\r\n"
f"Proxy-Authorization: Basic {creds}\r\n"
"Connection: close\r\n\r\n"
)
s = socket.create_connection(("gate.proxeon.net", 8080))
s.sendall(req.encode())
print(s.recv(65535).decode(errors="replace"))

这里没有任何代理库,只有 socket 和一行拼好的字符串。明文模式的 HTTP 代理简单到一晚上就能写出来,这既是它的优势,也是它的弱点。

HTTP 模式下域名在哪里解析

在绝对形式中,客户端把主机名原样传给代理,解析由代理完成。客户端不需要知道目标服务器的 IP,也不会发出本地 DNS 查询。这很方便,但引出了主要局限:上述方案只适用于未加密的 HTTP。对于 HTTPS,绝对形式没用,因为 TLS 连接必须建立在客户端与目标服务器之间,代理不能插在中间而不破坏证书。CONNECT 就是为此而生的。

CONNECT 方法:把 HTTP 代理变成 TCP 隧道

CONNECT 是一个 HTTP 方法,它请求代理不要转发请求,而是与指定主机和端口建立 TCP 连接,之后成为一根透明管道。请求长这样:

CONNECT shop.example:443 HTTP/1.1
Host: shop.example:443
Proxy-Authorization: Basic dXNlcjpwYXNz
Proxy-Connection: Keep-Alive

注意目标的格式:只有主机和端口,没有协议和路径。这是请求的 authority-form,继 origin-form 和 absolute-form 之后的第三种格式。

如果代理同意,它会回复:

HTTP/1.1 200 Connection established

代码后面的文字可以是任何内容,客户端只看 2xx。从这一刻起,HTTP 就结束了。客户端写入套接字的一切,代理都逐字节转发给目标服务器,反之亦然。客户端在这个套接字里直接开始 TLS 握手,就像直连服务器一样。

CONNECT 之后中间人还能看到什么

有趣的部分开始了。刚才还全知全能的代理,突然就瞎了。成功 CONNECT 之后,代理知道:

  • CONNECT 行里的主机名和端口;这是他获得的唯一语义信息;
  • 连接建立和关闭的时间;
  • 两个方向上传输的字节数;
  • 如果隧道内是 TLS,还有 TLS ClientHello 的内容:其中以明文传输 SNI(服务器名称)、支持的加密套件列表、扩展字段;现代的 Encrypted Client Hello 扩展会连 SNI 也隐藏,但支持度目前还不普遍;
  • HTTP 层面的一切都看不到:URL、请求头、Cookie、正文,统统没有。

换句话说,CONNECT 之后 HTTP 代理看到的东西跟 SOCKS5 一样多。主机、端口、时间、数据量。两种协议在可见性上的差异,只存在于未加密的 HTTP 流量中——而到了 2026 年,实际任务里这种流量已经少之又少。

CONNECT 不一定要连 HTTPS

一个常见的思维误区:CONNECT 只用于 HTTPS。实际上标准并不限制隧道内容。通过 CONNECT 可以连接到 993 端口的 IMAP 服务器、22 端口的 SSH、任何 TCP 服务。问题只在于代理配置是否允许。许多公共和企业 HTTP 代理只允许 CONNECT 到 443 端口、有时加上 80,以防滥用。Proxeon 允许 CONNECT 到任意端口,但如果你的软件支持 SOCKS5,对于非 HTTP 协议它仍然是更自然的选择,理由见下文。

示例:手工通过 CONNECT 实现 TLS

openssl 工具可以自行穿过 HTTP 代理:

openssl s_client -proxy gate.proxeon.net:8080 -connect shop.example:443 -servername shop.example

下面是 Python 标准库的写法,无需外部依赖:

import http.client, ssl

conn = http.client.HTTPSConnection("gate.proxeon.net", 8080,
 context=ssl.create_default_context())
conn.set_tunnel("shop.example", 443,
headers={"Proxy-Authorization": "Basic dXNlcjpwYXNz"})
conn.request("GET", "/")
resp = conn.getresponse()
print(resp.status, resp.getheader("server"))

set_tunnel 方法做的正是上面描述的事:发送 CONNECT,等待 200,然后把套接字包装成 TLS。注意 TLS 上下文验证的是 shop.example 的证书,而不是代理的证书。在这种方案里,代理无法替换证书而不引发客户端报错。

HTTP/2 和 HTTP/3 中的 CONNECT

在 HTTP/2 中 CONNECT 方法得以保留,但工作在一个多路复用的连接内:每个隧道是一个独立的流。这样可以经一条到代理的 TCP 连接维持数十个隧道,省去重复握手。扩展 CONNECT(带 :protocol 伪头)用于在 HTTP/2 之上跑 WebSocket。基于 QUIC 的 HTTP/3 中出现了 CONNECT-UDP 规范,形式上允许通过 HTTP 代理隧道传输 UDP。不过客户端库支持这还比较罕见,实践中如果你需要 UDP 走代理,还是得看 SOCKS5。

CONNECT 做不到的事

  • 缓存:隧道内容是不透明的。
  • 修改请求头:根本看不到。
  • 在 HTTP/1.1 中通过一条隧道复用多个目标主机:一个 CONNECT 对应一条 TCP 连接。
  • 在 HTTP/1.1 和 HTTP/2 中传输 UDP。

SOCKS5:握手、认证与 ATYP

SOCKS5 由 RFC 1928 定义,其用户名密码认证方案由 RFC 1929 定义。两份规范都只有几页,这是该协议的优势之一:从零实现并不难,因此实现多、歧义少。

SOCKS5 是二进制协议。没有文本,只有固定结构的字节。我们来看 CONNECT 命令的完整交互。

第一步:问候与认证方法选择

客户端发送版本号和它支持的认证方法列表:

05 02 00 02
VER=5 NMETHODS=2 METHODS: 00 (无需认证), 02 (用户名/密码)

服务器选择一种方法,用两个字节回复:

05 02
VER=5 METHOD=02 (服务器要求用户名/密码)

如果服务器回复 05 FF,表示「所提供的方法都不适用」,客户端必须关闭连接。这正是你忘了填密码、而 Proxeon 代理配置了用户名认证时,字节层面的表现。

第二步:按 RFC 1929 认证

如果选择了方法 02,客户端发送:

01 04 75 73 65 72 04 70 61 73 73
VER=1 ULEN=4 UNAME="user" PLEN=4 PASSWD="pass"

注意:这里子协议版本是 01,不是 05。这是自写客户端常见的错误来源。服务器回复:

01 00
VER=1 STATUS=00 (成功;其他值表示拒绝)

用户名和密码以明文传输。如果你和代理之间的网络不可信,这点要留意。实践中到代理的连接通常走自家运营商的通道,问题可以在传输层解决,或者改用 IP 授权。

第三步:连接请求

现在是最重要的消息:

05 01 00 03 0C 73 68 6F 70 2E 65 78 61 6D 70 6C 65 01 BB
VER=5 CMD=01 (CONNECT) RSV=00 ATYP=03 (域名)
LEN=12 ADDR="shop.example" PORT=0x01BB (443)

CMD 字段有三个取值:01 CONNECT(向外建立 TCP 连接)、02 BIND(等待入站连接,几乎不用)、03 UDP ASSOCIATE(创建 UDP 中继)。UDP ASSOCIATE 的机制和坑我们在另一篇文章里专门讲过,这里只记录一点:这是两种协议中唯一原生支持 UDP 的。

ATYP:域名还是 IP

ATYP 字段决定客户端以何种形式传递目标地址:

  • 0x01 IPv4:后面 4 个字节是地址;
  • 0x03 域名:一个长度字节,然后是域名,不带结尾的零;
  • 0x04 IPv6:后面 16 个字节是地址。

看起来是个技术细节。实际上,这是客户端做出的最重要的决策之一,原因如下。

如果客户端使用 ATYP=0x01 或 0x04,说明它在发送请求前自己完成了 DNS 解析。你的机器查询了自己的 DNS 服务器,拿到地址后把现成的 IP 传给了代理。后果是:

  • DNS 查询经过你的本地网络,对你的运营商或管理员可见;
  • 你拿到的是 DNS 为你所在地区返回的 IP;对于使用 CDN 的网站,意味着位于另一国家的代理会去连一个「别人的」边缘节点,拖慢连接,还可能显得异常;
  • 如果你的机器没有 IPv6,就永远拿不到 AAAA 记录,即使代理有 IPv6 路径也用不上;
  • 如果你网络里的 DNS 行为不标准,代理会拿到错误的地址,你得花很长时间找原因。

如果客户端使用 ATYP=0x03,解析由代理完成。它使用自己的 DNS 基础设施,拿到与其所在位置相关的地址,你的本地网络也看不到你访问了哪些域名。对于需要地理定位的任务,这至关重要:如果一个代理位于某国的意义,因为你家庭网络的 DNS 选定了目标服务器而部分丧失,那就白买了。

怎么让客户端传递域名?取决于客户端。在 curl 和 Python 库中,这由 socks5 与 socks5h 的选择决定,那是另一篇文章的主题。这里重要的是理解原因:字母 h 代表「hostname」,意味着启用 ATYP=0x03。没有它,库会在本地解析域名。

第四步:服务器响应

05 00 00 01 C0 A8 01 0A 1F 90
VER=5 REP=00 (成功) RSV=00 ATYP=01 BND.ADDR=192.168.1.10 BND.PORT=8080

REP 字段包含结果码。调试时记住它们很有用:

  • 00 成功;
  • 01 服务器一般性错误;
  • 02 规则禁止连接;
  • 03 网络不可达;
  • 04 主机不可达;
  • 05 目标服务器拒绝连接;
  • 06 TTL 过期;
  • 07 命令不支持;
  • 08 地址类型不支持。

代码 08 常见于老旧或简化实现,它们不支持 ATYP=0x03 或 IPv6。Proxeon 支持全部三种地址类型。

为什么 SOCKS5 不解析内容

REP=00 之后,SOCKS5 协议就结束了。客户端此后写入的一切,代理都直接搬进目标连接,不做分析。这不是实现的局限,而是设计属性。SOCKS5 没有「请求」的概念,也没有「头」的概念。它不知道什么是 HTTP、IMAP 或 TLS。你给它地址和端口,它把两根管子接起来。

由此产生几个实际后果:

  • SOCKS5 无法缓存:它不知道一个响应在哪里结束、下一个从哪里开始;
  • SOCKS5 无法添加头或替换内容;最多只能关闭连接;
  • SOCKS5 无法按 URL 过滤,只能按主机和端口;
  • SOCKS5 对任何 TCP 协议都同样有效,包括你昨天刚发明的那个。

用 Python 完整实现 SOCKS5 握手

import socket, struct

def socks5_connect(proxy, user, pwd, host, port):
s = socket.create_connection(proxy)
s.sendall(b"\x05\x02\x00\x02")
ver, method = s.recv(2)
if method == 0x02:
u, p = user.encode(), pwd.encode()
s.sendall(b"\x01" + bytes([len(u)]) + u + bytes([len(p)]) + p)
if s.recv(2)[1] != 0:
raise RuntimeError("auth failed")
elif method != 0x00:
raise RuntimeError("no acceptable auth method")
h = host.encode()
s.sendall(b"\x05\x01\x00\x03" + bytes([len(h)]) + h + struct.pack("!H", port))
reply = s.recv(4)
if reply[1] != 0:
raise RuntimeError(f"connect failed, REP={reply[1]}")
atyp = reply[3]
s.recv({1: 4, 4: 16}.get(atyp, 0) if atyp != 3 else s.recv(1)[0])
s.recv(2)
return s

sock = socks5_connect(("gate.proxeon.net", 1080), "user", "pass", "shop.example", 443)
# 接下来可以把 sock 包装成 ssl.wrap_socket 或交给任意协议使用

三十行代码,你就有了一个能用的 SOCKS5 客户端,它传递域名(ATYP=0x03),把解析留给代理服务器。正是这种简单性让 SOCKS5 成为可编程访问的事实标准。

按标准对比:各自在哪里胜出

现在两种协议的机制已经拆解到字节级别,对比不再是口味之争,而是一份可衡量的差异清单。

非 HTTP 协议

SOCKS5 原生支持任何 TCP 协议:CONNECT 命令、主机、端口,完事。HTTP 代理也能通过 CONNECT 实现,但有附带条件:客户端必须能对非 HTTP 流量发送 CONNECT(邮件客户端可以,大多数命令行工具不行),而且代理必须允许目标端口。胜者:SOCKS5。

UDP

SOCKS5 有 UDP ASSOCIATE。HTTP 代理在 1.1 和 2 版本里什么都没有,而 HTTP/3 的 CONNECT-UDP 几乎不被客户端支持。如果任务涉及直连 DNS 查询、QUIC、语音协议、游戏流量,没有别的选择。胜者:SOCKS5,但要注意并非所有 SOCKS5 服务器都开启 UDP,请查阅套餐文档。

建立连接的开销

以客户端到代理之间的往返次数(RTT)计,不计 TCP 握手本身:

  • HTTP 代理,明文模式,无认证:额外 0 个 RTT,请求直接发出;
  • HTTP CONNECT,认证在第一个请求中:1 个 RTT(CONNECT,回复 200);
  • SOCKS5,无认证:2 个 RTT(问候,请求);
  • SOCKS5,用户名密码:3 个 RTT(问候,认证,请求)。

字节数上则相反:带认证的完整 SOCKS5 握手大约 40 字节,而带 Host、Proxy-Authorization、User-Agent 头的 CONNECT 轻松超过 150 字节。但实际任务中决定因素是 RTT,不是字节。当到代理的延迟为 50 毫秒时,带认证的 SOCKS5 每条新连接比 CONNECT 多花 150 毫秒。如果客户端复用连接(keep-alive、连接池),这是一次性成本。如果每个请求都开新套接字,差异就会累积。按 RTT 胜出:HTTP。缩小 SOCKS5 差距的办法:IP 授权,省掉一个 RTT。

缓存

只有明文模式的 HTTP 代理能缓存。CONNECT 不行,SOCKS5 也不行。而且只能缓存未加密流量,如今这种流量已很少,所以这项标准从决定性因素变成了小众需求。对于可信网络内部的内网构建系统、软件包镜像、重复调用不带 TLS 的 API 依然有用。胜者:HTTP,但适用范围很窄。

日志记录

这一点常被误解。每种模式下代理能记录什么?

  • HTTP 明文:完整 URL、方法、请求头、必要时正文;响应状态码;数据量。
  • HTTP CONNECT:CONNECT 行里的主机和端口;时间;数据量;必要时从 ClientHello 中取 SNI。
  • SOCKS5 且 ATYP=0x03:域名和端口;时间;数据量;必要时取 SNI。
  • SOCKS5 且 ATYP=0x01/0x04:只有 IP 和端口;代理不知道域名;时间;数据量;SNI。

对于加密流量,CONNECT 和 SOCKS5 给中间人的可见度相同。差异只出现在 SOCKS5 客户端传 IP 而不是域名的情况下,这时代理知道得更少。但与此同时,目标服务器收到的请求来自「错误」的区域,参见 ATYP 章节。平局,各有细微差异。

错误诊断

HTTP 代理回复可读的状态码:407 表示认证问题,403 表示禁止,502 表示目标不可达,504 表示超时。有些实现还会在正文中给出解释。SOCKS5 只回复一个 REP 字节,而且不是每个库都会把它翻译成易懂的消息。在陌生环境中调试时,HTTP 代理更好诊断。胜者:HTTP。

多路复用

HTTP/2 代理允许多个 CONNECT 隧道跑在一条连接上。SOCKS5 每个目标都要求单独的 TCP 连接。对于有成百上千并发连接的客户端,这会显著降低网络栈和代理的负担。但客户端对到代理的 HTTP/2 支持目前还很零散。潜在胜者:HTTP,实际平手。

对流量干预的能力

明文模式的 HTTP 代理能改一切。CONNECT 模式和 SOCKS5 只能断开连接。对于想要流量不被篡改的客户端,SOCKS5 或内部带 TLS 的 CONNECT 是最好的。可预测性胜者:SOCKS5。

认证

两者都支持用户名密码。HTTP 还额外支持 Digest、NTLM、Negotiate 方案,这对企业环境很重要。SOCKS5 形式上支持 GSSAPI 方法,但在商业代理中几乎见不到。对于 Proxeon 这类服务这不重要:用 Basic 或 IP 白名单。

按任务选型:对照表

把结论整理成实用工具。下表假设两种模式在同一 IP 上都可用(Proxeon 正是如此),问题只在于该填哪个端口。

任务推荐协议原因
手动操作或测试用的浏览器HTTP(HTTPS 走 CONNECT)所有浏览器都支持通过系统设置或扩展使用带认证的 HTTP 代理;Chromium 系浏览器的系统代理无法使用带用户名密码的 SOCKS5,只能靠 IP 授权
Python、Node.js、Go 的 HTTP 客户端、爬虫、数据采集SOCKS5,传递域名(ATYP=0x03)在代理侧解析能拿到正确的 CDN 区域地址,库对两种模式都支持,而 SOCKS5 不会去动请求头
大量短连接、不用 keep-alive 的爬虫HTTP CONNECT 或 IP 授权的 SOCKS5每次握手的 RTT 会乘以连接数;CONNECT 相比带密码的 SOCKS5 省 1-2 个 RTT
邮件客户端(IMAP、SMTP、POP3)SOCKS5邮件协议不是 HTTP;桌面客户端原生支持 SOCKS5,走 HTTP 代理则需要 CONNECT 到非标准端口
SSH、数据库客户端、RDP、任何 TCP 协议SOCKS5OpenSSH 通过 ProxyCommand 或兼容 ProxyJump 的包装器原生支持,多数数据库客户端也原生支持;HTTP CONNECT 不是到处都能用
使用 UDP 的应用:DNS 客户端、QUIC、语音带 UDP ASSOCIATE 的 SOCKS5两种协议中唯一在规范中支持 UDP 的
内网构建和软件包镜像的缓存代理HTTP 明文只有它能理解响应的语义并进行缓存
只能用 Windows 系统代理的企业应用HTTPWinHTTP 和 Windows 系统设置支持 HTTP 代理;那里的 SOCKS 支持受限,且不支持认证
测试环境中的 Android 或 iOS 移动应用HTTP两大平台的 Wi-Fi 系统设置只支持 HTTP 代理;SOCKS5 需要应用层代理工具
直接操作套接字的自研软件SOCKS5三十行实现,二进制格式无需解析,支持任何上层协议,能明确控制解析位置
命令行工具:curl、wget、gitwget 用 HTTP;curl 和 git 用 SOCKS5 或 HTTP 均可wget 完全不支持 SOCKS;curl 和 git 两种模式都能用
从不同区域监控网站可用性SOCKS5,ATYP=0x03需要让 DNS 解析从代理所在区域发出,否则检查的不是那个边缘节点
出问题时排查原因HTTP407、403、502、504 比 REP 字节好懂,curl -v 能显示与代理的完整对话

四步选型算法

  1. 确定里面是什么协议。如果不是 HTTP 或 HTTPS,直接选 SOCKS5,不用犹豫。
  2. 看客户端支持什么。用 socks5、proxy、CONNECT 这些关键词查它的文档。如果没有 SOCKS5,或者 SOCKS5 不支持认证,选择就已经替你做好了。
  3. 决定域名该在哪里解析。如果区域重要(CDN、地域相关内容、监控),选传递域名的 SOCKS5 或 HTTP CONNECT:两种情况都由代理解析。
  4. 评估连接特征。大量无池化的短连接:看握手的 RTT,选 HTTP CONNECT 或启用 IP 授权。

主流客户端和库的兼容性

理论止于真实客户端。下面按 2026 年的情况分析最常用工具的行为。请检查实际版本:行为会随版本变化。

curl

支持一切。-x 参数或环境变量里的方案:http://、https://(到代理本身的 TLS)、socks4://、socks4a://、socks5://、socks5h://。socks5 与 socks5h 的区别在另一篇文章讲过,这里只需记住:要让代理做解析,用 socks5h。

curl -x socks5h://user:pass@gate.proxeon.net:1080 https://shop.example/
curl -x http://user:pass@gate.proxeon.net:8080 https://shop.example/

环境变量 http_proxy、https_proxy、all_proxy 会被自动读取;all_proxy 也接受 SOCKS 方案。

wget

只支持 HTTP 代理。完全不支持 SOCKS,任何形式都没有。如果要用 wget 走 SOCKS5,需要系统级代理工具。

Python:requests

开箱支持 HTTP 代理。SOCKS5 需要额外的 PySocks 包(安装为 requests[socks])。

import requests

proxies = {
"http": "socks5h://user:pass@gate.proxeon.net:1080",
"https": "socks5h://user:pass@gate.proxeon.net:1080",
}
r = requests.get("https://shop.example/api/items", proxies=proxies, timeout=15)
print(r.status_code, r.headers.get("cf-ray"))

对于经 HTTP 代理的 HTTPS,requests 会自动使用 CONNECT。对于经 HTTP 代理的 HTTP,使用绝对形式。

Python:httpx

开箱支持 HTTP 代理,SOCKS5 通过 socksio 包(httpx[socks])。httpx 中的 socks5:// 方案会把域名传给代理服务器。支持 async,适合并行任务。

import httpx, asyncio

async def main():
async with httpx.AsyncClient(proxy="socks5://user:pass@gate.proxeon.net:1080") as c:
r = await c.get("https://shop.example/")
print(r.status_code)

asyncio.run(main())

Python:aiohttp

原生支持 HTTP 代理。SOCKS5 通过 aiohttp-socks 包,使用 ProxyConnector 类,其 rdns 参数控制解析位置。

Node.js:fetch 和 undici

Node.js 内置 fetch 使用 undici,其中有用于 HTTP 代理的 ProxyAgent。SOCKS5 则使用生态中的独立代理,比如用于 http/https 模块的 socks-proxy-agent。

import { ProxyAgent, fetch } from "undici";

const agent = new ProxyAgent({
 uri: "http://gate.proxeon.net:8080",
 token: "Basic " + Buffer.from("user:pass").toString("base64"),
});
const res = await fetch("https://shop.example/", { dispatcher: agent });
console.log(res.status);

Go

标准库 net/http 通过 Transport.Proxy 和环境变量支持 HTTP 代理。从 Go 1.13 起,HTTP_PROXY 里的 socks5:// 方案也被标准库接受。精细控制用 golang.org/x/net/proxy 包。

package main

import (
"fmt"
"net/http"
"net/url"
)

func main() {
u, _ := url.Parse("socks5://user:pass@gate.proxeon.net:1080")
tr := &http.Transport{Proxy: http.ProxyURL(u)}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://shop.example/")
if err != nil {
panic(err)
}
fmt.Println(resp.Status)
}

在 Go 中,socks5 方案会把域名传给代理:不执行本地解析。

Java

HTTP 代理通过系统属性 http.proxyHost、https.proxyHost 或类型为 Type.HTTP 的 Proxy 对象。SOCKS 通过 socksProxyHost 和 Proxy.Type.SOCKS。认证用 Authenticator 类。从 JDK 8u111 起,经 CONNECT 的 Basic 认证默认被 jdk.http.auth.tunneling.disabledSchemes 属性禁用,必须清空它,否则你会收到毫无解释的 407。这是最常见的支持工单原因之一。

.NET

HttpClient 搭配 SocketsHttpHandler 支持通过 WebProxy 使用 HTTP 代理。SOCKS4、SOCKS4a 和 SOCKS5 支持从 .NET 6 开始,同样通过 WebProxy 构造,地址形如 socks5://host:port。

浏览器

Chromium 系浏览器使用系统代理设置或 --proxy-server 参数。带认证的 HTTP 代理可用:浏览器会弹出登录对话框。通过系统设置使用 SOCKS5 时不支持认证,无法传入用户名密码。要用带密码的 SOCKS5,需要带 proxy API 的扩展或 IP 授权。Firefox 有自己的代理设置,支持 SOCKS5 和「Proxy DNS when using SOCKS v5」选项,也就是启用 ATYP=0x03。Firefox 同样不原生支持 SOCKS5 认证。

邮件客户端

Thunderbird 使用的代理设置类似 Firefox,支持对 IMAP 和 SMTP 使用 SOCKS5。许多其他桌面邮件客户端在账户设置里有独立的 SOCKS5 代理部分。通过 HTTP 代理收发邮件,只有在客户端能向 993 和 465 端口发 CONNECT 时才可行,这很少见。

系统级代理工具

对于没有自身代理设置的应用,存在一些程序在系统层面拦截网络调用,并将其重定向到 SOCKS5 或 HTTP 代理。几乎所有这类工具都倾向选 SOCKS5 作为传输,正是因为它不依赖内部协议。如果你的任务用到这类工具,请准备 SOCKS5 端口。

兼容性检查清单

  • 客户端支持 SOCKS5 吗?能在 SOCKS5 里传用户名密码吗?
  • 客户端是自己解析域名还是交给代理?有 remote DNS 设置吗?
  • 客户端能正确处理 407 并带认证重试吗?
  • 客户端对 HTTPS 使用 CONNECT,还是试图用 https 方案发绝对 URI(一些古老库会这样,而且不可行)?
  • 客户端复用与代理的连接,还是每个请求都开新连接?

工程实践中的案例

下面的数字对所述场景具有代表性,用于理解量级,不是保证结果。

案例一:跨区域监控店面

团队通过多国代理监控自有区域店面的可用性和价格。用 Python 和 requests 的 socks5:// 方案。偶尔指标显示某区域「错误的」价格,而店面本身运行正常。原因是:库在本地(办公室所在国)解析域名,把 CDN 边缘节点的 IP 传给了代理。代理虽然在其他国家,但还是老老实实连到这个地址,CDN 根据自己节点的位置而非客户端 IP 返回内容。换成 socks5h 方案后,解析交给了代理。异常测量比例几乎降到零,中位响应时间也下降了约三分之一,因为代理开始访问离自己最近的 CDN 节点。

案例二:大量短连接的采集器集群

一个开放价格聚合服务每天通过带用户名密码的 SOCKS5 执行多达数十万次请求,每个请求开新连接。性能分析显示,到代理延迟约 40 毫秒时,三 RTT 握手每次请求产生 120 毫秒开销,约占全部时间四分之一。改了两点:在 HTTP 客户端启用连接池,并切换到 IP 授权去掉一个 RTT。遍历列表的总时间缩短约 30%,代理数量不变。协议仍保留 SOCKS5,因为换成 HTTP CONNECT 的收益小于连接池。

案例三:邮箱与 IMAP

支持部门为几个区域代表处部署桌面邮件客户端,每个邮箱都需要通过特定区域的 IP 连接邮件服务器。第一次尝试 HTTP 代理失败:客户端不支持对 IMAP 发 CONNECT。切换到 Proxeon 的 SOCKS5 端口后几分钟就解决了,因为客户端对每个账户都有原生 SOCKS5 设置。不需要额外软件。

案例四:Java 应用和神秘的 407

一个企业集成商要让 Java 服务通过带 Basic 认证的 HTTP 代理连接外部 API。代理稳定返回 407,尽管凭据正确,而用相同参数的 curl 能工作。原因是:从某次 JDK 更新起,CONNECT 隧道的 Basic 认证默认被禁用。一个清空 jdk.http.auth.tunneling.disabledSchemes 的 JVM 参数解决了问题。这里正好体现了 HTTP 代理回复可读代码的好处:407 立刻指明了搜索方向,而 SOCKS5 在同样情况下会返回 05 FF,不懂协议就更难理解。

常见误解

  • 「SOCKS5 比 HTTP 代理更安全。」对于加密流量,两种协议给中间人的可见度相同:主机、端口、时间、数据量、SNI。内容安全靠 TLS 保障,不是靠代理协议。差异只在未加密 HTTP 上,明文 HTTP 代理能看到一切。
  • 「HTTP 代理不支持 HTTPS。」通过 CONNECT 支持。这是标准且普遍使用的机制,所有浏览器正是这样通过企业代理访问 HTTPS 网站的。
  • 「SOCKS5 因为是二进制所以更快。」建立连接时,带认证的 SOCKS5 比 HTTP CONNECT 花更多 RTT。数据传输时两者都不增加任何东西:握手之后两种情况都是纯 TCP 管道。速度取决于代理的通道,不是协议。
  • 「CONNECT 只能连 443 端口。」标准不限制端口。限制来自具体代理的配置。
  • 「SOCKS5 总是在代理端解析域名。」只有当客户端传 ATYP=0x03 时。许多库默认本地解析并传 IP。
  • 「HTTP 代理总会添加 X-Forwarded-For 暴露我的 IP。」那是具体企业配置的行为,不是协议属性。商业代理不会添加这类头,而在 CONNECT 模式下根本不可能添加。
  • 「代理能看到我的代理密码,那也能看到我的网站密码。」代理密码传给它,按标准不会再转发。TLS 隧道内的网站密码,代理看不到。
  • 「既然 SOCKS5 不解析内容,就没法限制它。」代理可以按目标主机、端口、数据量、速度和连接数限制。只是不能按 URL。
  • 「同一服务商的 HTTP 和 SOCKS5 端口是不同服务器。」通常是一台服务器、同一个出口 IP,只是入口协议不同。Proxeon 正是如此。
  • 「浏览器里带密码的 SOCKS5 和 HTTP 配置方式一样。」不一样,Chromium 和 Firefox 的系统设置不会向 SOCKS5 传凭据。需要 IP 授权或扩展。

常见问题

目标网站能判断我是通过 HTTP 代理还是 SOCKS5 进来的吗?

不能。目标服务器只能看到第二条连接,即从代理到自己,这两种情况都是来自代理 IP 地址的普通 TCP。唯一的例外是明文 HTTP 代理,它会添加 Via 或 X-Forwarded-For 头。CONNECT 模式和 SOCKS5 中这是不可能的。

如果我用 HTTP 代理访问 HTTPS,代理能窥探或替换内容吗?

除非它进行 TLS 拦截并替换证书,那样你的客户端会看到证书验证错误——除非你手动把该代理的根证书安装为受信任的。在正常工作中,CONNECT 之后代理只能看到加密流,无法读取或修改内容。

为什么带密码的 SOCKS5 在 Chrome 里不工作,而 HTTP 代理却可以?

Chromium 网络栈的实现支持通过 407 机制对 HTTP 代理弹出认证对话框,但不实现 RFC 1929 的 0x02 方法用于 SOCKS5。这是浏览器开发者的决定,不是协议限制。解决方案:在 Proxeon 面板使用 IP 地址授权,或用通过 API 管理代理的扩展。

如果 SOCKS5 返回 REP=05 怎么办?

代码 05 表示目标服务器拒绝了 TCP 连接:端口关闭、服务未运行,或者目标防火墙拦截了代理 IP。检查你传的端口是否正确,换一个代理地址试试。代理本身工作正常,问题在路径的后半段。

SOCKS5 的 REP=02 和 HTTP 代理的 403 有什么区别?

语义上相同:代理按自己的规则拒绝建立连接。通常意味着目标端口或主机被代理策略禁止。HTTP 代理的 403 响应正文里有时有解释,SOCKS5 只有一个字节。

HTTP 和 SOCKS5 端口能用同一个用户名密码吗?

在 Proxeon 可以:账号通用,只是端口和协议不同。切换协议不需要改凭据。

怎么判断我的客户端是在本地解析域名还是交给代理?

最可靠的方法:在自己的网卡上抓包,看连接代理之前是否有对目标域名的 DNS 查询。更简单的方法:在 hosts 文件里给目标域名临时指一个明显错误的地址。如果通过代理的请求仍能工作,说明代理在解析。如果坏了,说明客户端在解析。

如果库支持,值得对代理使用 HTTP/2 吗?

如果你有许多到不同目标的并行连接,而且客户端真的复用 CONNECT 流,收益会很明显:更少的 TCP 连接、更少的握手。但客户端和代理服务器对此的支持目前还不均衡。请查阅客户端文档,并确认你的套餐是否支持 HTTP/2 入口。

HTTP 代理里为什么用绝对 URI,不是有 Host 头吗?

HTTP/1.1 标准要求客户端访问代理时使用绝对形式,以便代理明确知道请求需要转发而不是本地处理。Host 头保留是因为代理转发给目标服务器时会用 origin-form,那里 Host 是必需的。重复是兼容性的代价。

对于爬虫,HTTP 和 SOCKS5 哪个更重要?

对 HTTPS 目标,可见度没有区别,区别在于库的行为。如果库对 SOCKS5 工作良好且传递域名,就选 SOCKS5:它不会去动请求头,还能给出正确的区域解析。如果库用 SOCKS5 很别扭,或者你有大量无池化的短连接,HTTP CONNECT 也毫不逊色。最重要的建议:不要每个请求都开一个新代理连接,这比任何协议选择都更贵。

结语

面板里的两个端口不是「普通版和高级版」的营销组合。它们是同一台机器的两种沟通语言。HTTP 代理说请求和响应的语言,理解语义,能缓存,用可读的状态码解释错误,对于不懂的东西它有 CONNECT,把自己变成 TCP 隧道。SOCKS5 说「主机和端口」的语言,不解析内容,支持任何 TCP 协议和 UDP,还能让你明确控制域名在哪里解析。

对于加密流量,两种协议给中间人的可见度相同。选择取决于实际三件事:你的客户端支持什么、里面是什么协议、你想在哪里解析域名。

现在该做什么

  1. 确定任务内的协议。不是 HTTP:直接选 SOCKS5。
  2. 查阅客户端文档,确认是否支持带认证的 SOCKS5 以及远程解析选项。
  3. 如果处理地域相关资源,确保域名传给代理:socks5h、remote DNS、ATYP=0x03 或 HTTP CONNECT。
  4. 为到代理的连接启用连接池或 keep-alive。这比换任何协议收益都大。
  5. 如果客户端无法在 SOCKS5 里传用户名,在 Proxeon 面板配置 IP 授权。
  6. 调试时先用 curl -v:它会显示与代理的完整对话,立刻区分是客户端问题还是网络问题。

最后一点。两种协议都比使用它们的多数工程师还要老,而且至今仍无本质变化地工作着。这是设计简洁性取胜的罕见例子。理解连接前四十个字节里到底发生了什么,你就不再随便选端口,而是开始选工具。