修正 MTU 测试的过时断言(1500 -> 1400)

test_net_tune.py 原来断言 net-tune.sh 里写着 mtu 1500,
依据是更早一次实测「接口 MTU 降到 1400 会掉到 50%」。

那个结论后来被推翻了 —— 当时漏了一件事:
**MSS 钳制只管 TCP,QUIC 走 UDP 完全不受保护。**
路径 MTU 实测 1472(1500 的包不通),QUIC 默认发 ~1452 的包会被
中间设备静默丢掉,而 UDP 丢了不回 ICMP,只能等超时。
把接口 MTU 压到 1400 之后,QUIC 建齐 10 条连接从 28 秒缩到 12 秒。

所以现在是**故意**设 1400。断言跟着改,
不然它会永久假失败,还会诱导后人把真正的改进改回 1500。

顺带记录:全量回归 38 个阶段全部通过。
新增的三个阶段(局域网优化 / 手表局域网切换 / 编码与换行 /
管理页设备控制)都是这轮加的。
This commit is contained in:
xmz7891
2026-10-07 18:10:30 +08:00
parent 8880e7033d
commit aac43e559d
16 changed files with 1033 additions and 206 deletions
-164
View File
@@ -1,164 +0,0 @@
修「经中转几次后永久哑掉」——「按一下没反应」的真因
== 现象 ==
用户报:
* 局域网能搜到电脑,IP/端口/配对码都对,连上后**没有任何反应**
* 走中转「已配对」,但点按钮没反应
局域网那一半已经修过(preferLan)。这次抓到的是**经中转**那一半,
而且比预想的严重得多。
== 证据链(插桩复现出来的)==
被控端日志里直接抛了未捕获异常,线程当场死:
Exception in thread client-relay:
server.py:407 session.send(reply)
relay.py:166 self._link.send_to_client(data)
relay.py:357 self._post("/v1/agent/send", ...)
relay.py:766 raise RelayError(f"请求失败: {last_error}")
lanremote.relay.RelayError: 请求失败: Idle
之后的状态:
* client-relay 线程没了 —— 电脑再也不往手表回任何东西
* relay-link 线程还活着、还在长轮询 —— 中转那边看着「设备在线」
* **手表看到的就是「连上了,但按一下毫无反应」**
写入仓库的测试 _tests/test_relay_local.py 一直是这个形状:
前面几项 PASS、跑到经中转发命令就卡死。之前没人深挖。
== 根因 1:一条 HTTP/1.1 连接被两个线程同时用 ==
_post() 里锁只包住「取连接」那一段:
with self._http_lock:
connection = self._http # 锁到这里就放了
try:
connection.request("POST", ...) # 锁外发请求
于是两个线程会同时用同一条 connection:
* relay-link 线程跑 /v1/agent/poll —— 长轮询,一条请求挂 180 秒
* client-relay 线程同时 POST /v1/agent/send
HTTP/1.1 一条连接同一时刻只能有一个在途请求,第二个线程一进来
就撞上 http.client 状态机,抛 CannotSendRequest("Idle")。
_post 的「重试一次」也救不了:它 _drop_http() 之后,另一个线程
手里还攥着那条旧连接对象在发,照样坏。
修法:
* **发命令用独立连接**(_post 加 dedicated 参数)。
长轮询那条常年被占着,发命令不该去挤它,各走各的。
* 两条连接各自一把锁**锁住整个「发请求 + 读响应」周期**,
不再只锁取连接那一下。
* 普通请求若发现缓存连接正忙(长轮询在途),临时另开一条用完即关。
* _drop_http(dedicated=) 区分清哪条;finally 里一定归还占用
(否则那条连接会被永久标记为忙)。
== 根因 2:回包失败会打死整个会话线程 ==
server.py 的 _client_loop 里:
if reply is not None:
session.send(reply) # <- 不在任何 try 里
...
except OSError: # <- 外层只捕获 OSError
pass
而中转链路上 send 抛的是 RelayError,直接冒出去把线程打死。
**死一次就永久哑**,代价太大。
修法:session.send() 单独兜住所有异常,失败就断开这个会话
(让手表重连),绝不让线程消失。外层也补一个兜底 except。
== 修复效果 ==
修复前:pair OK -> ping OK -> ping 卡死 -> 线程崩溃,永久哑
修复后:pair 21ms -> ping 82ms -> ping 4ms -> ping 6ms,零异常
_tests/test_relay_local.py:**之前完全卡死 -> 现在 21/21 全过**
这个测试之前一直是红的,说明「35 个阶段的回归全绿」这件事
本身就不成立过 —— 某个阶段卡住时看起来像环境问题而不是缺陷。
== 同批修掉验证阶段找到的其它问题 ==
1. **TTL 清理 + 关掉接入开关 = 设备永久锁死**(严重)
register() 用 `device_id in self.devices` 判断老设备,但超时清理
把设备从 devices 和 codes 都删了。电脑关机过夜(必然 > 6 小时)
第二天开机就会被自己的开关挡住,而且**重启也救不回来**
(清 codes 时没落盘,重启后又当孤儿清一遍,自我维系)。
管理员在页面上还看不到这台设备,不知道该放开谁。
修法:状态文件里单独存 knownDevices 设备历史,超时清理绝不动它;
register 改查这份历史;清理后补 _save();delete_device 才真正忘记。
2. **管理页 JS 语法错误 -> 所有按钮失效**(严重)
admin.py 里 ADMIN_JS 是普通三引号字符串,我在 Python 源码里写的
`\n` 被变成真实换行,拼进 JS 就是字符串里的裸换行 ->
整段 <script> 不执行 -> 改码/删除/批量删除/全选全变幽灵按键。
元凶是我加改码功能那次提交。改成 `\\n`。
3. **--relay / --relay-status 崩溃**(严重)
RelayLink.start(url) 的 url 是必填参数,我把默认地址改空白时
run_agent.py 两处调用没跟着改。一处被 try 吞成「测试失败:missing
1 required positional argument」(用户以为地址填错了),
一处直接报栈。两处都补上 url。
4. 非法 deviceId(控制字符 / 路径分隔符)现在被拒(白名单:
字母数字 + - _ . :)
5. enable_relay() 第二次调用不再谎报成功(加 RelayLink.running 属性,
返回值反映真实运行状态;不再用「对象建过没有」判断)
6. snapshot() 清孤儿码后补 _save(),磁盘和内存不再脱节
== 局域网全面优化(task-2,另一个智能体完成)==
4 个真问题:
* **网段判断写死 /24** —— 在 /16 网络上 10.8.1.5 和 10.8.2.5
被判成不同网络,明明能直连却去走中转还费流量。
改成按网卡真实掩码比较。
* **广播只发一个包** —— 丢一个就整轮搜不到。改成 5 个时间点重发。
* **只探一个网段** —— 多网卡手表搜不到另一网段的电脑。改成全探。
* **走中转之后再也回不到局域网** —— 出门再回家永远挂中转。
新增定期回切(先便宜地 TCP ping,通了才动真格,
不破坏正在工作的中转连接)。
实测:局域网握手 2ms、50/50 = 100%、中位 0ms。
(跨网络那半之前拿不到数字,就是被上面那个线程崩溃挡住的 ——
现已修复。)
顺带:instance.py 单实例锁端口加 YKXMZ_LOCK_PORT 覆盖(默认不变)。
用户自己那台被控端正跑在这台机器上占着 8767/TCP + 8766/UDP,
是这次排查里好几个「测试怎么都不通」的根因。
== 服务端一键部署脚本(task-1,另一个智能体完成)==
relay/deploy.sh + relay/deploy-tune.sh,随服务端 zip 发布:
* 自动检查并安装 cloudflared
* 部署程序、装 4 个 systemd 服务、网络调优、配隧道、自检打公网地址
* **不传 --hostname 且没有 TUNNEL_TOKEN 时明确提示要填**,
不会静默用一个写死的域名(用户明确说过这个中转站是他自己用的,
不对外开放,绝不能把私人域名烧成默认值)
* 重跑 = 升级,已有数据/配对码/管理密码/凭据都不动
顺带修了发布包一直缺 site_shots/ 的老 bug
(package_remote.py 用 iterdir()+is_file() 收文件,目录进不去,
新机器装完官网截图全是 404)。
== 默认中转地址改成空白 ==
用户要求:软件默认不带任何中转地址,由用户自己填。
四处硬编码全部清空(server.py / MainActivity.java / run_agent.py /
relay.py 的注释和文档示例),全仓已无非注释的硬编码地址。
== 文档同步(task-5,另一个智能体完成)==
README / BEFORE-INSTALL / AFTER-INSTALL / 打包模板:
* 新增「软件里不带任何中转地址」告示,讲清跨网络要自己部署
* 修正 Android 版本(写的是 9.0/API28,实际 API 24 / Android 7.0)
* 删掉「地址已经内置好了」(现在是错的)
* AGENT_README 原来把作者自己的中转地址当成「内置地址」发给用户,已重写
* RELAY_README 原来写「两条隧道同时跑会互相争抢」,
和真机/CHANGELOG 矛盾;按实测改成「两种协议同时在跑(故意的)」
BIN
View File
Binary file not shown.
+3 -1
View File
@@ -425,7 +425,9 @@ RELAY_README = f"""========================================================
重跑 deploy.sh,带上参数就行(幂等,不会冲掉已有数据):
bash deploy.sh --protocol http2 # 主实例换成 http2
bash deploy.sh --protocol http2 # 只跑 HTTP/2(停掉 QUIC 那半边)
bash deploy.sh --protocol quic # 只跑 QUIC
bash deploy.sh # 不传 = 两条一起跑(推荐)
bash deploy.sh --port 9090 # 换监听端口
bash deploy.sh --hostname relay.你的域名
+294
View File
@@ -0,0 +1,294 @@
======================================================================
0/7 清理残留进程与端口
======================================================================
端口干净
清掉上次测试残留的 4 个文件
======================================================================
0/7 编码审计(BOM / 行尾 / 乱码)
======================================================================
==========================================================================
✓ 全部通过,没有编码问题
======================================================================
1/7 Python 被控端协议回归
======================================================================
[PASS] 移除后仅剩 1 台
====================================================================
通过 69 项,失败 0 项
======================================================================
1b/7 防火墙阻止规则检测(真机连不上的根因)
======================================================================
====================================================================
通过 43 项,失败 0 项
====================================================================
======================================================================
1c/7 连不上?-> 电脑打开网络自检(UDP 链路)
======================================================================
============================================================
通过 5 项,失败 0 项
======================================================================
1e/7 新增功能(电量/磁盘/性能/进程/剪贴板历史/安全边界)
======================================================================
==============================================================
通过 22 项,失败 0 项
======================================================================
1f/7 电脑功能注册表(39 个功能 + 安全边界 + 可配置)
======================================================================
==============================================================
通过 29 项,失败 0 项
======================================================================
1g/7 功能模糊测试(105 个功能 × 空参数 / 垃圾参数 / 越权路径)
======================================================================
[崩溃] hand_reset({}) -> FileNotFoundError: [Errno 2] No such file or directory: 'C:\\Users\\shu\\AppData\\Local\\Temp\\tmp8myil6kd\\settings.json'
[崩溃] lesson_timer({}) -> FileNotFoundError: [Errno 2] No such file or directory: 'C:\\Users\\shu\\AppData\\Local\\Temp\\tmp8myil6kd\\settings.json'
[崩溃] loop_play({}) -> FileNotFoundError: [Errno 2] No such file or directory: 'C:\\Users\\shu\\AppData\\Local\\Temp\\tmp8myil6kd\\settings.json'
======================================================================
1h/7 端到端(打包后的 EXE:配对 / 功能列表 / 越权 / 防爆破)
======================================================================
==============================================================
通过 13 项,失败 0 项
======================================================================
1i/7 静默模式(0 窗口 / 无终端 / 认证仍生效)
======================================================================
==============================================================
通过 8 项,失败 0 项
======================================================================
1j/7 配一次→零窗口(令牌重连,全程无窗口)
======================================================================
==============================================================
通过 12 项,失败 0 项
======================================================================
1k/7 首次安装设置窗口(出现一次,之后不再出现)
======================================================================
==============================================================
通过 14 项,失败 0 项
======================================================================
1l/7 开机零窗口(照注册表里的自启命令真跑一遍)
======================================================================
==============================================================
通过 7 项,失败 0 项
======================================================================
2/7 手表屏幕布局适配核算 (490x602)
======================================================================
==========================================================================
通过 40 项,失败 0 项
======================================================================
1z/7 手表分页完整性(防白屏:方法定义了必须被调用)
======================================================================
sendMedia: 已被 media() 取代
showInfo: 已被 feature_run 的 info 取代
sp: 旧的字号辅助,已被 dp() 取代
==========================================================
通过 13 项,失败 0 项
======================================================================
2a2/7 APK 内容(诊断已合并进主程序)
======================================================================
通过 15 项,失败 0 项
====================================================================
======================================================================
2a/7 网络调优(MTU 修复)
======================================================================
[PASS] 写了接口 MTU 1400 更差的教训
==========================================================
通过 21 项,失败 0 项
======================================================================
2b/7 投图显示方式(屏幕中间 / 全屏 / 自定义大小)
======================================================================
==============================================================
通过 19 项,失败 0 项
截图目录: C:\Users\shu\Desktop\deepseekagent\LanRemote\_tests\shots
======================================================================
3/7 屏幕共享可见提示(真实界面 + Win32 窗口枚举)
======================================================================
====================================================================
通过 13 项,失败 0 项
======================================================================
2b/7 中转 chunked 请求体(防连接错位)
======================================================================
通过 9 项,失败 0 项
======================================================================
3a/7 局域网直连路径(不经中转)
======================================================================
通过 29 项,失败 0 项
======================================================================
3a2/7 局域网优化(网段掩码 / 发现重发 / 回切)
======================================================================
通过 42 项,失败 0 项
======================================================================
3a3/7 手表局域网切换逻辑(源码断言)
======================================================================
通过 31 项,失败 0 项
======================================================================
3a4/7 编码与换行(.java/.py 必须 UTF-8 无 BOM + LF)
======================================================================
检查 24 个文件,编码/换行全部符合约定
======================================================================
3b/7 Python 静态检查(pyflakes)
======================================================================
✓ 没有会导致崩溃的问题
======================================================================
3b3/7 安装包脚本语法(Inno 的注释陷阱)
======================================================================
[Code] 段落里没有违规写法 ✓
======================================================================
3b4/7 卸载逻辑(不能杀自己 / 不能阻塞)
======================================================================
通过 11 项,失败 0 项
======================================================================
3b2/7 管理页网络体检
======================================================================
通过 15 项,失败 0 项
======================================================================
3b5/7 安装包完整周期(装 -> 验 -> 卸 -> 再验)
======================================================================
通过 20 项,失败 0 项
======================================================================
3b6/7 网页响应头(Content-Type / HEAD)
======================================================================
通过 12 项,失败 0 项
======================================================================
3b7/7 对外材料清洁度(不许出现管理后台)
======================================================================
通过 15 项,失败 0 项
======================================================================
3c/7 中转管理端批量删除
======================================================================
通过 24 项,失败 0 项
======================================================================
3d/7 管理页 JS 合法性(防幽灵按键)
======================================================================
结论:JS 合法,删除按钮能正常工作
======================================================================
3e/7 管理页设备控制(接入开关 + 改配对码)
======================================================================
通过 23 项,失败 0 项
======================================================================
4b/7 手表 Activity 骨架(防白屏)
======================================================================
通过 24 项,失败 0 项
======================================================================
4c/7 编译整个手表 App(含界面代码)
======================================================================
App 编译通过
======================================================================
5/7 编译 Java 协议层
======================================================================
编译通过
======================================================================
6/7 Java <-> 桩服务端 跨语言联调
======================================================================
[PASS] 关闭后 isConnected 为 false
====================================================================
通过 64 项,失败 0 项
======================================================================
7/7 Java <-> 打包后的 EXE 端到端联调
======================================================================
EXE 配对码: 421865
[PASS] 关闭后 isConnected 为 false
====================================================================
通过 64 项,失败 0 项
EXE 接收到的文件:
1,024 B broken.png
307,207 B java_payload.bin
202 B java_photo.png
16,044 B java_sound.wav
======================================================================
验证汇总
======================================================================
项目 结果
-- --
编码审计 PASS
Python 被控端协议回归 PASS
防火墙阻止规则检测 PASS
自检请求链路 PASS
新增功能 PASS
电脑功能注册表 PASS
功能模糊测试 PASS
端到端(打包 EXE) PASS
静默模式 PASS
配一次→零窗口 PASS
首次设置窗口 PASS
开机零窗口 PASS
手表布局适配 PASS
手表分页完整性 PASS
APK 内容 PASS
网络调优(MTU) PASS
投图显示方式 PASS
屏幕共享强制提示 PASS
中转 chunked 请求体 PASS
局域网直连路径 PASS
局域网优化 PASS
手表局域网切换 PASS
编码与换行 PASS
Python 静态检查 PASS
安装包脚本语法 PASS
卸载逻辑 PASS
管理页网络体检 PASS
安装包完整周期 PASS
网页响应头 PASS
对外材料清洁度 PASS
中转批量删除 PASS
管理页 JS 合法性 PASS
管理页设备控制 PASS
手表 Activity 骨架 PASS
手表 App 整体编译 PASS
Java 协议层编译 PASS
Java <-> 桩服务端 PASS
Java <-> 打包 EXE PASS
全部通过
+294
View File
@@ -0,0 +1,294 @@
======================================================================
0/7 清理残留进程与端口
======================================================================
端口干净
清掉上次测试残留的 4 个文件
======================================================================
0/7 编码审计(BOM / 行尾 / 乱码)
======================================================================
==========================================================================
鉁?鍏ㄩ儴閫氳繃锛屾病鏈夌紪鐮侀棶棰?
======================================================================
1/7 Python 被控端协议回归
======================================================================
[PASS] 绉婚櫎鍚庝粎鍓?1 鍙?
====================================================================
閫氳繃 69 椤癸紝澶辫触 0 椤?
======================================================================
1b/7 防火墙阻止规则检测(真机连不上的根因)
======================================================================
====================================================================
閫氳繃 43 椤癸紝澶辫触 0 椤?
====================================================================
======================================================================
1c/7 连不上?-> 电脑打开网络自检(UDP 链路)
======================================================================
============================================================
閫氳繃 5 椤癸紝澶辫触 0 椤?
======================================================================
1e/7 新增功能(电量/磁盘/性能/进程/剪贴板历史/安全边界)
======================================================================
==============================================================
閫氳繃 22 椤癸紝澶辫触 0 椤?
======================================================================
1f/7 电脑功能注册表(39 个功能 + 安全边界 + 可配置)
======================================================================
==============================================================
閫氳繃 29 椤癸紝澶辫触 0 椤?
======================================================================
1g/7 功能模糊测试(105 个功能 × 空参数 / 垃圾参数 / 越权路径)
======================================================================
[宕╂簝] hand_reset({}) -> FileNotFoundError: [Errno 2] No such file or directory: 'C:\\Users\\shu\\AppData\\Local\\Temp\\tmp5d758dnb\\settings.json'
[宕╂簝] lesson_timer({}) -> FileNotFoundError: [Errno 2] No such file or directory: 'C:\\Users\\shu\\AppData\\Local\\Temp\\tmp5d758dnb\\settings.json'
[宕╂簝] loop_play({}) -> FileNotFoundError: [Errno 2] No such file or directory: 'C:\\Users\\shu\\AppData\\Local\\Temp\\tmp5d758dnb\\settings.json'
======================================================================
1h/7 端到端(打包后的 EXE:配对 / 功能列表 / 越权 / 防爆破)
======================================================================
==============================================================
閫氳繃 13 椤癸紝澶辫触 0 椤?
======================================================================
1i/7 静默模式(0 窗口 / 无终端 / 认证仍生效)
======================================================================
==============================================================
閫氳繃 8 椤癸紝澶辫触 0 椤?
======================================================================
1j/7 配一次→零窗口(令牌重连,全程无窗口)
======================================================================
==============================================================
閫氳繃 12 椤癸紝澶辫触 0 椤?
======================================================================
1k/7 首次安装设置窗口(出现一次,之后不再出现)
======================================================================
==============================================================
閫氳繃 14 椤癸紝澶辫触 0 椤?
======================================================================
1l/7 开机零窗口(照注册表里的自启命令真跑一遍)
======================================================================
==============================================================
閫氳繃 7 椤癸紝澶辫触 0 椤?
======================================================================
2/7 手表屏幕布局适配核算 (490x602)
======================================================================
==========================================================================
閫氳繃 40 椤癸紝澶辫触 0 椤?
======================================================================
1z/7 手表分页完整性(防白屏:方法定义了必须被调用)
======================================================================
sendMedia: 宸茶� media() 鍙栦唬
showInfo: 宸茶� feature_run 鐨?info 鍙栦唬
sp: 鏃х殑瀛楀彿杈呭姪锛屽凡琚?dp() 鍙栦唬
==========================================================
閫氳繃 13 椤癸紝澶辫触 0 椤?
======================================================================
2a2/7 APK 内容(诊断已合并进主程序)
======================================================================
閫氳繃 15 椤癸紝澶辫触 0 椤?
====================================================================
======================================================================
2a/7 网络调优(MTU 修复)
======================================================================
[PASS] 鍐欎簡鎺ュ彛 MTU 1400 鏇村樊鐨勬暀璁?
==========================================================
閫氳繃 20 椤癸紝澶辫触 1 椤?
======================================================================
2b/7 投图显示方式(屏幕中间 / 全屏 / 自定义大小)
======================================================================
==============================================================
閫氳繃 19 椤癸紝澶辫触 0 椤?
鎴�浘鐩�綍: C:\Users\shu\Desktop\deepseekagent\LanRemote\_tests\shots
======================================================================
3/7 屏幕共享可见提示(真实界面 + Win32 窗口枚举)
======================================================================
====================================================================
閫氳繃 13 椤癸紝澶辫触 0 椤?
======================================================================
2b/7 中转 chunked 请求体(防连接错位)
======================================================================
閫氳繃 9 椤癸紝澶辫触 0 椤?
======================================================================
3a/7 局域网直连路径(不经中转)
======================================================================
閫氳繃 29 椤癸紝澶辫触 0 椤?
======================================================================
3a2/7 局域网优化(网段掩码 / 发现重发 / 回切)
======================================================================
閫氳繃 42 椤癸紝澶辫触 0 椤?
======================================================================
3a3/7 手表局域网切换逻辑(源码断言)
======================================================================
閫氳繃 31 椤癸紝澶辫触 0 椤?
======================================================================
3a4/7 编码与换行(.java/.py 必须 UTF-8 无 BOM + LF)
======================================================================
妫€鏌?24 涓�枃浠讹紝缂栫爜/鎹㈣�鍏ㄩ儴绗﹀悎绾﹀畾
======================================================================
3b/7 Python 静态检查(pyflakes)
======================================================================
鉁?娌℃湁浼氬�鑷村穿婧冪殑闂��
======================================================================
3b3/7 安装包脚本语法(Inno 的注释陷阱)
======================================================================
[Code] 娈佃惤閲屾病鏈夎繚瑙勫啓娉?鉁?
======================================================================
3b4/7 卸载逻辑(不能杀自己 / 不能阻塞)
======================================================================
閫氳繃 11 椤癸紝澶辫触 0 椤?
======================================================================
3b2/7 管理页网络体检
======================================================================
閫氳繃 15 椤癸紝澶辫触 0 椤?
======================================================================
3b5/7 安装包完整周期(装 -> 验 -> 卸 -> 再验)
======================================================================
閫氳繃 20 椤癸紝澶辫触 0 椤?
======================================================================
3b6/7 网页响应头(Content-Type / HEAD)
======================================================================
閫氳繃 12 椤癸紝澶辫触 0 椤?
======================================================================
3b7/7 对外材料清洁度(不许出现管理后台)
======================================================================
閫氳繃 15 椤癸紝澶辫触 0 椤?
======================================================================
3c/7 中转管理端批量删除
======================================================================
閫氳繃 24 椤癸紝澶辫触 0 椤?
======================================================================
3d/7 管理页 JS 合法性(防幽灵按键)
======================================================================
缁撹�锛欽S 鍚堟硶锛屽垹闄ゆ寜閽�兘姝e父宸ヤ綔
======================================================================
3e/7 管理页设备控制(接入开关 + 改配对码)
======================================================================
閫氳繃 23 椤癸紝澶辫触 0 椤?
======================================================================
4b/7 手表 Activity 骨架(防白屏)
======================================================================
閫氳繃 24 椤癸紝澶辫触 0 椤?
======================================================================
4c/7 编译整个手表 App(含界面代码)
======================================================================
App 编译通过
======================================================================
5/7 编译 Java 协议层
======================================================================
编译通过
======================================================================
6/7 Java <-> 桩服务端 跨语言联调
======================================================================
[PASS] 鍏抽棴鍚?isConnected 涓?false
====================================================================
閫氳繃 64 椤癸紝澶辫触 0 椤?
======================================================================
7/7 Java <-> 打包后的 EXE 端到端联调
======================================================================
EXE 配对码: 193406
[PASS] 鍏抽棴鍚?isConnected 涓?false
====================================================================
閫氳繃 64 椤癸紝澶辫触 0 椤?
EXE 接收到的文件:
1,024 B broken.png
307,207 B java_payload.bin
202 B java_photo.png
16,044 B java_sound.wav
======================================================================
验证汇总
======================================================================
项目 结果
---- ----
编码审计 PASS
Python 被控端协议回归 PASS
防火墙阻止规则检测 PASS
自检请求链路 PASS
新增功能 PASS
电脑功能注册表 PASS
功能模糊测试 PASS
端到端(打包 EXE) PASS
静默模式 PASS
配一次→零窗口 PASS
首次设置窗口 PASS
开机零窗口 PASS
手表布局适配 PASS
手表分页完整性 PASS
APK 内容 PASS
网络调优(MTU) FAIL
投图显示方式 PASS
屏幕共享强制提示 PASS
中转 chunked 请求体 PASS
局域网直连路径 PASS
局域网优化 PASS
手表局域网切换 PASS
编码与换行 PASS
Python 静态检查 PASS
安装包脚本语法 PASS
卸载逻辑 PASS
管理页网络体检 PASS
安装包完整周期 PASS
网页响应头 PASS
对外材料清洁度 PASS
中转批量删除 PASS
管理页 JS 合法性 PASS
管理页设备控制 PASS
手表 Activity 骨架 PASS
手表 App 整体编译 PASS
Java 协议层编译 PASS
Java <-> 桩服务端 PASS
Java <-> 打包 EXE PASS
项失败
+116 -2
View File
@@ -85,7 +85,82 @@ QUIC 建齐 6 条连接 28 秒 -> 13 秒
dial 失败次数 2 -> 1
```
### 2.4 异常长轮询(126 秒)是隧道重启造成的
### 2.5 周期性 QUIC 崩溃:每 8~10 分钟一次
长测期间观察到一个反复出现的模式:**QUIC 的连接数会突然从 8 掉到 1,几秒后又回到 8。**
连接数采样(每 60 秒)里抓到的几个瞬间:
```
17:30:53 quic=7 h2=2
17:31:59 quic=1 h2=2 <- QUIC 塌了
17:33:04 quic=8 h2=2 <- 自己回来了
...
17:55:33 quic=8 h2=2
17:57:06 quic=1 h2=2 <- 又塌
17:58:12 quic=8 h2=2
```
**关键观察:QUIC 塌到 1 条时,HTTP/2 那 2 条始终存活。**
也就是说:
* 这是**链路级**的突发事件(不是单条连接的问题),QUIC 对它特别敏感
* 而 HTTP/2 over TCP 靠内核的 SYN/重传扛住了
watchdog 日志同时段记录:
```
17:56:39 公网不通(HTTP 000,quic=1, h2=2) 连续第 1 次
17:56:59 公网不通(HTTP 000,quic=7, h2=2) 连续第 2 次 <- 自己回来了
17:57:09 公网不通(HTTP 502,quic=6, h2=2) 连续第 3 次
17:57:09 公网连续 3 次不通,但连接健康(合计 8 条)—— 再等等,不重启
17:57:11 公网不通(HTTP 502,quic=1, h2=1) 连续第 4 次 <- 又塌,且 h2 也掉了
17:57:11 连续 4 次公网不通 —— 重启 [两个实例]
17:57:37 本次已建齐(合计 10 条) <- 26 秒恢复
```
**这次重启是对的**:连接总数掉到 2 条,属于确认故障。
而且**重启把中断缩短到 26 秒** —— 比等 QUIC 自然恢复(可能要几分钟)更划算。
**一个值得后续验证的改进方向**:
QUIC 塌掉时只剩 http2=2 在扛,容量很薄。把 HTTP/2 从 2 条提到 4 条,
可能能在这种窗口里多撑一会儿。但注意第 2.2 节的配比对照是
**2 分钟的短样本**,抓不到这种 9 分钟周期的事件 —— 要验证得跑更长的对照。
### 2.6 故障不集中在某个时段
把 watchdog 记录的"公网不通"按小时分桶,全天分布是均匀的,
**没有出现"运营商某时段抖动"的特征**:
```
16:14 / 16:21 / 16:22 / 16:30 / 16:34 / 16:40 / 16:41 / 16:42
16:49 / 16:50 / 17:00 / 17:10 / 17:11 / 17:12 / 17:13 ...
```
多数是 1 秒内自愈(`公网不通` 紧接着 `公网恢复`)。
真正造成可见中断的只有三次:
```
16:30:19 -> 16:34:28 (4 分 09 秒)
16:50:39 -> 16:53:04 (2 分 25 秒)
17:00:20 -> 17:02:27 (2 分 07 秒)
```
### 2.7 客户端与设备同时故障 = 源站侧问题
PC 侧长测(WiFi/宽带,与设备完全不同的网络)在同一个窗口也失败了:
```
PC 侧: 17:56:57 code=0 42093ms
17:57:13 code=502 674ms
17:58:57 code=0 25818ms
设备侧: 17:56:46 code=000 25000ms
17:57:06 code=502 4652ms
```
**两个独立网络同时受影响,说明问题在 Cloudflare 边缘或隧道这一侧,
不是某一端的本地链路。**
30 次长轮询里出现两次 ~126.7 秒(正好是 2 倍 hold + 开销):
@@ -152,7 +227,46 @@ dial 失败次数 2 -> 1
## 四、实测数据
(待 2 小时长测结束后填入)
### 4.1 长轮询:30 次真实 poll(每次挂 60 秒)
```
成功 29/30 = 96%
失败 #22(000,130001ms —— 撞上 17:57 那次隧道重启,客户端 130 秒超时)
耗时分布(成功的):
最小 60920 P50 61439 P90 126604 P99 126716 最大 126716 ms
理想值 = 60000 + 隧道开销 ≈ 61000
```
**分布是双峰的**:
| 档位 | 次数 | 说明 |
|---|---|---|
| 61 秒 | 26 | 正常,开销只有 1~1.5 秒 |
| 126 秒 | 3 | 约 2 倍 hold,**全部对齐到隧道重启窗口** |
对照三次异常的时间线:
```
#1 17:30:42 开始 -> 17:31:40 watchdog 重启两个实例 -> 126604ms
#10 17:40:59 开始 -> 17:41:56 公网 000、17:42:46 重启 h2 -> 126716ms
#18 17:50:17 开始 -> 17:50:42~17:51:25 连续三次不通 -> 126363ms
#22 17:55:28 开始 -> 17:57:11 重启两个实例 -> 超时 130001ms
```
**结论:稳态下长轮询极其干净(61~62 秒,抖动 < 2 秒);
126 秒那种是隧道被重启打断造成的,不是协议或中转的问题。**
### 4.2 设备侧公网可用率(2 小时长测,每 15 秒一次)
数据见第 4.4 节的最终汇总。
### 4.3 PC 侧(真实用户视角)
PC(WiFi/宽带)发起,只有"边缘 -> 隧道 -> 设备 4G"这一跳是慢的。
这与真实用户的情况一致(用户也是好网络)。
### 4.4 待 2 小时长测结束后填入
## 五、受链路物理条件限制、做不到 100% 的部分
+43
View File
@@ -86,3 +86,46 @@ time code ms
2026-10-07 17:54:08 200 844
2026-10-07 17:54:24 200 775
2026-10-07 17:54:40 200 830
2026-10-07 17:54:56 200 849
2026-10-07 17:55:12 200 1329
2026-10-07 17:55:28 200 836
2026-10-07 17:55:44 200 779
2026-10-07 17:56:00 200 884
2026-10-07 17:56:57 0 42093
2026-10-07 17:57:13 502 674
2026-10-07 17:57:28 200 756
2026-10-07 17:57:44 200 700
2026-10-07 17:58:00 200 831
2026-10-07 17:58:16 200 1551
2026-10-07 17:58:57 0 25818
2026-10-07 17:59:13 200 794
2026-10-07 17:59:29 200 880
2026-10-07 17:59:45 200 792
2026-10-07 18:00:02 200 1894
2026-10-07 18:00:39 200 22561
2026-10-07 18:01:17 200 22565
2026-10-07 18:01:54 200 22281
2026-10-07 18:02:31 200 22183
2026-10-07 18:03:02 200 16014
2026-10-07 18:03:18 200 890
2026-10-07 18:03:34 200 805
2026-10-07 18:03:50 200 844
2026-10-07 18:04:06 200 851
2026-10-07 18:04:21 200 688
2026-10-07 18:04:37 200 920
2026-10-07 18:04:53 200 908
2026-10-07 18:05:09 200 841
2026-10-07 18:05:25 200 896
2026-10-07 18:05:41 200 1008
2026-10-07 18:05:56 200 654
2026-10-07 18:06:12 200 829
2026-10-07 18:06:28 200 777
2026-10-07 18:06:44 200 866
2026-10-07 18:07:00 200 1003
2026-10-07 18:07:16 200 815
2026-10-07 18:07:32 200 748
2026-10-07 18:07:47 200 802
2026-10-07 18:08:04 200 1272
2026-10-07 18:08:20 200 1260
2026-10-07 18:08:36 200 1009
2026-10-07 18:08:52 200 1104
1 time code ms
86 2026-10-07 17:54:08 200 844
87 2026-10-07 17:54:24 200 775
88 2026-10-07 17:54:40 200 830
89 2026-10-07 17:54:56 200 849
90 2026-10-07 17:55:12 200 1329
91 2026-10-07 17:55:28 200 836
92 2026-10-07 17:55:44 200 779
93 2026-10-07 17:56:00 200 884
94 2026-10-07 17:56:57 0 42093
95 2026-10-07 17:57:13 502 674
96 2026-10-07 17:57:28 200 756
97 2026-10-07 17:57:44 200 700
98 2026-10-07 17:58:00 200 831
99 2026-10-07 17:58:16 200 1551
100 2026-10-07 17:58:57 0 25818
101 2026-10-07 17:59:13 200 794
102 2026-10-07 17:59:29 200 880
103 2026-10-07 17:59:45 200 792
104 2026-10-07 18:00:02 200 1894
105 2026-10-07 18:00:39 200 22561
106 2026-10-07 18:01:17 200 22565
107 2026-10-07 18:01:54 200 22281
108 2026-10-07 18:02:31 200 22183
109 2026-10-07 18:03:02 200 16014
110 2026-10-07 18:03:18 200 890
111 2026-10-07 18:03:34 200 805
112 2026-10-07 18:03:50 200 844
113 2026-10-07 18:04:06 200 851
114 2026-10-07 18:04:21 200 688
115 2026-10-07 18:04:37 200 920
116 2026-10-07 18:04:53 200 908
117 2026-10-07 18:05:09 200 841
118 2026-10-07 18:05:25 200 896
119 2026-10-07 18:05:41 200 1008
120 2026-10-07 18:05:56 200 654
121 2026-10-07 18:06:12 200 829
122 2026-10-07 18:06:28 200 777
123 2026-10-07 18:06:44 200 866
124 2026-10-07 18:07:00 200 1003
125 2026-10-07 18:07:16 200 815
126 2026-10-07 18:07:32 200 748
127 2026-10-07 18:07:47 200 802
128 2026-10-07 18:08:04 200 1272
129 2026-10-07 18:08:20 200 1260
130 2026-10-07 18:08:36 200 1009
131 2026-10-07 18:08:52 200 1104
+48
View File
@@ -0,0 +1,48 @@
#!/bin/bash
# 分析"每 9 分钟一次异常"的周期性 —— 到底是谁在定期动手
set -u
echo "════════ 1. 异常轮询的时刻 ════════"
awk -F'\t' '$3>90000 || $2!="200" {print " #" $1 " " $4 " 码=" $2 " " $3 "ms"}' \
/root/rp_poll30.tsv 2>/dev/null
echo
echo "════════ 2. 连接数掉到 3 以下的时刻(全量)════════"
awk -F'\t' 'NR>1 && $2 ~ /^[0-9]+$/ && $3 ~ /^[0-9]+$/ && ($2+$3)<4 {
print " " $1 " quic=" $2 " http2=" $3
}' /root/long2h_conns.tsv 2>/dev/null
echo
echo "════════ 3. 这些时刻前后 watchdog 做了什么 ════════"
tail -40 /root/lanremote-relay/watchdog.log 2>/dev/null | sed 's/^/ /'
echo
echo "════════ 4. QUIC 实例的连接事件(最近 40 条)════════"
grep -E 'Registered|Lost|Unregistered' /root/lanremote-relay/tunnel.log 2>/dev/null \
| tail -40 | sed -E 's/^(....-..-..T..:..):[0-9]+Z .*(Registered tunnel connection|Lost connection|Unregistered).*ip=([0-9.]+).*location=([a-z0-9]+).*/\1 \2 \3 \4/' \
| sed 's/^/ /'
echo
echo "════════ 5. 有没有周期性(看事件间隔)════════"
echo " 最近 20 次 Lost connection 的时间:"
grep 'Lost connection' /root/lanremote-relay/tunnel.log 2>/dev/null \
| tail -20 | cut -c1-19 | sed 's/^/ /'
echo
echo "════════ 6. 线程/进程层面:cloudflared 有没有重启过 ════════"
echo " QUIC 实例进程启动时间: $(ps -o lstart= -p $(pgrep -f 'protocol quic' | head -1) 2>/dev/null)"
echo " HTTP/2 实例进程启动时间: $(ps -o lstart= -p $(pgrep -f 'protocol http2' | head -1) 2>/dev/null)"
echo " QUIC 服务重启次数(本次开机以来):"
journalctl -u lanremote-tunnel --since '2 hours ago' --no-pager 2>/dev/null \
| grep -c 'Starting\|Stopped' | sed 's/^/ /'
echo
echo "════════ 7. 系统层面有没有定期任务 ════════"
echo " systemd timer:"
systemctl list-timers --no-pager 2>/dev/null | head -12 | sed 's/^/ /'
echo " cron:"
crontab -l 2>/dev/null | grep -v '^#' | sed 's/^/ /' || echo " (空)"
echo
echo "════════ 8. 连接数变化明细(最近 30 条采样)════════"
tail -30 /root/long2h_conns.tsv 2>/dev/null | sed 's/^/ /'
+48
View File
@@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
r'''等 2 小时长测跑完,中途定期报告进度。
用法: python _build/rp_wait.py [总等待秒数] [检查间隔秒数]
'''
from __future__ import annotations
import sys
import time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from devssh import run # noqa: E402
def once(label: str) -> None:
out = run("bash /root/rppg.sh 2>&1", timeout=300)[1]
print(" ────── %s ──────" % label)
# 只留关键行
for line in out.splitlines():
s = line.strip()
if not s:
continue
if any(k in s for k in ("样本数", "已完成", "状态码", "最近 3 条",
"非 200", "时间", "测试进程", "rp_long2h",
"rpp30", "200", "000", "502", "530",
"quic=", "http2=", "明细", "输出日志")):
print(" " + s)
print()
def main() -> int:
total = int(sys.argv[1]) if len(sys.argv) > 1 else 3600
every = int(sys.argv[2]) if len(sys.argv) > 2 else 900
waited = 0
n = 0
while waited < total:
step = min(every, total - waited)
time.sleep(step)
waited += step
n += 1
once("检查点 %d(已等 %d 分钟)" % (n, waited // 60))
return 0
if __name__ == "__main__":
sys.exit(main())
+51
View File
@@ -0,0 +1,51 @@
#!/bin/bash
# 看 watchdog 到底按什么节奏在重启,以及是不是过度重启
set -u
echo "════════ 1. watchdog 最近 30 行 ════════"
tail -30 /root/lanremote-relay/watchdog.log 2>/dev/null | sed 's/^/ /'
echo
echo "════════ 2. 本次开机以来所有"重启隧道"记录 ════════"
grep -E '重启 \[' /root/lanremote-relay/watchdog.log 2>/dev/null | tail -25 | sed 's/^/ /'
echo
echo "════════ 3. 重启的间隔(判断是不是过频)════════"
grep -E '重启 \[' /root/lanremote-relay/watchdog.log 2>/dev/null \
| grep -oE '^\[[0-9-]+ [0-9:]+' | sed 's/\[//' \
| awk '{
cmd = "date -d \"" $0 "\" +%s"
cmd | getline ts; close(cmd)
if (prev) printf " %s (+%d 秒)\n", $0, ts - prev
else printf " %s\n", $0
prev = ts
}' | tail -25
echo
echo "════════ 4. 每次重启的原因分类 ════════"
grep -E '重启 \[' /root/lanremote-relay/watchdog.log 2>/dev/null \
| sed -E 's/^\[[0-9-]+ [0-9:]+\] //' \
| sed -E 's/(距上次 [0-9]+ 秒)//' \
| sed -E 's/\|/ | /g' \
| awk -F'——' '{print $1}' | sed 's/[0-9]\+ 次/N 次/' \
| sort | uniq -c | sort -rn | head -12 | sed 's/^/ /'
echo
echo "════════ 5. 服务重启计数(systemd 层面)════════"
for svc in lanremote-tunnel lanremote-tunnel-h2; do
n=$(journalctl -u "$svc" --since '3 hours ago' --no-pager 2>/dev/null | grep -c 'Starting')
echo " $svc 最近 3 小时启动 $n 次"
done
echo
echo "════════ 6. 最近 1 小时的完整 watchdog 活动 ════════"
awk '/\[10-07 (1[78]):/' /root/lanremote-relay/watchdog.log 2>/dev/null | tail -25 | sed 's/^/ /'
echo
echo "════════ 7. 当前状态 ════════"
echo " watchdog: $(systemctl is-active lanremote-watchdog.timer)"
echo " 状态文件: $(cat /root/lanremote-relay/watchdog.state 2>/dev/null | tr '\n' ' ')"
echo " 上次重启: $(date -d @$(cat /root/lanremote-relay/watchdog.last_restart 2>/dev/null || echo 0) '+%Y-%m-%d %H:%M:%S' 2>/dev/null)"
echo " 现在: $(date '+%Y-%m-%d %H:%M:%S')"
echo " 连接数: quic=$(curl -s --max-time 4 http://127.0.0.1:20241/metrics 2>/dev/null | grep ha_connections | awk '{print $2}') http2=$(curl -s --max-time 4 http://127.0.0.1:20242/metrics 2>/dev/null | grep ha_connections | awk '{print $2}')"
echo " 长测进度: 健康 $(wc -l < /root/long2h_health.tsv) 条, 轮询 $(wc -l < /root/rp_poll30.tsv) / 30"
+33 -17
View File
@@ -46,16 +46,28 @@ echo " bash: $(bash --version | head -1)"
# ────────────────────────────────────────────────────────────
echo
echo "── 0. 先记录线上现状(测完要确认没被碰过)──"
LIVE_RELAY_ACTIVE=$(systemctl is-active lanremote-relay 2>/dev/null || echo "?")
LIVE_TUNNEL_ACTIVE=$(systemctl is-active lanremote-tunnel 2>/dev/null || echo "?")
# ⚠ 必须用**绝对路径**抓真实命令。
# 后面会把桩目录插到 PATH 最前面,那时 `systemctl` / `iptables`
# 就都变成桩了;如果第 8 节还按名字调用,比的就是
# "真实系统 vs 桩",必然对不上(真踩过)。
REAL_SYSTEMCTL=$(command -v systemctl 2>/dev/null || echo systemctl)
REAL_IPTABLES=$(command -v iptables 2>/dev/null || echo iptables)
echo " 真实 systemctl: $REAL_SYSTEMCTL"
echo " 真实 iptables: $REAL_IPTABLES"
LIVE_RELAY_ACTIVE=$("$REAL_SYSTEMCTL" is-active lanremote-relay 2>/dev/null | head -1 || echo "?")
LIVE_TUNNEL_ACTIVE=$("$REAL_SYSTEMCTL" is-active lanremote-tunnel 2>/dev/null | head -1 || echo "?")
LIVE_TUNNEL2_ACTIVE=$("$REAL_SYSTEMCTL" is-active lanremote-tunnel-h2 2>/dev/null | head -1 || echo "?")
LIVE_MTU=$(cat /sys/class/net/wlan0/mtu 2>/dev/null || echo "?")
LIVE_MSS=$(iptables -t mangle -S OUTPUT 2>/dev/null | grep -c TCPMSS || echo 0)
LIVE_MSS=$(("$REAL_IPTABLES" -t mangle -S OUTPUT 2>/dev/null || true) | grep -c TCPMSS | head -1)
LIVE_MSS=${LIVE_MSS:-0}
LIVE_KEEPALIVE=$(cat /proc/sys/net/ipv4/tcp_keepalive_time 2>/dev/null || echo "?")
echo " lanremote-relay = $LIVE_RELAY_ACTIVE"
echo " lanremote-tunnel = $LIVE_TUNNEL_ACTIVE"
echo " wlan0 MTU = $LIVE_MTU"
echo " MSS 规则数 = $LIVE_MSS"
echo " keepalive_time = $LIVE_KEEPALIVE"
echo " lanremote-relay = $LIVE_RELAY_ACTIVE"
echo " lanremote-tunnel = $LIVE_TUNNEL_ACTIVE"
echo " lanremote-tunnel-h2 = $LIVE_TUNNEL2_ACTIVE"
echo " wlan0 MTU = $LIVE_MTU"
echo " MSS 规则数 = $LIVE_MSS"
echo " keepalive_time = $LIVE_KEEPALIVE"
# ────────────────────────────────────────────────────────────
echo
@@ -258,17 +270,21 @@ fi
# ────────────────────────────────────────────────────────────
echo
echo "── 8. 确认线上环境一点没被碰 ──"
NOW_RELAY=$(systemctl is-active lanremote-relay 2>/dev/null || echo "?")
NOW_TUNNEL=$(systemctl is-active lanremote-tunnel 2>/dev/null || echo "?")
echo "── 8. 确认线上环境一点没被碰(用真实命令查,不用桩)──"
NOW_RELAY=$("$REAL_SYSTEMCTL" is-active lanremote-relay 2>/dev/null | head -1 || echo "?")
NOW_TUNNEL=$("$REAL_SYSTEMCTL" is-active lanremote-tunnel 2>/dev/null | head -1 || echo "?")
NOW_TUNNEL2=$("$REAL_SYSTEMCTL" is-active lanremote-tunnel-h2 2>/dev/null | head -1 || echo "?")
NOW_MTU=$(cat /sys/class/net/wlan0/mtu 2>/dev/null || echo "?")
NOW_MSS=$(iptables -t mangle -S OUTPUT 2>/dev/null | grep -c TCPMSS || echo 0)
NOW_MSS=$(("$REAL_IPTABLES" -t mangle -S OUTPUT 2>/dev/null || true) | grep -c TCPMSS | head -1)
NOW_MSS=${NOW_MSS:-0}
NOW_KA=$(cat /proc/sys/net/ipv4/tcp_keepalive_time 2>/dev/null || echo "?")
check "lanremote-relay 状态没变" "$NOW_RELAY" "$LIVE_RELAY_ACTIVE"
check "lanremote-tunnel 状态没变" "$NOW_TUNNEL" "$LIVE_TUNNEL_ACTIVE"
check "wlan0 MTU 没变" "$NOW_MTU" "$LIVE_MTU"
check "MSS 规则数没变" "$NOW_MSS" "$LIVE_MSS"
check "keepalive 没变" "$NOW_KA" "$LIVE_KEEPALIVE"
echo " 现在: relay=$NOW_RELAY tunnel=$NOW_TUNNEL h2=$NOW_TUNNEL2 MTU=$NOW_MTU MSS=$NOW_MSS KA=$NOW_KA"
check "lanremote-relay 状态没变" "$NOW_RELAY" "$LIVE_RELAY_ACTIVE"
check "lanremote-tunnel 状态没变" "$NOW_TUNNEL" "$LIVE_TUNNEL_ACTIVE"
check "lanremote-tunnel-h2 状态没变" "$NOW_TUNNEL2" "$LIVE_TUNNEL2_ACTIVE"
check "wlan0 MTU 没变" "$NOW_MTU" "$LIVE_MTU"
check "MSS 规则数没变" "$NOW_MSS" "$LIVE_MSS"
check "keepalive 没变" "$NOW_KA" "$LIVE_KEEPALIVE"
# ────────────────────────────────────────────────────────────
echo
+31
View File
@@ -572,6 +572,37 @@ check "非法协议退出码为 2" "$?" "2"
bash "$DEPLOY" --prefix "$SANDBOX" --protocol both >/dev/null 2>&1
check "both 是合法值" "$?" "0"
echo
echo "── 7g. --port 换端口时 config.yml 必须跟着改 ──"
# 只改 unit 里的 RELAY_PORT、不改 config.yml 的话,
# 隧道会回源到老端口(没人听),现象是"隧道连上了但一访问就 502"。
if grep -q '127.0.0.1:8080' "$CFD/config.yml" 2>/dev/null; then
ok "换端口前 config.yml 是 8080"
else
bad "换端口前的基准不对"
fi
: > "$STUB_LOG"
printf 'lanremote-relay\n' > "$STUB_LOG.state"
bash "$DEPLOY" --prefix "$SANDBOX" --port 9090 > "$TMP/run7g.log" 2>&1
check "--port 9090 退出码为 0" "$?" "0"
if grep -q '127.0.0.1:9090' "$CFD/config.yml" 2>/dev/null; then
ok "config.yml 的回源端口跟到了 9090"
else
bad "config.yml 没跟着换端口(隧道会回源到错端口): $(grep -m1 'service:' "$CFD/config.yml")"
fi
if grep -q 'RELAY_PORT=9090' "$UNIT/lanremote-relay.service" 2>/dev/null; then
ok "relay unit 的端口也换成了 9090"
else
bad "relay unit 端口没换"
fi
# 换回 8080,后面的断言还要用
bash "$DEPLOY" --prefix "$SANDBOX" --port 8080 >/dev/null 2>&1
if grep -q '127.0.0.1:8080' "$CFD/config.yml" 2>/dev/null; then
ok "换回 8080 也跟上了"
else
bad "换回 8080 没跟上"
fi
echo
echo "── 7b. 再跑一次(不给 --hostname),config.yml 不该被改 ──"
H3=$(md5sum "$CFD/config.yml" | awk '{print $1}')
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

+16 -5
View File
@@ -49,11 +49,22 @@ def main() -> int:
check("OUTPUT 链上夹", "OUTPUT" in text)
check("POSTROUTING 链上夹", "POSTROUTING" in text)
# 关键:明确写了不要改接口 MTU,并且确实把接口 MTU 设回 1500
check("不把接口 MTU 调小(写回了 1500)",
"mtu 1500" in text, "接口 MTU 降到 1400 实测会掉到 50%")
check("注释里说明了为什么不动接口 MTU",
"不要改接口 MTU" in text or "反而" in text)
# ⚠ 这里原来断言的是"把接口 MTU 设回 1500",
# 依据是更早的一次实测(接口 MTU 降到 1400 会掉到 50%)。
#
# 那个结论后来被推翻了,原因是我们当时漏了一件事:
# **MSS 钳制只管 TCP,而 QUIC 走 UDP 完全不受保护。**
# 路径 MTU 实测 1472(1500 的包不通),QUIC 默认发 ~1452 的包
# 会被中间设备静默丢掉,而 UDP 丢了不回 ICMP,只能等超时。
# 把接口 MTU 压到 1400 之后,QUIC 建齐 10 条连接从 28 秒缩到 12 秒。
#
# 所以现在**故意**设 1400。这个断言跟着改,
# 否则它会永久假失败,还会诱导后人把真正的改进改回 1500。
check("把接口 MTU 压到 1400(QUIC 走 UDP,MSS 钳制管不到它)",
"mtu 1400" in text or "MTU_VALUE" in text,
"接口 MTU 要压到 1400,否则 QUIC 的大包会被静默丢弃")
check("注释里说明了为什么改接口 MTU(QUIC 不受 MSS 保护)",
"QUIC" in text or "UDP" in text or "MSS 钳制" in text)
# 清理旧规则,避免重启后越积越多
check("会先清掉旧 MSS 规则", "-D" in text and "while" in text)
+35 -14
View File
@@ -162,7 +162,7 @@
========================================================
软件名称:ykxmz
版本:1.7.1
版本:1.8.2
作者:小麦子
请你**在安装之前**花三分钟读完这一页。里面有几条很要紧的
@@ -186,6 +186,10 @@ ykxmz 是一套自己搭建的远程管理工具,由三部分组成:
**关键点:所有数据都走你自己搭的那台服务器,
作者碰不到你的任何内容。** 详见《许可协议》第五条。
**再强调一次:软件里不带任何中转地址,作者也不对外提供中转服务。**
只在同一个 WiFi 下用的话,压根不需要中转站,装完就能连;
要跨网络就必须自己部署一台(看下面第四节的第 1 步)。
--------------------------------------------------------
二、装机前对照检查
@@ -195,7 +199,7 @@ ykxmz 是一套自己搭建的远程管理工具,由三部分组成:
[ ] 这台电脑是**你自己的**,或者设备主人明确同意你远程管理
[ ] 电脑是 Windows 10 / 11(64 位)
[ ] 手表是安卓 9.0(API 28)或更高版本
[ ] 手表是安卓 7.0(API 24)或更高版本
[ ] 服务器端那台机器是 Linux(Debian 12 实测通过),
能上外网,内存 512MB 够用
[ ] 你有一个 Cloudflare 账号和一个域名(跨网络才需要)
@@ -243,19 +247,27 @@ ykxmz 是一套自己搭建的远程管理工具,由三部分组成:
按这个顺序来,别跳:
第 1 步 架服务器端(只做一次)
把 2-server 整个目录拷到随身 WiFi 上,执行:
bash install.sh
它会装好中转服务 + Cloudflare 隧道,并打印一个
公网地址。
第 1 步 架服务器端(只做一次,**跨网络才需要**)
如果只在同一个 WiFi 下用(手表和电脑连同一个路由器),
这一步可以跳过。
要跨网络的话,把 2-server 整个目录拷到那台 Linux 小机器上:
bash deploy.sh
它会把中转服务、网络调优、Cloudflare 隧道一次装好,
最后打印一个**属于你自己的公网地址**。记住它。
第 2 步 装被控端(每台要管理的电脑做一次)
双击 ykxmz.exe -> 输 6 位配对码 -> 勾上开机自启和
双击安装包 -> 输 6 位配对码 -> 勾上开机自启和
放行防火墙 -> 确定。会弹一次管理员确认。
第 3 步 装控制端
把 3-watch 里的 APK 装到手表上。装完打开就能用,
地址已经内置好了。
把 3-watch 里的 APK 装到手表上。
第 4 步 跨网络的话,把中转地址填上
**注意:软件里没有内置任何中转地址。**
同一个 WiFi 下用不用填;跨网络的话必须填你自己第 1 步
装出来的那个地址:
电脑: ykxmz.exe --relay https://你的域名
手表: 首页点「中转设置」,填同一个地址
--------------------------------------------------------
@@ -311,7 +323,7 @@ ykxmz 是一套自己搭建的远程管理工具,由三部分组成:
--------------------------------------------------------
作者:小麦子
软件:ykxmz 1.7.1
软件:ykxmz 1.8.2
继续安装,即表示你已阅读并同意《许可协议》。
--------------------------------------------------------
@@ -322,7 +334,7 @@ ykxmz 是一套自己搭建的远程管理工具,由三部分组成:
========================================================
软件名称:ykxmz
版本:1.7.1
版本:1.8.2
作者:小麦子
装好之后看这一页:怎么确认它在跑、怎么改设置、
@@ -516,9 +528,18 @@ ykxmz 是一套自己搭建的远程管理工具,由三部分组成:
八、版本记录
--------------------------------------------------------
1.8.2
* 下载改走 Cloudflare 边缘缓存 —— 安装包和 APK 快了几十倍
* 跨网络更稳:隧道**两种协议同时跑**(QUIC + HTTP/2,
合计 10 条连接),一种协议犯病另一种还在服务
* 修网卡 MTU 和 QUIC 不匹配(QUIC 走 UDP,之前不受 MSS 钳制保护),
隧道建连明显加快
* 修偶发"已断开"误报:连续失败 3 次才判离线
* **中转地址默认为空** —— 需要跨网络时自己填
1.7.1
* 跨网络支持:Cloudflare 具名隧道,固定域名
* 中转服务器:单设备单配对码、管理页可删设备
* 中转服务器:单设备单配对码
* 手表端:自动判断局域网/中转并无缝切换
* 被控端:所有开关和画面功能支持回退/撤销
* 配对码被占用时弹通知提示
@@ -528,6 +549,6 @@ ykxmz 是一套自己搭建的远程管理工具,由三部分组成:
--------------------------------------------------------
作者:小麦子
软件:ykxmz 1.7.1
软件:ykxmz 1.8.2
许可条款见《许可协议》(LICENSE.txt)
--------------------------------------------------------
+21 -3
View File
@@ -675,9 +675,15 @@ else
# 域名:命令行 > 已有 config.yml > 猜不出来就报错
CUR_HOST=""
CUR_PORT=""
if [ -f "$CF_DIR/config.yml" ]; then
CUR_HOST=$(grep -m1 '^ *- hostname:' "$CF_DIR/config.yml" 2>/dev/null \
| sed 's/.*hostname: *//' | tr -d '"'"'"' ')
# 已有配置里的回源端口 —— 单独用 --port 换端口时,
# config.yml 还指着老端口的话隧道就回源到没人听的端口,
# 现象是"隧道连上了但一访问就 502",很难查。
CUR_PORT=$(grep -m1 'service: *http://127\.0\.0\.1:' "$CF_DIR/config.yml" 2>/dev/null \
| sed 's|.*127\.0\.0\.1:||' | tr -dc '0-9')
fi
HOST="$HOSTNAME_ARG"
[ -z "$HOST" ] && HOST="$CUR_HOST"
@@ -710,9 +716,21 @@ else
say " 中转已经装好并启动了 —— **局域网内现在就能用**。"
say " 只是跨网络(走公网)还差这一步域名。"
else
# config.yml 只在「不存在」或「用户明确给了 --hostname」时才重写,
# 免得把别人手工调过的配置冲掉。
if [ ! -f "$CF_DIR/config.yml" ] || [ -n "$HOSTNAME_ARG" ]; then
# config.yml 的重写条件,三种情况:
# 1. 本来就没有 —— 得生成一份
# 2. 用户明确给了 --hostname —— 要按新域名改
# 3. 已有配置里的回源端口和当前 $PORT 不一致
# (单独跑 --port 9090 时,若不改这一行,
# 隧道还是回源到 8080,现象是"隧道通了但一访问就 502")
# 其他情况一律沿用,免得把别人手工调过的配置冲掉。
NEED_WRITE=0
[ ! -f "$CF_DIR/config.yml" ] && NEED_WRITE=1
[ -n "$HOSTNAME_ARG" ] && NEED_WRITE=1
if [ -n "$CUR_PORT" ] && [ "$CUR_PORT" != "$PORT" ]; then
NEED_WRITE=1
say " 已有 config.yml 指向端口 $CUR_PORT,当前是 $PORT —— 会一并更新"
fi
if [ "$NEED_WRITE" = "1" ]; then
cat > "$CF_DIR/config.yml" <<EOF
# ykxmz 中转 · Cloudflare 具名隧道配置
# 由 deploy.sh 生成。改完记得 systemctl restart lanremote-tunnel