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 分钟对照确认。
124 lines
4.9 KiB
Python
124 lines
4.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
r'''手表:过期 IP 给提示 + 跳过注定失败的直连尝试。
|
|
|
|
用户遇到的情况(截图):
|
|
首页「已配对的电脑」显示 DESKTOP-RN5I625
|
|
192.168.2.64:8765
|
|
但那台电脑早就换网段了(现在是 192.168.10.103)。
|
|
点一下 -> 先花 6 秒试直连(必然失败)-> 再走中转 40 秒 -> 用户以为卡死。
|
|
|
|
改两处:
|
|
|
|
1. refreshDeviceList:保存的 IP 如果和手表不在同一个网段,
|
|
按钮上直接标出来"(地址已过期)"。用户一眼就知道该重新搜。
|
|
|
|
2. quickConnect:如果保存的 IP 明显不同网段,
|
|
**跳过直连**,直接走中转 —— 省掉那 6 秒白等。
|
|
中转连上后会报回电脑的最新局域网地址,记录也就自动刷新了。
|
|
'''
|
|
from __future__ import annotations
|
|
|
|
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_LIST = """ Button button = bigButton(label + "\\n" + host + ":" + port, C_CARD, 14);"""
|
|
|
|
NEW_LIST = """ // 保存的地址可能已经过期(电脑换了网络/网段)。
|
|
// 和手表不在同一网段时直接在按钮上标出来 ——
|
|
// 不然用户点下去只会等 6 秒直连失败 + 40 秒中转超时,
|
|
// 完全不知道问题出在哪儿。
|
|
String stale = "";
|
|
if (!host.isEmpty() && !hostInMySubnet(host)) {
|
|
stale = " ⚠ 地址可能已过期";
|
|
}
|
|
Button button = bigButton(label + "\\n" + host + ":" + port + stale,
|
|
C_CARD, 14);"""
|
|
|
|
# ---------------------------------------------------------------- 2. 跳过死路
|
|
OLD_QUICK = """ private void quickConnect(String host, int port, String token, String key) {
|
|
// preferLan = true:先按记录里的地址直连。
|
|
// 记录里的地址可能是旧的(换过网络),直连会失败 ——
|
|
// 那时自动退回中转,中转会报回电脑的最新局域网地址并刷新记录。
|
|
connect(host, port, null, token, key, true);
|
|
}"""
|
|
|
|
NEW_QUICK = """ private void quickConnect(String host, int port, String token, String key) {
|
|
// 先判断这个保存下来的地址还有没有意义。
|
|
// 如果它和手表明显不在同一网段(电脑换网络了),
|
|
// 直连必然失败 —— 那就别浪费那 6 秒,直接走中转。
|
|
// 中转连上后会报回电脑的最新局域网地址,记录自动刷新。
|
|
boolean sameNet = host.isEmpty() || hostInMySubnet(host);
|
|
if (!sameNet) {
|
|
appendLog("记录的地址 " + host + " 和手表不在同一网段"
|
|
+ "(电脑可能换了网络),直接走中转查最新地址");
|
|
toast("电脑地址已过期,正在通过中转查询最新地址…");
|
|
}
|
|
// preferLan = true 时先按记录里的地址直连,失败自动退回中转。
|
|
connect(host, port, null, token, key, sameNet);
|
|
}
|
|
|
|
/**
|
|
* host 是不是和手表在同一个 /24 网段。
|
|
*
|
|
* <p>读不到手表自己的网卡时返回 true(保守:宁可按原地址试一次,
|
|
* 也不要因为拿不到权限就误判成"过期")。
|
|
*/
|
|
private boolean hostInMySubnet(String host) {
|
|
if (host == null || host.isEmpty()) {
|
|
return true;
|
|
}
|
|
java.util.List<String> mine = localIpv4List();
|
|
if (mine.isEmpty()) {
|
|
return true;
|
|
}
|
|
for (String myIp : mine) {
|
|
if (sameSubnet(host, myIp)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}"""
|
|
|
|
|
|
def patch(text: str, old: str, new: str, label: str) -> tuple:
|
|
if old not in text:
|
|
print(" ✗ 没找到锚点:%s" % label)
|
|
return text, False
|
|
if text.count(old) != 1:
|
|
print(" ! 锚点出现 %d 次:%s" % (text.count(old), label))
|
|
return text, False
|
|
print(" ✓ %s" % label)
|
|
return text.replace(old, new, 1), True
|
|
|
|
|
|
def main() -> int:
|
|
text = SRC.read_text(encoding="utf-8")
|
|
before = text.count("\n") + 1
|
|
|
|
print(" 打补丁:")
|
|
ok_all = True
|
|
for old, new, label in (
|
|
(OLD_LIST, NEW_LIST, "设备列表标出过期地址"),
|
|
(OLD_QUICK, NEW_QUICK, "quickConnect 跳过注定失败的直连"),
|
|
):
|
|
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 行" % (before, text.count("\n") + 1))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|