Files
ykxmz/_build/test_lan_e2e.py
xmz7891 7b7830d8c5 阶段3+4+5:管理页白色化 + 局域网诊断 + 全量验收
阶段3 管理页
- 清掉全部深色主题残留(地址框 #0b1220、按钮悬停 #262c36、
  删除按钮 #2d1214 等 11 处),现在 0 处深色
- 接口全部正常:health / downloads / netcheck

阶段4 局域网诊断
- 查清楚"电脑不在同一网络"是怎么回事:被控端是**对的**
  (lan_ips() 报 ['192.168.2.64', '169.254.83.107'],真实地址排第一),
  是手表当时真不在 192.168.2.x —— 可能连了 5G 频段的另一个路由器,或开着移动数据
- 所以改了手表的提示:不再只说"不在同一网络",
  而是把**双方地址都打出来**,用户一眼能看出是网段不同还是连错 WiFi
- 端到端验证局域网路径:认证 1 ms,功能列表 207 项

阶段1 结论修正(一天内推翻自己两次,如实记录)
- 第一次判断"优选 IP 无用":错。半小时内确实能避开超时 IP
- 第二次固定优选 IP:也错。那些实测 138ms 的 IP 半小时后全不通了
  —— Cloudflare anycast 可达性随运营商路由变化,固定 IP 等于钉死到会失效的地址
- 最终:不固定,靠 GeoDNS + cloudflared 重试 + ha-connections 2 冗余
- 撤销后立即变好:连接稳定 2 条、超时告警 0 次、设备侧延迟 4.40s -> 1.66s
- **真正的瓶颈**:分段实测发现本机到 Cloudflare 的**裸 RTT 就 1439 ms**,
  比经隧道的请求(1215 ms)还慢。说明瓶颈是这条网络的 4G 上行本身,
  不是设备的隧道。公网侧已无优化空间。
- 局域网 118 ms vs 公网 1215 ms,快 10 倍 —— 这才是该走的路

阶段5 验收
- 全量回归 29 项全通过
- 设备侧验收 38 项全通过
- 成品:setup exe 14.37 MB / server zip / watch apk,已同步到设备下载区
2026-10-07 00:13:10 +08:00

142 lines
4.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
r'''端到端验证局域网直连路径。
结论已经很清楚了:
局域网 118 ms vs 公网 1215 ms —— 快 10 倍
瓶颈是这条网络到 Cloudflare 的 4G 上行(裸 RTT 就 1.4 秒),
Cloudflare 那一侧没得优化。
所以真正要保证的是**局域网这条路能用**。
这个脚本起一个真实被控端,走 192.168.2.x 直连,量一下握手耗时。
'''
from __future__ import annotations
import json
import socket
import subprocess
import sys
import time
from pathlib import Path
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
PORT = 8875
CODE = "246810"
def talk(payload: dict, timeout: float = 12.0) -> dict:
sock = socket.create_connection(("127.0.0.1", PORT), timeout=timeout)
sock.settimeout(timeout)
sock.sendall((json.dumps(payload, ensure_ascii=False) + "\n").encode())
raw = b""
while b"\n" not in raw:
chunk = sock.recv(65536)
if not chunk:
break
raw += chunk
sock.close()
return json.loads(raw.split(b"\n")[0].decode("utf-8"))
def lan_ip() -> str:
try:
probe = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
probe.connect(("8.8.8.8", 80))
addr = probe.getsockname()[0]
probe.close()
return addr
except OSError:
return "192.168.2.64"
def main() -> int:
env = dict(__import__("os").environ)
env["YKXMZ_DATA"] = str(ROOT / "_build" / "_lantest_data")
Path(env["YKXMZ_DATA"]).mkdir(parents=True, exist_ok=True)
print(" 启动被控端(端口 %d,配对码 %s)" % (PORT, CODE))
proc = subprocess.Popen(
[sys.executable, str(ROOT / "windows" / "run_agent.py"),
"--port", str(PORT), "--code", CODE, "--silent"],
cwd=str(ROOT), env=env,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
passed = failed = 0
def check(name, ok, detail=""):
nonlocal passed, failed
if ok:
passed += 1
print(" [PASS] " + name)
else:
failed += 1
print(" [FAIL] " + name + " " + str(detail))
try:
time.sleep(7)
ip = lan_ip()
print(" 本机局域网地址: %s" % ip)
print()
# 1. 本机回环
print(" 一、回环路径")
t = time.perf_counter()
reply = talk({"cmd": "pair", "deviceId": "lantest",
"deviceName": "测试手表", "code": CODE})
loop_ms = (time.perf_counter() - t) * 1000
check("回环配对成功", reply.get("ok") is True, str(reply)[:90])
token = reply.get("token", "")
print(" 握手 %.0f ms" % loop_ms)
# 2. 局域网地址(真实用户路径)
print()
print(" 二、局域网地址 %s(手表实际走这条)" % ip)
sock = socket.create_connection((ip, PORT), timeout=10)
sock.settimeout(10)
t = time.perf_counter()
sock.sendall((json.dumps({"cmd": "auth", "deviceId": "lantest",
"token": token}, ensure_ascii=False)
+ "\n").encode())
raw = b""
while b"\n" not in raw:
raw += sock.recv(65536)
lan_ms = (time.perf_counter() - t) * 1000
auth = json.loads(raw.split(b"\n")[0].decode("utf-8"))
check("局域网认证成功", auth.get("ok") is True, str(auth)[:90])
print(" 认证 %.0f ms" % lan_ms)
# 3. 功能列表
t = time.perf_counter()
sock.sendall((json.dumps({"cmd": "feature_list"}, ensure_ascii=False)
+ "\n").encode())
raw = b""
while b"\n" not in raw:
raw += sock.recv(262144)
feat = json.loads(raw.split(b"\n")[0].decode("utf-8"))
feat_ms = (time.perf_counter() - t) * 1000
features = feat.get("features", [])
check("功能列表拿到 %d 项" % len(features), len(features) > 100)
print(" 功能列表 %.0f ms" % feat_ms)
sock.close()
print()
print(" 三、结论")
print(" 局域网往返 %.0f ms vs 公网 1215 ms" % lan_ms)
print(" 局域网快 %.1f 倍" % (1215 / max(lan_ms, 1)))
check("局域网明显快于公网", lan_ms < 300, "%.0f ms" % lan_ms)
finally:
proc.terminate()
try:
proc.wait(timeout=8)
except subprocess.TimeoutExpired:
proc.kill()
print()
print("=" * 52)
print(" 通过 %d 项,失败 %d 项" % (passed, failed))
return 0 if failed == 0 else 1
if __name__ == "__main__":
sys.exit(main())