mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 12:30:44 +00:00
纠正一个传出去的错误结论:不是「运营商封了 UDP」
我在给部署脚本的指示里写过:
实测这条线路上运营商对 UDP 有限制 ——
12 个 Cloudflare edge IP:TCP 12/12 通、UDP 0/12 通
它照这个把注释写进了 relay/deploy.sh。
**但这个测量是无效的。** relay-stability 后来自己纠正了:
那个 UDP 探测包是手搓的简化包,格式不对,
Cloudflare 本来就不该回应它 —— 「0/12 无回应」什么都证明不了。
用 cloudflared --loglevel debug 跑临时实例,真相是:
UDP Connectivity region1/region2 PASS QUIC connection successful
SUMMARY: Environment is healthy. cloudflared will use 'quic'
Registered tunnel connection (握手 1 秒)
**UDP 完全通。**
真因是:QUIC 建连要 6+ 个包往返,而 TCP 的 SYN 只有 3 个包、
内核还会按 1s/2s/4s 激进重传。约 5.4%/包 丢包下:
(1 - 0.054)^6 ≈ 72% <- 和实测的 72% 对得上
所以 QUIC 的弱点在「连不上」,不在「连上之后不好」;
一旦连上,它没有 TCP 的跨流队头阻塞,反而更稳。
结论的**用途**没变(还是要两条协议一起跑),但留着错误的根因注释会:
* 误导后人以为 UDP 被封 -> 可能做出「干脆别用 QUIC」的错误决定
* 掩盖真正可优化的方向(QUIC 握手对丢包敏感是链路物理属性,
只能靠 HTTP/2 兜底来绕,不是「运营商限制」)
改动:relay/deploy.sh 两处、_build/test_deploy_sh.sh 一处。
教训记一笔:**手搓协议包去探测可达性非常容易得出错误结论** ——
那个包格式不对,对方根本没义务回应。要么用官方工具的自检
(cloudflared 自己会打 UDP Connectivity PASS/FAIL),
要么别下结论。
This commit is contained in:
@@ -1,57 +0,0 @@
|
|||||||
修局域网/中转对比基准:它自己有两个 bug,害产品背了黑锅
|
|
||||||
|
|
||||||
这个基准一直是红的(中转那条路报「连接阶段失败: timed out」),
|
|
||||||
而且报告里写着"这是中转链路的产品 bug"。**查下来不是产品的问题。**
|
|
||||||
|
|
||||||
== bug 1:两条路用了同一个配对码 ==
|
|
||||||
|
|
||||||
基准为了排除互相干扰,给两条路**各起一台全新被控端**(设计是对的),
|
|
||||||
但两台都用了写死的 code="246813"。
|
|
||||||
|
|
||||||
中转有「一个配对码只属于一台设备」的规则:
|
|
||||||
第 1 台(跑局域网)注册成功
|
|
||||||
-> stop_agent 杀掉进程,但**中转还以为它在线**(last_seen 没过期)
|
|
||||||
第 2 台(跑中转)用同一个码注册 -> **被拒**
|
|
||||||
中转日志:配对码 246813 已被 ... 占用,拒绝 ...
|
|
||||||
而 start_agent 只检查"有没有设备在线" —— 第 1 台僵尸还挂着,
|
|
||||||
于是**以为新的一台登记成功了**
|
|
||||||
-> 没有任何进程在回包 -> 超时
|
|
||||||
|
|
||||||
对照实验(新增 _build/verify_code_reuse.py):
|
|
||||||
场景 A 同一个码 -> **不通**
|
|
||||||
场景 B 各用不同码 -> 通
|
|
||||||
|
|
||||||
修法:两条路各用不同码;start_agent 的判据从"有没有在线设备"
|
|
||||||
改成"有没有**配对码正好是这一台要用的码**的在线设备"。
|
|
||||||
|
|
||||||
== bug 2:attach 用的还是旧码(我第一版补丁也漏了这处)==
|
|
||||||
|
|
||||||
relay_connect() 里 `client.attach(code)` 用的还是局域网那个码 ——
|
|
||||||
attach 到一台已经死掉、但中转以为在线的设备上,pair 自然没人回。
|
|
||||||
|
|
||||||
修成 `attach(relay_code)`。
|
|
||||||
|
|
||||||
== 顺带更新一段过时注释 ==
|
|
||||||
|
|
||||||
原文写着「当前版本经中转只稳定支撑前 1~2 次交互……这是中转链路的
|
|
||||||
产品 bug」,并据此把中转的轮数调小、超时调短。
|
|
||||||
|
|
||||||
那个 bug **已经修好了**(根因:一条 HTTP/1.1 连接被长轮询和发命令
|
|
||||||
两个线程同时用,撞状态机抛 CannotSendRequest("Idle"),
|
|
||||||
异常冒到 _client_loop 外面把 client-relay 线程打死)。
|
|
||||||
注释不更新会误导后人以为产品还有这个问题。
|
|
||||||
|
|
||||||
== 修好之后的实测(本地中转,30 次/路)==
|
|
||||||
|
|
||||||
局域网 握手 16ms 成功 30/30 (100%) 中位 0ms P90 0ms 最大 1ms
|
|
||||||
中转 握手 46ms 成功 30/30 (100%) 中位 15ms P90 29ms 最大 32ms
|
|
||||||
|
|
||||||
这是本地中转,量的是**协议本身的开销**(长轮询往返 vs 直连 TCP);
|
|
||||||
真实跨网络那条腿的 1.0~1.5 秒来自隧道和 4G,是另一回事。
|
|
||||||
|
|
||||||
结论:局域网确实是"毫秒级、不耗流量"的那条根本路径 ——
|
|
||||||
中位 0ms vs 15ms,而且不走任何外网。
|
|
||||||
|
|
||||||
踩坑记录:这两处都是"看起来像产品 bug、实际是测试自己的 bug"。
|
|
||||||
先怀疑测量工具,比先怀疑产品更省时间 ——
|
|
||||||
上一次(管理页缓存头)也是我拿 curl -I 测 GET 白查了半天。
|
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
r'''纠正一个我自己传出去的**错误结论**。
|
||||||
|
|
||||||
|
== 错在哪 ==
|
||||||
|
|
||||||
|
I(Lead) 在给部署脚本智能体的消息里写过:
|
||||||
|
|
||||||
|
「实测这条线路上运营商对 UDP 有限制 —— 对 12 个 Cloudflare edge IP
|
||||||
|
探测,TCP:7844 12/12 可达,UDP:7844 0/12 有回应」
|
||||||
|
|
||||||
|
它照这个说法把注释写进了 relay/deploy.sh。
|
||||||
|
|
||||||
|
**但这个测量是无效的。** relay-stability 后来自己纠正了:
|
||||||
|
那个 UDP 探测包是我用 Python 手搓的简化包,**格式不对,
|
||||||
|
Cloudflare 本来就不该回应它** —— "0/12 无回应"什么都证明不了。
|
||||||
|
|
||||||
|
用 `cloudflared --loglevel debug` 跑临时实例,真相是:
|
||||||
|
|
||||||
|
INF | UDP Connectivity region1.v2.argotunnel.com PASS QUIC connection successful
|
||||||
|
INF | UDP Connectivity region2.v2.argotunnel.com PASS QUIC connection successful
|
||||||
|
INF | SUMMARY: Environment is healthy. cloudflared will use 'quic' as primary protocol
|
||||||
|
INF Registered tunnel connection (握手 1 秒)
|
||||||
|
|
||||||
|
**UDP 完全通,握手成功时只要 1 秒。**
|
||||||
|
|
||||||
|
== 真因 ==
|
||||||
|
|
||||||
|
QUIC 握手需要 6+ 个包的往返;
|
||||||
|
TCP 的 SYN 只有 3 个包,而且内核会按 1s/2s/4s 激进重传。
|
||||||
|
|
||||||
|
在约 5.4%/包 的丢包下:
|
||||||
|
|
||||||
|
成功率 ≈ (1 - p)^6 = (1 - 0.054)^6 ≈ 72% <- 和实测的 72% 对得上
|
||||||
|
|
||||||
|
所以 QUIC 的弱点是「**连不上**」,不是「连上之后不好」;
|
||||||
|
一旦连上,它没有 TCP 的跨流队头阻塞,反而更稳。
|
||||||
|
|
||||||
|
== 为什么要改 ==
|
||||||
|
|
||||||
|
结论错了但**结论的用途是对的**(所以要两条协议一起跑),
|
||||||
|
可是留着一句错误的根因注释会:
|
||||||
|
* 误导后人以为 UDP 被封 -> 可能做出"干脆别用 QUIC"的错误决定
|
||||||
|
* 掩盖真正可优化的方向(QUIC 握手对丢包敏感,这是链路的物理属性,
|
||||||
|
只能靠 HTTP/2 兜底来绕,不是"运营商限制")
|
||||||
|
'''
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
|
||||||
|
|
||||||
|
NEW_REASON = """为什么两条一起跑 —— QUIC 握手对丢包敏感:
|
||||||
|
它要 6+ 个包往返才建得起来,而 TCP 的 SYN 只有
|
||||||
|
3 个包、内核还会按 1s/2s/4s 激进重传。
|
||||||
|
在约 5% 丢包下 (1-p)^6 只有 ~72% 能建成 ——
|
||||||
|
所以刻意让 HTTP/2 占几条连接兜住那 28%。
|
||||||
|
(UDP 本身是通的:cloudflared 自检 PASS,
|
||||||
|
握手成功时只要 1 秒。不是"运营商封了 UDP"。)"""
|
||||||
|
|
||||||
|
NEW_BLOCK = """# 根因也查清了:**QUIC 握手对丢包敏感**。
|
||||||
|
#
|
||||||
|
# QUIC 注册 525 失败 197 掉线 48 拨号成功率 72%
|
||||||
|
# HTTP/2 注册 13 失败 0 掉线 7 拨号成功率 100%
|
||||||
|
#
|
||||||
|
# QUIC 建连要 6+ 个包的往返,TCP 的 SYN 只有 3 个包、内核还会
|
||||||
|
# 按 1s/2s/4s 激进重传。约 5.4%/包的丢包下:
|
||||||
|
#
|
||||||
|
# (1 - 0.054)^6 ≈ 72% <- 和实测的 72% 对得上
|
||||||
|
#
|
||||||
|
# 注意:**这不是"运营商封了 UDP"**。曾经这么以为过,但那个判断
|
||||||
|
# 来自一个格式不对的手搓 UDP 探测包(Cloudflare 本来就不该回应它)。
|
||||||
|
# 用 cloudflared --loglevel debug 实测:
|
||||||
|
#
|
||||||
|
# INF | UDP Connectivity region1.v2.argotunnel.com PASS
|
||||||
|
# INF | UDP Connectivity region2.v2.argotunnel.com PASS
|
||||||
|
# INF | SUMMARY: Environment is healthy ...
|
||||||
|
# INF Registered tunnel connection (握手 1 秒)
|
||||||
|
#
|
||||||
|
# UDP 是通的。QUIC 的弱点在"连不上",不在"连上之后不好"——
|
||||||
|
# 一旦连上,它没有 TCP 的跨流队头阻塞,反而更稳。
|
||||||
|
# 所以对策是「多 QUIC + 少量 HTTP/2 兜底」,而不是放弃 QUIC。"""
|
||||||
|
|
||||||
|
|
||||||
|
def patch(path: Path, old: str, new: str, label: str) -> bool:
|
||||||
|
if not path.is_file():
|
||||||
|
print(" [跳过] %s 不存在" % path.name)
|
||||||
|
return True
|
||||||
|
text = path.read_text(encoding="utf-8")
|
||||||
|
if old not in text:
|
||||||
|
if "握手对丢包敏感" in text:
|
||||||
|
print(" • %s 已经是新说法" % label)
|
||||||
|
return True
|
||||||
|
print(" ✗ %s 没找到锚点" % label)
|
||||||
|
return False
|
||||||
|
if text.count(old) != 1:
|
||||||
|
print(" ! %s 锚点出现 %d 次" % (label, text.count(old)))
|
||||||
|
return False
|
||||||
|
path.write_text(text.replace(old, new, 1), encoding="utf-8", newline="\n")
|
||||||
|
print(" ✓ %s" % label)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
ok = True
|
||||||
|
|
||||||
|
ok &= patch(
|
||||||
|
ROOT / "relay" / "deploy.sh",
|
||||||
|
"""为什么两条一起跑 —— 运营商对 UDP 有限制
|
||||||
|
(12 个 edge IP:TCP 12/12 通、UDP 0/12 通),""",
|
||||||
|
NEW_REASON, "deploy.sh --help 里的说法")
|
||||||
|
|
||||||
|
ok &= patch(
|
||||||
|
ROOT / "relay" / "deploy.sh",
|
||||||
|
"""# 根因也查清了:**运营商对 UDP 有限制**。
|
||||||
|
#
|
||||||
|
# 对 12 个 Cloudflare edge IP 探测:
|
||||||
|
# TCP:7844 12/12 可达(169~252ms)
|
||||||
|
# UDP:7844 0/12 有回应
|
||||||
|
#
|
||||||
|
# 也就是 UDP 被链路限制,QUIC 先天不利,所以让它当主力、
|
||||||
|
# HTTP/2 兜底。""",
|
||||||
|
NEW_BLOCK, "deploy.sh 的根因注释")
|
||||||
|
|
||||||
|
ok &= patch(
|
||||||
|
ROOT / "_build" / "test_deploy_sh.sh",
|
||||||
|
"""# 运营商限 UDP(12 个 edge IP:TCP 12/12 通、UDP 0/12 通)""",
|
||||||
|
"""# QUIC 握手要 6+ 个包往返,TCP SYN 只要 3 个且激进重传,
|
||||||
|
# 约 5% 丢包下 (1-p)^6 ≈ 72%(不是"运营商封 UDP",那是错的测量)""",
|
||||||
|
"test_deploy_sh.sh 的说明")
|
||||||
|
|
||||||
|
print()
|
||||||
|
if not ok:
|
||||||
|
print(" 有锚点没匹配")
|
||||||
|
return 1
|
||||||
|
print(" 纠正完成")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
[PASS] 基准脚本存在
|
||||||
|
---- 一、两条路用不同的配对码 ----
|
||||||
|
[PASS] 定义了局域网用的 code
|
||||||
|
[PASS] 定义了中转用的 relay_code
|
||||||
|
[PASS] 两个码不一样(否则第 2 台会被一码一机规则拒掉)
|
||||||
|
---- 二、attach 要用 relay_code ----
|
||||||
|
[PASS] 能定位到 relay_connect
|
||||||
|
[PASS] relay_connect 里用的是 relay_code
|
||||||
|
[PASS] relay_connect 里没有裸 attach(code)
|
||||||
|
---- 三、start_agent 的判据要认码 ----
|
||||||
|
[PASS] start_agent 接受码参数
|
||||||
|
[PASS] 没有「只看有没有在线设备」的松判据
|
||||||
|
[PASS] 判据里比对了配对码
|
||||||
|
---- 四、真跑一遍短基准(两条路都要通)----
|
||||||
|
[PASS] 基准跑完并输出了 BENCH_RESULT
|
||||||
|
[PASS] 局域网那条路通了
|
||||||
|
[PASS] 中转那条路通了
|
||||||
|
|
||||||
|
========================================================
|
||||||
|
通过 13 项,失败 0 项
|
||||||
@@ -152,3 +152,24 @@ time code ms
|
|||||||
2026-10-07 18:14:59 200 1089
|
2026-10-07 18:14:59 200 1089
|
||||||
2026-10-07 18:15:15 200 987
|
2026-10-07 18:15:15 200 987
|
||||||
2026-10-07 18:15:31 200 999
|
2026-10-07 18:15:31 200 999
|
||||||
|
2026-10-07 18:15:48 200 1426
|
||||||
|
2026-10-07 18:16:04 200 1222
|
||||||
|
2026-10-07 18:16:20 200 1397
|
||||||
|
2026-10-07 18:16:37 200 1127
|
||||||
|
2026-10-07 18:16:53 200 1010
|
||||||
|
2026-10-07 18:17:09 200 1140
|
||||||
|
2026-10-07 18:17:25 200 1032
|
||||||
|
2026-10-07 18:17:41 200 1367
|
||||||
|
2026-10-07 18:17:57 200 1111
|
||||||
|
2026-10-07 18:18:13 200 1285
|
||||||
|
2026-10-07 18:18:30 200 1169
|
||||||
|
2026-10-07 18:18:46 200 1380
|
||||||
|
2026-10-07 18:19:02 200 1017
|
||||||
|
2026-10-07 18:19:18 200 1121
|
||||||
|
2026-10-07 18:19:34 200 1184
|
||||||
|
2026-10-07 18:19:51 200 1386
|
||||||
|
2026-10-07 18:20:07 200 1351
|
||||||
|
2026-10-07 18:20:23 200 1381
|
||||||
|
2026-10-07 18:20:40 200 1432
|
||||||
|
2026-10-07 18:20:56 200 1468
|
||||||
|
2026-10-07 18:21:13 200 1462
|
||||||
|
|||||||
|
@@ -519,7 +519,8 @@ echo "── 7c. 默认:两个隧道单元都要启用(协议级故障隔离
|
|||||||
stub_ok "7c"
|
stub_ok "7c"
|
||||||
# 真机实测(40 次/组):quic 8 + h2 2 拿到 100% 可用率 + 最低 P90/P99。
|
# 真机实测(40 次/组):quic 8 + h2 2 拿到 100% 可用率 + 最低 P90/P99。
|
||||||
# 单独跑一个协议都不如两个一起跑:
|
# 单独跑一个协议都不如两个一起跑:
|
||||||
# 运营商限 UDP(12 个 edge IP:TCP 12/12 通、UDP 0/12 通)
|
# QUIC 握手要 6+ 个包往返,TCP SYN 只要 3 个且激进重传,
|
||||||
|
# 约 5% 丢包下 (1-p)^6 ≈ 72%(不是"运营商封 UDP",那是错的测量)
|
||||||
# -> quic 拨号成功率只有约 72%,需要 http2 兜住失败的那部分。
|
# -> quic 拨号成功率只有约 72%,需要 http2 兜住失败的那部分。
|
||||||
: > "$STUB_LOG"
|
: > "$STUB_LOG"
|
||||||
printf 'lanremote-relay\n' > "$STUB_LOG.state"
|
printf 'lanremote-relay\n' > "$STUB_LOG.state"
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""守住「局域网/中转对比基准」的两个坑。
|
||||||
|
|
||||||
|
为什么专门为测试脚本立一个测试:
|
||||||
|
这个基准一度是红的,报告里写着「这是中转链路的产品 bug」——
|
||||||
|
而实际上产品是好的,是基准自己有两个 bug:
|
||||||
|
1. 两条路用了同一个配对码 -> 第 2 台被「一码一机」规则拒掉,
|
||||||
|
而第 1 台虽然被杀、中转还以为在线,于是"看着成功、实际没人回包"
|
||||||
|
2. relay_connect() 里 attach 用的还是旧码,attach 到死设备上
|
||||||
|
这两个 bug 让一个已经修好的产品背了黑锅,而且**很难自己发现** ——
|
||||||
|
因为症状(连接超时)看起来完全像产品问题。
|
||||||
|
|
||||||
|
所以既要做源码守卫(防止改回去),也要真跑一遍短版本
|
||||||
|
(防止断言与实现脱钩)。
|
||||||
|
|
||||||
|
覆盖:
|
||||||
|
一、源码守卫:两条路必须用不同的码
|
||||||
|
二、源码守卫:attach 必须用 relay_code
|
||||||
|
三、源码守卫:start_agent 的判据必须认码(不能被僵尸设备骗过)
|
||||||
|
四、真跑一遍短基准:两条路都要通
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
BENCH = ROOT / "_tests" / "bench_lan_vs_relay.py"
|
||||||
|
|
||||||
|
passed = 0
|
||||||
|
failed = 0
|
||||||
|
|
||||||
|
|
||||||
|
def check(name, ok, detail=""):
|
||||||
|
global passed, failed
|
||||||
|
if ok:
|
||||||
|
passed += 1
|
||||||
|
print(" [PASS] " + name)
|
||||||
|
else:
|
||||||
|
failed += 1
|
||||||
|
print(" [FAIL] " + name + ((" —— " + str(detail)) if detail else ""))
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
check("基准脚本存在", BENCH.is_file(), BENCH)
|
||||||
|
if not BENCH.is_file():
|
||||||
|
return 1
|
||||||
|
src = BENCH.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------- 一
|
||||||
|
print(" ---- 一、两条路用不同的配对码 ----")
|
||||||
|
has_lan = re.search(r'^\s*code\s*=\s*"(\d{6})"', src, re.M)
|
||||||
|
has_relay = re.search(r'^\s*relay_code\s*=\s*"(\d{6})"', src, re.M)
|
||||||
|
check("定义了局域网用的 code", bool(has_lan),
|
||||||
|
"找不到 `code = \"数字\"`")
|
||||||
|
check("定义了中转用的 relay_code", bool(has_relay),
|
||||||
|
"找不到 `relay_code = ...`")
|
||||||
|
if has_lan and has_relay:
|
||||||
|
check("两个码不一样(否则第 2 台会被一码一机规则拒掉)",
|
||||||
|
has_lan.group(1) != has_relay.group(1),
|
||||||
|
"两个码都是 %s" % has_lan.group(1))
|
||||||
|
|
||||||
|
# ---------------------------------------------------------- 二
|
||||||
|
print(" ---- 二、attach 要用 relay_code ----")
|
||||||
|
# 找出 relay_connect 函数体
|
||||||
|
m = re.search(r"def relay_connect\(\):(.*?)(?=\n def |\n relay =)",
|
||||||
|
src, re.S)
|
||||||
|
body = m.group(1) if m else ""
|
||||||
|
check("能定位到 relay_connect", bool(body), "函数没找到或改名了")
|
||||||
|
if body:
|
||||||
|
check("relay_connect 里用的是 relay_code",
|
||||||
|
"relay_code" in body,
|
||||||
|
"里面还在用 code —— attach 会打到局域网那台死设备上")
|
||||||
|
check("relay_connect 里没有裸 attach(code)",
|
||||||
|
not re.search(r"attach\(\s*code\s*\)", body),
|
||||||
|
"attach(code) 还在,要改成 attach(relay_code)")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------- 三
|
||||||
|
print(" ---- 三、start_agent 的判据要认码 ----")
|
||||||
|
m2 = re.search(r"def start_agent\(([^)]*)\):(.*?)(?=\n def )",
|
||||||
|
src, re.S)
|
||||||
|
body2 = m2.group(2) if m2 else ""
|
||||||
|
check("start_agent 接受码参数", bool(m2) and "agent_code" in m2.group(1),
|
||||||
|
m2.group(1) if m2 else "没找到")
|
||||||
|
if body2:
|
||||||
|
# 不能只判断"有没有在线设备"
|
||||||
|
only_online = re.search(
|
||||||
|
r'if\s+any\(d\.get\("online"\)', body2)
|
||||||
|
check("没有「只看有没有在线设备」的松判据", not only_online,
|
||||||
|
"僵尸设备会骗过它 —— 上一台被杀但中转还没清理")
|
||||||
|
check("判据里比对了配对码", "agent_code" in body2,
|
||||||
|
"应改成「在线且码正好是这一台要用的码」")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------- 四
|
||||||
|
print(" ---- 四、真跑一遍短基准(两条路都要通)----")
|
||||||
|
try:
|
||||||
|
proc = subprocess.run(
|
||||||
|
[sys.executable, "-u", str(BENCH),
|
||||||
|
"--rounds", "6", "--relay-rounds", "6", "--relay-timeout", "40"],
|
||||||
|
cwd=str(ROOT), capture_output=True, text=True,
|
||||||
|
encoding="utf-8", errors="replace", timeout=420)
|
||||||
|
out = (proc.stdout or "") + (proc.stderr or "")
|
||||||
|
m3 = re.search(r"BENCH_RESULT\s+(.*)", out)
|
||||||
|
result = m3.group(1) if m3 else ""
|
||||||
|
check("基准跑完并输出了 BENCH_RESULT", bool(result),
|
||||||
|
out[-300:] if not result else "")
|
||||||
|
if result:
|
||||||
|
nums = dict(re.findall(r"(\w+)=([\w/.\-]+)", result))
|
||||||
|
lan_ok = nums.get("lan_ok", "0/0")
|
||||||
|
relay_ok = nums.get("relay_ok", "0/0")
|
||||||
|
check("局域网那条路通了", lan_ok.startswith("6/6"), lan_ok)
|
||||||
|
check("中转那条路通了", relay_ok.startswith("6/6"), relay_ok)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
check("基准在 420 秒内跑完", False, "超时")
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
check("基准能跑起来", False, exc)
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 56)
|
||||||
|
print(" 通过 %d 项,失败 %d 项" % (passed, failed))
|
||||||
|
return 0 if failed == 0 else 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -400,6 +400,17 @@ $stageOut = & python (Join-Path $root "_tests\check_encoding.py") 2>&1
|
|||||||
Show-Fails $stageOut
|
Show-Fails $stageOut
|
||||||
Record "编码与换行" ($LASTEXITCODE -eq 0)
|
Record "编码与换行" ($LASTEXITCODE -eq 0)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- 3a5
|
||||||
|
Banner "3a5/7 局域网/中转对比基准(防测试自己骗人)"
|
||||||
|
# 这个基准一度是红的,而且报告里写着「这是中转链路的产品 bug」——
|
||||||
|
# 实际是基准自己有两个 bug:两条路用了同一个配对码、
|
||||||
|
# 以及 attach 用了旧码(打到已经死掉的设备上)。
|
||||||
|
# 一个已经修好的产品因此背了黑锅,而且症状(连接超时)
|
||||||
|
# 看起来完全像产品问题,极难自己发现。
|
||||||
|
$stageOut = & python (Join-Path $root "_tests\test_lan_bench_guard.py") 2>&1
|
||||||
|
Show-Fails $stageOut
|
||||||
|
Record "局域网对比基准" ($LASTEXITCODE -eq 0)
|
||||||
|
|
||||||
# ---------------------------------------------------------------- 3b
|
# ---------------------------------------------------------------- 3b
|
||||||
Banner "3b/7 Python 静态检查(pyflakes)"
|
Banner "3b/7 Python 静态检查(pyflakes)"
|
||||||
# 这一层能抓到"运行到某行才炸"的问题。
|
# 这一层能抓到"运行到某行才炸"的问题。
|
||||||
|
|||||||
+31
-9
@@ -98,8 +98,13 @@ ykxmz 服务器端 · 一键部署 v$VERSION
|
|||||||
--protocol P quic / http2 / both(默认 both)
|
--protocol P quic / http2 / both(默认 both)
|
||||||
默认**两条隧道一起跑**,这是实测最优:
|
默认**两条隧道一起跑**,这是实测最优:
|
||||||
主 quic + 8 条 副本 http2 + 2 条
|
主 quic + 8 条 副本 http2 + 2 条
|
||||||
为什么两条一起跑 —— 运营商对 UDP 有限制
|
为什么两条一起跑 —— QUIC 握手对丢包敏感:
|
||||||
(12 个 edge IP:TCP 12/12 通、UDP 0/12 通),
|
它要 6+ 个包往返才建得起来,而 TCP 的 SYN 只有
|
||||||
|
3 个包、内核还会按 1s/2s/4s 激进重传。
|
||||||
|
在约 5% 丢包下 (1-p)^6 只有 ~72% 能建成 ——
|
||||||
|
所以刻意让 HTTP/2 占几条连接兜住那 28%。
|
||||||
|
(UDP 本身是通的:cloudflared 自检 PASS,
|
||||||
|
握手成功时只要 1 秒。不是"运营商封了 UDP"。)
|
||||||
所以 quic 拨号成功率只有约 72%;但连上之后
|
所以 quic 拨号成功率只有约 72%;但连上之后
|
||||||
它的丢包恢复好于 http2。
|
它的丢包恢复好于 http2。
|
||||||
两个一起 = http2 兜住 quic 拨号失败的那部分,
|
两个一起 = http2 兜住 quic 拨号失败的那部分,
|
||||||
@@ -169,19 +174,36 @@ fi
|
|||||||
# quic 3 + h2 7 可用率 97% 中位 1271 P90 2558 P99 13934
|
# quic 3 + h2 7 可用率 97% 中位 1271 P90 2558 P99 13934
|
||||||
# quic 8 + h2 2 可用率 100% 中位 1250 P90 1715 P99 2632 <- 采用
|
# quic 8 + h2 2 可用率 100% 中位 1250 P90 1715 P99 2632 <- 采用
|
||||||
#
|
#
|
||||||
# 根因也查清了:**运营商对 UDP 有限制**。
|
# 根因也查清了:**QUIC 握手对丢包敏感**。
|
||||||
# 对 12 个 Cloudflare edge IP 探测:
|
#
|
||||||
# TCP:7844 12/12 可达
|
# QUIC 注册 525 失败 197 掉线 48 拨号成功率 72%
|
||||||
# UDP:7844 0/12 有回应
|
# HTTP/2 注册 13 失败 0 掉线 7 拨号成功率 100%
|
||||||
# 所以 QUIC 的拨号成功率只有约 72%;但只要拨上了,
|
#
|
||||||
# 它的丢包恢复明显好于 HTTP/2(QUIC 没有队头阻塞)。
|
# QUIC 建连要 6+ 个包的往返,而 TCP 的 SYN 只有 3 个包、
|
||||||
|
# 内核还会按 1s/2s/4s 激进重传。约 5.4%/包的丢包下:
|
||||||
|
#
|
||||||
|
# (1 - 0.054)^6 ≈ 72% <- 和实测的 72% 对得上
|
||||||
|
#
|
||||||
|
# ⚠ 注意:**这不是「运营商封了 UDP」。** 曾经这么以为过 ——
|
||||||
|
# 但那个判断来自一个格式不对的手搓 UDP 探测包,
|
||||||
|
# Cloudflare 本来就不该回应它,"0/12 无回应"什么都证明不了。
|
||||||
|
# 用 cloudflared --loglevel debug 实测真相是:
|
||||||
|
#
|
||||||
|
# INF | UDP Connectivity region1.v2.argotunnel.com PASS
|
||||||
|
# INF | UDP Connectivity region2.v2.argotunnel.com PASS
|
||||||
|
# INF | SUMMARY: Environment is healthy ...
|
||||||
|
# INF Registered tunnel connection (握手 1 秒)
|
||||||
|
#
|
||||||
|
# UDP 是通的。QUIC 的弱点在「连不上」,不在「连上之后不好」——
|
||||||
|
# 一旦连上,它没有 TCP 的跨流队头阻塞,反而更稳。
|
||||||
#
|
#
|
||||||
# 于是:单独用 QUIC,会有近三成的时候连不上;
|
# 于是:单独用 QUIC,会有近三成的时候连不上;
|
||||||
# 单独用 HTTP/2,队头阻塞在高丢包下会拖长尾延迟。
|
# 单独用 HTTP/2,队头阻塞在高丢包下会拖长尾延迟。
|
||||||
# 两个一起跑,HTTP/2 正好兜住 QUIC 拨号失败的那部分 ——
|
# 两个一起跑,HTTP/2 正好兜住 QUIC 拨号失败的那部分 ——
|
||||||
# 这就是**协议级故障隔离**,比在同一个协议上堆连接数有效得多。
|
# 这就是**协议级故障隔离**,比在同一个协议上堆连接数有效得多。
|
||||||
#
|
#
|
||||||
# (早先这里写的是"两条同时跑会互相争抢",那个结论是错的,已作废。)
|
# (早先这里写的是"两条同时跑会互相争抢",那个结论是错的,已作废。
|
||||||
|
# 再早还写过"运营商封 UDP",那个测量方法本身是错的,也作废。)
|
||||||
MAIN_PROTO=quic
|
MAIN_PROTO=quic
|
||||||
MAIN_CONNS="${QUIC_CONNS:-8}"
|
MAIN_CONNS="${QUIC_CONNS:-8}"
|
||||||
ALT_PROTO=http2
|
ALT_PROTO=http2
|
||||||
|
|||||||
Reference in New Issue
Block a user