代理客户端怎么选?从 Clash、Shadowrocket 的延迟实测说起
同一个代理节点,换个客户端,会不会更快?
这次在 Mac 上测试了 Shadowrocket 和 Clash by Hako。通过近端 VLESS + TLS 服务完成一次新建连接请求,默认条件下,Shadowrocket 的中位耗时约为 53.4 ms,Clash 约为 4.7 ms。连接复用后,两者相对直连的额外耗时中位数都不到 0.2 ms。
差距主要出现在第一次请求。进一步抓包发现,Shadowrocket 的连接中出现了约 40 ms 的 TCP 确认等待;调整服务端 ACK 行为后,其新建连接耗时降到了约 6.5 ms。
如果你关心的是连接刚建立时的响应速度,这次测试中,Clash by Hako 的表现更好。 下面先看两款客户端的差异,再追踪这几十毫秒去了哪里。
目录
首次请求:53.4 ms 与 4.7 ms
为了减少公网线路波动的干扰,我们把代理服务和 HTTP 目标放在同一台 Mac 内的 Linux 虚拟机上。测试程序通过客户端的本地 HTTP CONNECT 入口,经 VLESS + TLS 访问目标,读取完整响应后停止计时。
新建连接的耗时包含连接建立、适用的握手和一次 HTTP 请求。服务端保持默认 ACK 行为时,结果如下,单位均为毫秒(ms):
| 客户端 | 新建连接中位数 | 新建连接 P95 |
|---|---|---|
| Shadowrocket | 53.364 | 58.344 |
| Clash by Hako | 4.667 | 7.279 |
两款客户端分批测试,没有进行同时段交错的双客户端对测;各批次均设置直连和 Python VLESS 对照,完整基线列在后面的测试方法中。P95 表示约 95% 的样本不超过的耗时。
53.4 ms 是一次请求的完整耗时。 Shadowrocket 同批次的直连中位数为 1.683 ms,Clash 同批次为 0.714 ms。两批基线有所变化,但 Shadowrocket 的几十毫秒等待也在复测中重复出现:首次为 53.484 ms,复测为 53.364 ms。
当一次操作需要新建连接,尤其存在连续、相互依赖的请求时,这类等待更值得关注。它是否会带来明显体感,还取决于应用如何建立和复用连接。
连接复用后,两者开销都很小
连接已经建立后,在同一条通道上继续发送请求,默认 ACK 条件下的结果是:
| 客户端 | 复用连接中位数 | 复用连接 P95 | 相对直连的额外耗时中位数 |
|---|---|---|---|
| Shadowrocket | 0.551 | 0.741 | 0.192 |
| Clash by Hako | 0.333 | 0.501 | 0.113 |
最后一列先计算每轮客户端请求与直连请求的耗时差,再取中位数,因此不一定等于两个中位数直接相减。
两款客户端复用连接后的额外开销都在毫秒以下。本次最显著的差异集中在首次请求;只看连接复用后的成绩,会漏掉这段等待。
Shadowrocket 的约 40 ms 等在了哪里?
首次拆分 Shadowrocket 的连接过程时,建立 CONNECT 通道约需 6–8 ms,随后第一次 HTTP 请求又等待了约 43–48 ms。
抓包中的一条连接出现了这样的时序:
| 时间 | 事件 |
|---|---|
| 15:25:15.110254 | 客户端向服务端发送 64 字节数据 |
| 15:25:15.153325 | 服务端发送 ACK,确认收到数据 |
| 15:25:15.153461 | 客户端随即发送下一段数据 |
服务端约 43 ms 后才确认收到数据,客户端收到确认后紧接着发送后续数据。另一条连接也出现了约 40 ms 的间隔,符合 TCP 小包发送与 delayed ACK(延迟确认)相互等待的特征。
我们只对虚拟机返回 Mac 的测试路径临时启用 QUICKACK,让服务端更及时地确认数据,然后重新测量:
| 客户端 | 默认 ACK:新建中位数 | QUICKACK:新建中位数 | QUICKACK:新建 P95 |
|---|---|---|---|
| Shadowrocket | 53.364 | 6.502 | 8.484 |
| Clash by Hako | 4.667 | 4.464 | 6.564 |
Shadowrocket 从约 53.4 ms 降到 6.5 ms,Clash 则从约 4.7 ms 变为 4.5 ms。Shadowrocket 首轮测量也出现了同样变化:53.484 → 6.751 ms。在这套环境里,Shadowrocket 的首次请求明显受到 ACK 等待影响,Clash 没有出现同样量级的延迟。
这些证据支持 TCP 确认等待的解释;没有客户端内部追踪,还无法确定具体是哪一处实现触发了这种交互。但对使用者而言,这几十毫秒仍然是等待请求完成时实际付出的时间。
QUICKACK 改变了服务端的 TCP 行为,这组数据用于解释原因。前面的默认结果更直接地反映了此次未作调整时的表现。
这次怎么选,接下来测什么
如果主要看本次 macOS、VLESS + TLS 环境下的首次请求延迟,我会更倾向于 Clash by Hako:默认条件下,新建连接请求约需 4.7 ms,Shadowrocket 则约需 53.4 ms。即使调整服务端 ACK 行为,Shadowrocket 降到约 6.5 ms 后,Clash 在本次测量中仍然更快一些。
连接复用后,两者相对直连的额外耗时中位数都不到 0.2 ms。对大量复用已有连接的应用,这次测得的差距就小得多。
这次测试的 Clash by Hako 使用 mihomo 1.19.31 内核。它在新建连接上的表现,可能得益于 mihomo 的连接处理实现。接下来会继续补测其他客户端和内核,也会比较采用相同内核的不同客户端,看看这种优势能否重复出现。
测试环境与方法
以下是供核对和复测使用的环境、基线与本机测试数据。
| 项目 | 配置 |
|---|---|
| 测试日期 | 2026 年 10 月 2 日 |
| 设备与系统 | Apple M3,macOS 14.8.9 |
| Shadowrocket | 2.2.92(3445),本地 HTTP 入口 127.0.0.1:1082 |
| Clash by Hako | 1.0.12(57),mihomo 1.19.31,本地 HTTP 入口 127.0.0.1:7890 |
| 服务端 | Mac 内的 Linux 虚拟机 192.168.1.4 |
| Docker 服务 | Xray 26.3.27、Python HTTP 服务,使用 host 网络 |
| 代理协议 | VLESS TCP + TLS,临时自签名证书,仅测试节点允许不验证该证书 |
| HTTP 目标 | HTTP/1.1,固定返回 2 字节 ok |
| 未启用 | REALITY、Vision、mux |
文中的 Clash 指表中这款应用及内核版本。测量范围是 macOS 上的本地 HTTP/CONNECT 入口和近端 VLESS 路径,尚未覆盖公网、iPhone、TUN 数据路径、UDP 或大流量;目标请求也不涉及 DNS 和目标站的 HTTPS 握手。
计时使用 perf_counter_ns(),从建立连接或发出请求开始,到读取完整响应结束。新建连接计入建连时间;复用连接沿用已经建立的 TCP 通道。服务端日志确认请求经过 VLESS,直连对照的路由没有进入 TUN。近端 HTTP 服务和 Python VLESS 对照 socket 启用了 TCP_NODELAY。
本机每条路径测 300 次新建连接、500 次复用请求,另各预热 20 次;VLESS 每条路径测 100 次新建连接、200 次复用请求,另各预热 10 次。同一批次内各路径随机交错,默认 ACK 与 QUICKACK 分开统计。首次本机 Shadowrocket 测试只保留了统计输出,后续各批次保留逐次样本。
VLESS 各批次的完整结果
下表采用 Shadowrocket 最后一次复测和 Clash 正式测试。Python 最小 VLESS 实现用来帮助定位路径差异。
| 条件 | 批次与路径 | 新建中位数 | 新建 P95 | 复用中位数 | 复用 P95 |
|---|---|---|---|---|---|
| 默认 ACK | Shadowrocket 批次:直连 | 1.683 | 3.037 | 0.368 | 0.474 |
| 默认 ACK | Shadowrocket | 53.364 | 58.344 | 0.551 | 0.741 |
| 默认 ACK | 同批 Python VLESS | 10.698 | 14.179 | 0.435 | 0.644 |
| 默认 ACK | Clash 批次:直连 | 0.714 | 1.053 | 0.220 | 0.354 |
| 默认 ACK | Clash | 4.667 | 7.279 | 0.333 | 0.501 |
| 默认 ACK | 同批 Python VLESS | 6.668 | 8.402 | 0.276 | 0.406 |
| QUICKACK | Shadowrocket 批次:直连 | 0.708 | 1.028 | 0.248 | 0.366 |
| QUICKACK | Shadowrocket | 6.502 | 8.484 | 0.386 | 0.549 |
| QUICKACK | 同批 Python VLESS | 6.500 | 8.262 | 0.299 | 0.423 |
| QUICKACK | Clash 批次:直连 | 0.682 | 1.028 | 0.199 | 0.328 |
| QUICKACK | Clash | 4.464 | 6.564 | 0.312 | 0.472 |
| QUICKACK | 同批 Python VLESS | 6.599 | 7.825 | 0.243 | 0.387 |
QUICKACK 条件下,按每轮与直连配对计算,新建/复用连接的额外耗时中位数分别为 Shadowrocket 5.642 / 0.138 ms、Clash 3.731 / 0.100 ms。这些差值包含协议握手、服务端处理和两端调度等整条路径上的额外耗时。
本机 CONNECT 转发
这组目标位于 Mac 回环地址,经过客户端的本地代理入口,没有经过 VLESS 节点。
| 批次与路径 | 新建连接中位数 | 复用连接中位数 |
|---|---|---|
| Shadowrocket 批次:本机直连 | 0.190 | 0.083 |
| Shadowrocket:CONNECT | 0.507 | 0.141 |
| Clash 批次:本机直连 | 0.189 | 0.086 |
| Clash:CONNECT | 0.351 | 0.131 |
分别与本批次直连中位数相减,Shadowrocket 增加约 0.317 / 0.058 ms,Clash 增加约 0.162 / 0.045 ms。两款客户端在这组本机转发中的额外开销都在毫秒以下。