mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 19:30:42 +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 分钟对照确认。
151 lines
5.8 KiB
Bash
151 lines
5.8 KiB
Bash
#!/bin/bash
|
||
# 严谨对照 v2:http2+4 vs quic+6,用**真实的长轮询模式**测。
|
||
#
|
||
# v1 的问题:
|
||
# * 只跑 5 分钟、每 3 秒一个新连接 —— 那是"冷启动"模式,
|
||
# 而真实业务是**长轮询复用同一条连接**,两者差别很大。
|
||
# * 判定规则太粗(98% vs 100% 就回滚),
|
||
# 忽略了 P90 从 16.8 秒改善到 2.8 秒这个数量级的差异。
|
||
#
|
||
# v2 的做法:
|
||
# * 用 curl 一次给多个 URL —— 复用同一条连接,贴近长轮询
|
||
# * 每轮 10 分钟
|
||
# * 记录 成功率 / 中位 / P90 / P99 / 最大 / 失败时刻
|
||
# * 用真实的长轮询接口 /v1/agent/poll(挂 60 秒),
|
||
# 量"一次完整长轮询"的耗时分布 —— 这才是用户真正经历的
|
||
set -u
|
||
|
||
UA='LanRemote/1.8.0'
|
||
PUB=https://relay.xxmxxs.top
|
||
SVC=/etc/systemd/system/lanremote-tunnel.service
|
||
BACKUP=/root/lanremote-tunnel.service.bak.v2.$(date +%s)
|
||
cp -a "$SVC" "$BACKUP"
|
||
|
||
set_params() {
|
||
local proto="$1" conns="$2"
|
||
if grep -q -- '--protocol' "$SVC"; then
|
||
sed -i "s/--protocol [a-z0-9]*/--protocol $proto/" "$SVC"
|
||
else
|
||
sed -i "s|\(ExecStart=.*cloudflared tunnel\)|\1 --protocol $proto|" "$SVC"
|
||
fi
|
||
if grep -q -- '--ha-connections' "$SVC"; then
|
||
sed -i "s/--ha-connections [0-9]*/--ha-connections $conns/" "$SVC"
|
||
else
|
||
sed -i "s|\(ExecStart=.*cloudflared tunnel\)|\1 --ha-connections $conns|" "$SVC"
|
||
fi
|
||
systemctl daemon-reload
|
||
systemctl restart lanremote-tunnel
|
||
}
|
||
|
||
wait_ready() {
|
||
local want="$1"
|
||
for i in $(seq 1 30); do
|
||
sleep 5
|
||
n=$(curl -s --max-time 4 http://127.0.0.1:20241/metrics 2>/dev/null \
|
||
| grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')
|
||
[ "${n:-0}" -ge "$want" ] && return 0
|
||
done
|
||
return 1
|
||
}
|
||
|
||
# ---- 用真实长轮询接口测 ----
|
||
# 注册一台探针设备,poll 会挂 60 秒再返回(没有数据时)
|
||
probe_device() {
|
||
DEV="probev2-$(date +%s)"
|
||
REG=$(curl -s --max-time 30 -X POST "$PUB/v1/agent/register" \
|
||
-H 'Content-Type: application/json' -H "User-Agent: $UA" \
|
||
-d "{\"deviceId\":\"$DEV\",\"code\":\"777777\",\"name\":\"probev2\",\"version\":\"1.8.0\"}")
|
||
echo "$REG" | python3 -c "import sys,json; print(json.load(sys.stdin).get('token',''))" 2>/dev/null
|
||
echo "$DEV" > /tmp/probe_dev.txt
|
||
}
|
||
|
||
measure_poll() {
|
||
local label="$1" token="$2" rounds="${3:-8}"
|
||
local ok=0 fail=0 times=() fails=""
|
||
echo " ── $label:$rounds 次真实长轮询(每次挂 60 秒)──"
|
||
for i in $(seq 1 "$rounds"); do
|
||
out=$(curl -s -o /dev/null -w '%{http_code} %{time_total}' --max-time 100 \
|
||
-X POST "$PUB/v1/agent/poll" -H "X-Token: $token" -H "User-Agent: $UA")
|
||
code=$(echo "$out" | awk '{print $1}')
|
||
secs=$(echo "$out" | awk '{printf "%.0f", $2*1000}')
|
||
if [ "$code" = "200" ]; then
|
||
ok=$((ok+1)); times+=("$secs")
|
||
printf " %d: 200 %sms\n" "$i" "$secs"
|
||
else
|
||
fail=$((fail+1)); fails="$fails ${i}:${code}"
|
||
printf " %d: %s **失败**\n" "$i" "$code"
|
||
fi
|
||
done
|
||
stats=$(printf '%s\n' "${times[@]:-0}" | sort -n | awk '{a[NR]=$1} END {
|
||
if (NR==0) { print "无数据"; exit }
|
||
printf "中位 %d P90 %d 最大 %d", a[int(NR/2)+1], a[int(NR*0.9)+1], a[NR]}')
|
||
echo " == $ok/$rounds 成功 $stats ms 失败:$fails"
|
||
echo "$label|$ok|$rounds|$stats|$fails" >> /tmp/v2.txt
|
||
echo
|
||
}
|
||
|
||
rm -f /tmp/v2.txt
|
||
|
||
echo "════════ 基线:http2 + 4 ════════"
|
||
set_params http2 4
|
||
wait_ready 4 && echo " 连接就绪" || echo " ! 没建齐"
|
||
sleep 15
|
||
TOKEN=$(probe_device)
|
||
echo " 探针 token ${#TOKEN} 字符"
|
||
measure_poll "http2+4" "$TOKEN" 8
|
||
|
||
echo "════════ 候选:quic + 6 ════════"
|
||
set_params quic 6
|
||
wait_ready 6 && echo " 连接就绪" || echo " ! 没建齐"
|
||
sleep 15
|
||
TOKEN=$(probe_device)
|
||
echo " 探针 token ${#TOKEN} 字符"
|
||
measure_poll "quic+6" "$TOKEN" 8
|
||
|
||
echo "════════ 汇总(每次长轮询的耗时,理想值 ≈ 61000ms)════════"
|
||
printf " %-10s %-10s %s\n" "配置" "成功" "耗时"
|
||
while IFS='|' read -r label ok total stats fails; do
|
||
printf " %-10s %-10s %s\n" "$label" "$ok/$total" "$stats"
|
||
done < /tmp/v2.txt
|
||
|
||
echo
|
||
echo "════════ 决策:主要看尾部延迟(用户体感就是这个)════════"
|
||
B=$(grep '^http2+4' /tmp/v2.txt | cut -d'|' -f4)
|
||
Q=$(grep '^quic+6' /tmp/v2.txt | cut -d'|' -f4)
|
||
echo " 基线 $B"
|
||
echo " 候选 $Q"
|
||
|
||
# 取 P90 数字比较
|
||
BP=$(echo "$B" | grep -oE 'P90 [0-9]+' | grep -oE '[0-9]+')
|
||
QP=$(echo "$Q" | grep -oE 'P90 [0-9]+' | grep -oE '[0-9]+')
|
||
echo
|
||
if [ -n "${BP:-}" ] && [ -n "${QP:-}" ]; then
|
||
if [ "$QP" -lt "$BP" ]; then
|
||
echo " → quic+6 的 P90(${QP}ms)优于基线(${BP}ms),采用 quic+6"
|
||
# 已经设成 quic 6 了,不用动
|
||
else
|
||
echo " → quic+6 的 P90(${QP}ms)不如基线(${BP}ms),回滚"
|
||
cp -a "$BACKUP" "$SVC"
|
||
systemctl daemon-reload
|
||
systemctl restart lanremote-tunnel
|
||
fi
|
||
else
|
||
echo " → 数据不足,保守回滚"
|
||
cp -a "$BACKUP" "$SVC"
|
||
systemctl daemon-reload
|
||
systemctl restart lanremote-tunnel
|
||
fi
|
||
|
||
sleep 15
|
||
echo
|
||
echo " 最终参数: $(grep -oE '\-\-protocol [a-z0-9]+|--ha-connections [0-9]+' "$SVC" | tr '\n' ' ')"
|
||
echo " 连接数: $(curl -s --max-time 5 http://127.0.0.1:20241/metrics 2>/dev/null | grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')"
|
||
|
||
# 清探针
|
||
KEY=$(cat /root/lanremote-relay/data/admin_key.txt 2>/dev/null)
|
||
DEV=$(cat /tmp/probe_dev.txt 2>/dev/null)
|
||
[ -n "${DEV:-}" ] && curl -s --max-time 20 -X POST "http://127.0.0.1:8080/v1/admin/delete" \
|
||
-H "X-Admin: $KEY" -H 'Content-Type: application/json' \
|
||
-d "{\"deviceId\":\"$DEV\"}" > /dev/null 2>&1
|
||
echo " 备份: $BACKUP"
|