mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 11:20:43 +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 分钟对照确认。
139 lines
4.9 KiB
Python
139 lines
4.9 KiB
Python
# -*- coding: utf-8 -*-
|
||
r'''把长轮询从 15 秒改成 60 秒(服务端 + 两个客户端同步改)。
|
||
|
||
== 为什么这是这一轮最有效的优化 ==
|
||
|
||
隧道每次请求要多花 1~2 秒(CF 边缘路由 + 回源)。实测:
|
||
|
||
本机直连 60 秒的 poll -> 60.0s
|
||
走公网隧道同一个 poll -> 61.5~62.0s 只多 1.5~2 秒
|
||
|
||
看着不多,但**这个开销是按"次数"收的**:
|
||
|
||
15 秒一循环 -> 一天 5760 次/端 -> 光路费就 2.4~3.2 小时
|
||
60 秒一循环 -> 一天 1440 次/端 -> 光路费就 0.6~0.8 小时
|
||
|
||
而且请求越频繁,"正好撞上丢包/重连窗口"的概率越大。
|
||
拉长到 60 秒,暴露面直接降到 1/4。
|
||
|
||
== 为什么不会让命令变慢 ==
|
||
|
||
长轮询是**有数据立刻返回**(relay.py 里用 device.event 唤醒),
|
||
不是"等满 60 秒才看一眼"。拉长超时只是减少"空转重开"的次数,
|
||
真正下命令时的延迟不变。
|
||
|
||
实测 60 秒长轮询穿过隧道 4/4 成功;Cloudflare 单请求上限是 100 秒,
|
||
60 秒留了 40 秒余量。
|
||
|
||
== 要同步改的三处 ==
|
||
|
||
1. relay/relay.py POLL_SECONDS 默认 25 -> 60
|
||
2. windows/lanremote/relay.py POLL_TIMEOUT 40 -> 90
|
||
3. android .../RelayTransport.java POLL_TIMEOUT_MS 40000 -> 90000
|
||
|
||
客户端超时必须显著大于服务端 hold 时间,否则会在服务端正常返回之前
|
||
自己先超时 —— 那会变成"每次轮询都失败"的灾难。
|
||
'''
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
|
||
BOM = b"\xef\xbb\xbf"
|
||
|
||
EDITS = [
|
||
(
|
||
"relay/relay.py",
|
||
'POLL_SECONDS = float(os.environ.get("RELAY_POLL", "25"))',
|
||
'# 长轮询挂多久。
|
||
# 25 -> 60:隧道每次请求要多花 1~2 秒(CF 边缘路由 + 回源),
|
||
# 而这个开销是按"次数"收的。60 秒一循环把请求数降到 1/4,
|
||
# 暴露在丢包/重连窗口里的概率也降到 1/4。
|
||
# **不会让命令变慢**:有数据时立刻返回(靠 device.event 唤醒),
|
||
# 拉长只是减少空转重开。
|
||
# Cloudflare 单请求上限 100 秒,60 留了 40 秒余量(实测 4/4 通过)。
|
||
POLL_SECONDS = float(os.environ.get("RELAY_POLL", "60"))',
|
||
),
|
||
(
|
||
"windows/lanremote/relay.py",
|
||
"POLL_TIMEOUT = 40.0",
|
||
"# 长轮询单次请求的超时。
|
||
# 必须显著大于服务端的 hold 时间(现在 60 秒),否则会在服务端
|
||
# 正常返回之前自己先超时 —— 那就变成"每次轮询都失败"了。
|
||
# 90 = 60 + 30 秒余量。
|
||
POLL_TIMEOUT = 90.0",
|
||
),
|
||
(
|
||
"android/src/com/lanremote/watch/RelayTransport.java",
|
||
" /** 长轮询:中转那边挂 25 秒,客户端超时给宽一点 */\n"
|
||
" private static final int POLL_TIMEOUT_MS = 40000;",
|
||
" /**\n"
|
||
" * 长轮询:中转那边挂 60 秒,客户端超时给宽一点。\n"
|
||
" *\n"
|
||
" * <p>必须显著大于服务端的 hold 时间,否则会在服务端正常返回\n"
|
||
" * 之前自己先超时 —— 那就变成\"每次轮询都失败\"了。\n"
|
||
" * 90 秒 = 60 + 30 秒余量。\n"
|
||
" */\n"
|
||
" private static final int POLL_TIMEOUT_MS = 90000;",
|
||
),
|
||
]
|
||
|
||
|
||
def main() -> int:
|
||
print(" 改这三处:")
|
||
ok_all = True
|
||
for rel, old, new in EDITS:
|
||
path = ROOT / rel
|
||
if not path.is_file():
|
||
print(" ✗ %s 不存在" % rel)
|
||
ok_all = False
|
||
continue
|
||
raw = path.read_bytes()
|
||
had_bom = raw.startswith(BOM)
|
||
text = raw.decode("utf-8-sig")
|
||
|
||
if old not in text:
|
||
if new.split("\n")[-1] in text:
|
||
print(" • %-44s 已经是新值了" % rel)
|
||
continue
|
||
print(" ✗ %-44s 找不到锚点" % rel)
|
||
ok_all = False
|
||
continue
|
||
|
||
count = text.count(old)
|
||
if count != 1:
|
||
print(" ! %-44s 锚点出现 %d 次" % (rel, count))
|
||
ok_all = False
|
||
continue
|
||
|
||
text = text.replace(old, new, 1)
|
||
data = text.encode("utf-8")
|
||
if had_bom or path.suffix == ".ps1":
|
||
data = BOM + data
|
||
path.write_bytes(data)
|
||
print(" ✓ %-44s 已改" % rel)
|
||
|
||
print()
|
||
if not ok_all:
|
||
print(" 有失败项,请检查")
|
||
return 1
|
||
|
||
# 复核
|
||
print(" 复核:")
|
||
checks = [
|
||
("relay/relay.py", 'os.environ.get("RELAY_POLL", "60")', "服务端 60 秒"),
|
||
("windows/lanremote/relay.py", "POLL_TIMEOUT = 90.0", "被控端 90 秒"),
|
||
("android/src/com/lanremote/watch/RelayTransport.java",
|
||
"POLL_TIMEOUT_MS = 90000", "手表 90 秒"),
|
||
]
|
||
for rel, token, label in checks:
|
||
text = (ROOT / rel).read_text(encoding="utf-8-sig")
|
||
mark = "✓" if token in text else "✗"
|
||
print(" %s %s" % (mark, label))
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|