Files
ykxmz/_build/poll60_patch.py
T
xmz7891 b1119d0da6 修「局域网连接被中转劫持」+ 长轮询 15s→60s
用户报两个问题:
  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 分钟对照确认。
2026-10-07 14:11:33 +08:00

139 lines
4.9 KiB
Python
Raw 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'''把长轮询从 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())