mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 23:00:44 +00:00
用户报两个问题:
1. 局域网能搜到电脑,IP/端口/配对码都对,点连接**没有任何反应**
2. 走中转 1426ms 延时,一会儿就 502
========== 一、局域网被劫持(真 bug)==========
MainActivity.connect() 里:
boolean usedLan = !relayUrl().isEmpty() ? false : true;
if (!relay.isEmpty()) {
reply = client.connectViaRelay(...); // 走中转
} else {
reply = client.connect(host, port, ...); // 才走局域网
}
**只看「中转设置里填没填地址」,填了就一律走中转,
完全无视用户填的局域网 IP 和端口。**
而中转单次超时是 40 秒 —— 用户点完就卡在「正在连接…」
四十秒,在他看来就是"没有任何反应"。
影响两条路:
* 「局域网连接」页面填 IP 直连(connectLanTyped)
* 首页「已配对的电脑」点一下重连(quickConnect)
修法:给 connect() 加 preferLan 参数。明确要走局域网时
**先直连(6 秒超时,局域网本该是毫秒级),失败再退回中转**,
保证最终能连上。
顺带两个体验改进:
* 设备列表里,保存的 IP 和手表不同网段时标「⚠ 地址可能已过期」
(用户截图里那个 192.168.2.64 就是过期地址,电脑早换成
192.168.10.103 了)
* quickConnect 判断出地址过期就跳过直连,直接走中转查最新地址 ——
省掉那 6 秒白等
========== 二、长轮询 15 秒 -> 60 秒 ==========
实测隧道每次请求要多花 1~2 秒(CF 边缘路由 + 回源):
本机直连 60 秒的 poll -> 60.0s
走公网同一个 poll -> 61.5~62.0s
这个开销是**按次数**收的:
15 秒一循环 -> 一天 5760 次/端,光路费 2.4~3.2 小时
60 秒一循环 -> 一天 1440 次/端,光费 0.6~0.8 小时
而且请求越频繁,撞上丢包/重连窗口的概率越大。
**不会让命令变慢**:长轮询是"有数据立刻返回"(device.event 唤醒),
拉长只是减少空转重开。实测 60 秒长轮询穿隧道 4/4 通过;
Cloudflare 单请求上限 100 秒,留了 40 秒余量。
三层同步改(客户端超时必须显著大于服务端 hold):
* relay/relay.py RELAY_POLL 默认 25 -> 60
* windows/lanremote/relay.py POLL_TIMEOUT 40 -> 90
* android .../RelayTransport.java POLL_TIMEOUT_MS 40000 -> 90000
========== 三、A/B 实测(还在跑更严谨的一轮)==========
短测发现基线 http2+4 虽然 100% 成功,但 **P90 是 16.8 秒**
(每 10 次有 1 次卡十几秒 —— 正是用户说的"没反应"),
而 quic+6 的 P90 只有 2.8 秒。正在用真实长轮询模式
跑 10 分钟对照确认。
173 lines
7.0 KiB
Python
173 lines
7.0 KiB
Python
# -*- coding: utf-8 -*-
|
||
r'''修「局域网连接被中转劫持」的 bug。
|
||
|
||
用户报的现象(手表截图):
|
||
局域网能搜到电脑,IP / 端口 / 配对码都填对了,
|
||
点连接**没有任何反应**,一点反应都没有。
|
||
|
||
根因(MainActivity.java 的 connect()):
|
||
|
||
boolean usedLan = !relayUrl().isEmpty() ? false : true;
|
||
if (!relay.isEmpty()) {
|
||
reply = client.connectViaRelay(...); // ← 走中转
|
||
} else {
|
||
reply = client.connect(host, port, ...); // ← 才走局域网
|
||
}
|
||
|
||
**只看「中转设置里填没填地址」,填了就一律走中转,
|
||
完全无视用户填的局域网 IP 和端口。**
|
||
|
||
而中转超时是 40 秒(40000ms)——
|
||
于是用户点完之后界面卡在「正在连接…」四十秒,
|
||
在他看来就是"没有任何反应"。
|
||
|
||
这个 bug 影响两条路:
|
||
1. 「局域网连接」页面填 IP 直连(connectLanTyped)
|
||
2. 首页「已配对的电脑」点一下重连(quickConnect)
|
||
|
||
修法:
|
||
给 connect() 加一个 preferLan 参数。明确要走局域网时:
|
||
先直连 host:port(超时给短一点,6 秒,局域网本来该是毫秒级)
|
||
直连不成**再退回中转**(保证最终能连上,不让用户卡死)
|
||
没有 preferLan 时保持原来的行为(走中转,连上后若同网段再切直连)。
|
||
'''
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
SRC = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote"
|
||
r"\android\src\com\lanremote\watch\MainActivity.java")
|
||
|
||
# ---------------------------------------------------------------- 1. 重载
|
||
OLD_SIG = """ private void connect(final String host, final int port, final String code,
|
||
final String token, final String key) {
|
||
if (busy) {
|
||
return;
|
||
}"""
|
||
|
||
NEW_SIG = """ private void connect(final String host, final int port, final String code,
|
||
final String token, final String key) {
|
||
connect(host, port, code, token, key, false);
|
||
}
|
||
|
||
/**
|
||
* @param preferLan 明确要求"先走局域网"。
|
||
*
|
||
* <p>用户点「局域网连接」填了 IP,或者点首页已配对电脑时,
|
||
* 应该先按他给的地址直连。原来的实现只看中转地址填没填 ——
|
||
* 填了就一律走中转,**完全无视用户填的局域网 IP**,
|
||
* 而中转超时 40 秒,用户点完只看到「正在连接…」,以为没反应。
|
||
*
|
||
* <p>设 true 时:先直连(局域网该是毫秒级,超时给 6 秒),
|
||
* 不成再退回中转,保证最终能连上。
|
||
*/
|
||
private void connect(final String host, final int port, final String code,
|
||
final String token, final String key,
|
||
final boolean preferLan) {
|
||
if (busy) {
|
||
return;
|
||
}"""
|
||
|
||
|
||
# ---------------------------------------------------------------- 2. 分支
|
||
OLD_BRANCH = """ final String relay = relayUrl();
|
||
Map<String, Object> reply;
|
||
if (!relay.isEmpty()) {"""
|
||
|
||
NEW_BRANCH = """ final String relay = relayUrl();
|
||
Map<String, Object> reply;
|
||
if (preferLan) {
|
||
// ---- 用户明确要走局域网:先直连,失败再退中转 ----
|
||
// 局域网直连本该是毫秒级,所以超时给短一点。
|
||
// 原来这里会走中转(40 秒超时),用户以为卡死了。
|
||
appendLog("局域网直连 " + host + ":" + port + " …");
|
||
reply = client.connect(host, port, code, token,
|
||
MainActivity.this, 6000);
|
||
ok = Json.bool(reply, "ok", false);
|
||
if (ok) {
|
||
usedLan = true;
|
||
} else if (!relay.isEmpty()) {
|
||
String lanWhy = Json.str(reply, "error",
|
||
"局域网直连失败");
|
||
appendLog("局域网直连不成(" + lanWhy
|
||
+ "),改走中转…");
|
||
ui.post(new Runnable() {
|
||
@Override
|
||
public void run() {
|
||
setStatus("局域网连不上,改用中转…", C_WARN);
|
||
}
|
||
});
|
||
reply = client.connectViaRelay(relay, code, token,
|
||
MainActivity.this, 40000);
|
||
usedLan = false;
|
||
}
|
||
} else if (!relay.isEmpty()) {"""
|
||
|
||
|
||
# ---------------------------------------------------------------- 3. 调用方
|
||
OLD_QUICK = """ connect(host, port, null, token, key);
|
||
}"""
|
||
|
||
NEW_QUICK = """ // preferLan = true:先按记录里的地址直连。
|
||
// 记录里的地址可能是旧的(换过网络),直连会失败 ——
|
||
// 那时自动退回中转,中转会报回电脑的最新局域网地址并刷新记录。
|
||
connect(host, port, null, token, key, true);
|
||
}"""
|
||
|
||
OLD_TYPED = """ connect(host, port, code, null,
|
||
DeviceStore.deviceKey(host, port, store.deviceId()));"""
|
||
|
||
NEW_TYPED = """ // preferLan = true:用户就是冲着"局域网直连"来的,
|
||
// 必须真的直连,不能因为有中转地址就偷偷走中转。
|
||
connect(host, port, code, null,
|
||
DeviceStore.deviceKey(host, port, store.deviceId()), true);"""
|
||
|
||
|
||
def patch(text: str, old: str, new: str, label: str) -> tuple:
|
||
if new.split("\n")[0] in text and label != "分支":
|
||
# 已经打过补丁的粗略判断
|
||
pass
|
||
if old not in text:
|
||
print(" ✗ 没找到锚点:%s" % label)
|
||
return text, False
|
||
count = text.count(old)
|
||
if count != 1:
|
||
print(" ! 锚点出现 %d 次(期望 1):%s" % (count, label))
|
||
return text, False
|
||
text = text.replace(old, new, 1)
|
||
print(" ✓ %s" % label)
|
||
return text, True
|
||
|
||
|
||
def main() -> int:
|
||
text = SRC.read_text(encoding="utf-8")
|
||
original = text
|
||
|
||
print(" 打补丁:")
|
||
ok_all = True
|
||
for old, new, label in (
|
||
(OLD_SIG, NEW_SIG, "connect() 加重载和 preferLan 参数"),
|
||
(OLD_QUICK, NEW_QUICK, "quickConnect 传 preferLan=true"),
|
||
(OLD_TYPED, NEW_TYPED, "connectLanTyped 传 preferLan=true"),
|
||
(OLD_BRANCH, NEW_BRANCH, "走局域网的分支"),
|
||
):
|
||
text, ok = patch(text, old, new, label)
|
||
ok_all = ok_all and ok
|
||
|
||
if not ok_all:
|
||
print()
|
||
print(" 有锚点没匹配,没有写回")
|
||
return 1
|
||
|
||
SRC.write_text(text, encoding="utf-8", newline="\n")
|
||
print()
|
||
print(" 已写回,文件 %d -> %d 行"
|
||
% (original.count("\n") + 1, text.count("\n") + 1))
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|