mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 13:40:45 +00:00
静态资源交给 Cloudflare 边缘缓存(下载提速 41 倍)
实测基线:
ykxmz-setup-1.8.1.exe 只下到 237568/15067199 字节就停(17 KB/s)
ykxmz-watch-1.8.1.apk 201523 字节 / 66.8 秒 = 3 KB/s
ykxmz-server-1.8.1.zip 214900 字节 / 3.1 秒 = 67 KB/s
原因:relay 给所有响应都发 Cache-Control: no-store,
Cloudflare 完全不缓存 —— 每次下载/每张图都要穿隧道回 4G 源站。
改动:
/download/* no-store -> public, max-age=604800, immutable
文件名带版本号,同一名字内容永不改变,可以长缓存
/ -> public, max-age=60(挡住刷新重复回源)
/favicon.ico 已是 max-age=86400,保持不变
/shot/* 已是 max-age=86400,保持不变
/v1/* 保持 no-store(接口必须实时)
顺带修两个自己踩的坑:
1. _send() 无条件先发 no-store,再追加 extra 里的 Cache-Control,
结果是**两个 Cache-Control 头**,客户端只认第一个 ——
调用方传的缓存策略被吃掉。改成 extra 里有就不再发默认值。
2. do_HEAD 和 do_GET 的缓存头不一致:HEAD 对非 /shot/ 一律报 no-store。
下载器/代理用 HEAD 探测时会误判文件不可缓存。
现在 _head_info 给每个路由带上和 GET 完全相同的缓存头,
do_HEAD 也不再盲目覆盖。
结果(同一个测试口径):
setup.exe 17 KB/s(截断) -> 695 KB/s 完整 15 MB 41 倍
watch.apk 3 KB/s -> 101 KB/s 33 倍
server.zip 67 KB/s -> 87 KB/s
三个文件全部 cf-cache-status=HIT(不再穿隧道回源)
排查过程记一笔:我一开始用 curl -I 验证,
看到的全是 no-store —— 因为 **-I 发的是 HEAD**,
而 HEAD 走的是另一个分支。查了半天才发现是验证方法错了。
This commit is contained in:
@@ -0,0 +1,178 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# quic+6 vs http2+4:用**真实长轮询**对照,方法完全一致。
|
||||||
|
#
|
||||||
|
# 为什么必须用长轮询测:
|
||||||
|
# 之前用 /v1/health 那种"每次新建连接"的方式测,
|
||||||
|
# 量到的是冷启动开销,和真实业务(复用连接的长轮询)差很多。
|
||||||
|
# 而且那种方式会看到 18 秒的尾巴 —— 长轮询完全没有。
|
||||||
|
#
|
||||||
|
# 为什么这次结果可信:
|
||||||
|
# 上一轮 A/B 里 quic 那组因为**两次探针用了同一个配对码**,
|
||||||
|
# 第二次注册被"一个码只能一台设备"规则拒掉,token 拿到空的,
|
||||||
|
# 8 次全 401 —— 那一轮数据作废。这次每个探针都用唯一的新码。
|
||||||
|
#
|
||||||
|
# 为什么值得测 QUIC:
|
||||||
|
# 刚才查出来:隧道连接 30 分钟 0 掉线、0 重连,很稳;
|
||||||
|
# 但请求级偶发 000(10 分钟 6 次)。
|
||||||
|
# 这是 HTTP/2 over TCP 的**队头阻塞** ——
|
||||||
|
# 一条 TCP 上丢一个包,那条连接上所有流一起卡。
|
||||||
|
# QUIC 每个流独立恢复,理论上正好治这个。
|
||||||
|
set -u
|
||||||
|
|
||||||
|
PUB=https://relay.xxmxxs.top
|
||||||
|
UA='LanRemote/1.8.1'
|
||||||
|
SVC=/etc/systemd/system/lanremote-tunnel.service
|
||||||
|
BACKUP=/root/lanremote-tunnel.service.bak.q.$(date +%s)
|
||||||
|
cp -a "$SVC" "$BACKUP"
|
||||||
|
ROUNDS=${1:-10}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
new_probe() {
|
||||||
|
# 每次都用全新的 deviceId + 全新的配对码,避免被"一码一机"拒掉
|
||||||
|
local stamp
|
||||||
|
stamp=$(date +%s%N | cut -c1-13)
|
||||||
|
local dev="qprobe-${stamp}"
|
||||||
|
local code
|
||||||
|
code=$(printf "%06d" $(( (stamp / 7) % 1000000 )))
|
||||||
|
local reg
|
||||||
|
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\":\"$code\",\"name\":\"qprobe\",\"version\":\"1.8.1\"}")
|
||||||
|
local tok
|
||||||
|
tok=$(echo "$reg" | python3 -c "import sys,json; print(json.load(sys.stdin).get('token',''))" 2>/dev/null)
|
||||||
|
PROBE_DEV="$dev"
|
||||||
|
PROBE_TOK="$tok"
|
||||||
|
PROBE_CODE="$code"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup_probe() {
|
||||||
|
local key
|
||||||
|
key=$(cat /root/lanremote-relay/data/admin_key.txt 2>/dev/null)
|
||||||
|
[ -n "${PROBE_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\":\"$PROBE_DEV\"}" > /dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
run_round() {
|
||||||
|
local label="$1" proto="$2" conns="$3"
|
||||||
|
|
||||||
|
echo "════════ $label ════════"
|
||||||
|
set_params "$proto" "$conns"
|
||||||
|
if wait_ready "$conns"; then
|
||||||
|
echo " 连接就绪($(curl -s --max-time 4 http://127.0.0.1:20241/metrics 2>/dev/null | grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}') 条)"
|
||||||
|
else
|
||||||
|
echo " ! 连接没建齐,仍然继续测"
|
||||||
|
fi
|
||||||
|
echo " 落点:"
|
||||||
|
curl -s --max-time 5 http://127.0.0.1:20241/metrics 2>/dev/null \
|
||||||
|
| grep 'server_locations.*} 1' | sed 's/^/ /'
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
new_probe
|
||||||
|
if [ -z "${PROBE_TOK:-}" ]; then
|
||||||
|
echo " [失败] 探针注册没拿到 token,这轮作废"
|
||||||
|
echo "$label|REGFAIL|0|0|0" >> /tmp/qab.txt
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo " 探针 code=$PROBE_CODE token=${#PROBE_TOK} 字符"
|
||||||
|
|
||||||
|
local mark
|
||||||
|
mark=$(wc -l < /root/lanremote-relay/tunnel.log 2>/dev/null || echo 0)
|
||||||
|
local ok=0 fail=0 times=() fails=""
|
||||||
|
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: $PROBE_TOK" \
|
||||||
|
-H "User-Agent: $UA")
|
||||||
|
code=$(echo "$out" | awk '{print $1}')
|
||||||
|
ms=$(echo "$out" | awk '{printf "%d", $2*1000}')
|
||||||
|
if [ "$code" = "200" ]; then
|
||||||
|
ok=$((ok+1)); times+=("$ms")
|
||||||
|
printf " %2d: 200 %6dms\n" "$i" "$ms"
|
||||||
|
else
|
||||||
|
fail=$((fail+1)); fails="$fails ${i}:${code}"
|
||||||
|
printf " %2d: %-4s **失败**\n" "$i" "$code"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
local stats
|
||||||
|
if [ "${#times[@]}" -gt 0 ]; then
|
||||||
|
stats=$(printf '%s\n' "${times[@]}" | sort -n | awk '{a[NR]=$1} END {
|
||||||
|
printf "中位 %d P90 %d 最大 %d", a[int(NR/2)+1], a[int(NR*0.9)+1], a[NR]}')
|
||||||
|
else
|
||||||
|
stats="无数据"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local reconn
|
||||||
|
reconn=$(tail -n +$((mark+1)) /root/lanremote-relay/tunnel.log 2>/dev/null \
|
||||||
|
| grep -c 'Registered tunnel connection' || echo 0)
|
||||||
|
|
||||||
|
echo " == $ok/$ROUNDS 成功 耗时: $stats ms 本轮重连 $reconn 次"
|
||||||
|
echo " 失败:$fails"
|
||||||
|
echo "$label|$ok|$ROUNDS|$((ok*100/ROUNDS))|$stats|$reconn" >> /tmp/qab.txt
|
||||||
|
cleanup_probe
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
rm -f /tmp/qab.txt
|
||||||
|
|
||||||
|
# 先基线,再候选 —— 同一网络条件下先后测,尽量公平
|
||||||
|
run_round "http2+4" http2 4 || true
|
||||||
|
run_round "quic+6" quic 6 || true
|
||||||
|
|
||||||
|
echo "════════ 汇总 ════════"
|
||||||
|
printf " %-10s %-10s %-8s %-32s %s\n" "配置" "成功" "可用率" "耗时" "本轮重连"
|
||||||
|
while IFS='|' read -r label ok total pct stats reconn; do
|
||||||
|
if [ "$ok" = "REGFAIL" ]; then
|
||||||
|
printf " %-10s 探针注册失败,作废\n" "$label"
|
||||||
|
else
|
||||||
|
printf " %-10s %-10s %-8s %-32s %s\n" "$label" "$ok/$total" "${pct}%" "$stats" "$reconn"
|
||||||
|
fi
|
||||||
|
done < /tmp/qab.txt
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 决策 ════════"
|
||||||
|
B_OK=$(grep '^http2+4' /tmp/qab.txt | cut -d'|' -f4)
|
||||||
|
Q_OK=$(grep '^quic+6' /tmp/qab.txt | cut -d'|' -f4)
|
||||||
|
B_REC=$(grep '^http2+4' /tmp/qab.txt | cut -d'|' -f6)
|
||||||
|
Q_REC=$(grep '^quic+6' /tmp/qab.txt | cut -d'|' -f6)
|
||||||
|
echo " 基线: 可用率 ${B_OK:-?}% 重连 ${B_REC:-?}"
|
||||||
|
echo " 候选: 可用率 ${Q_OK:-?}% 重连 ${Q_REC:-?}"
|
||||||
|
if [ "${Q_OK:-0}" -ge "${B_OK:-0}" ] && [ "${Q_OK:-0}" -ge 90 ]; then
|
||||||
|
echo " → 采用 quic+6"
|
||||||
|
else
|
||||||
|
echo " → 保留 http2+4"
|
||||||
|
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 " 备份: $BACKUP"
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# TCP 重传调优的受控 A/B。
|
||||||
|
#
|
||||||
|
# 背景(实测):
|
||||||
|
# TCPTimeouts: 3145 · TCPLostRetransmit: 2010
|
||||||
|
# 这些多秒/多分钟的尾巴全是 TCP 重传超时造成的。
|
||||||
|
# 这台机器内核是定制的,**没有 BBR**(只有 reno/cubic),
|
||||||
|
# 也没有任何替代 qdisc(只有 noqueue),所以只能在
|
||||||
|
# 现有 cubic 上调 TCP 的重传行为。
|
||||||
|
#
|
||||||
|
# 三个候选开关(都对症"细流 + 高丢包"这个场景):
|
||||||
|
# tcp_thin_linear_timeouts 细流不搞 RTO 指数退避,重传来得更快
|
||||||
|
# tcp_fack 前向确认,丢包恢复更积极
|
||||||
|
# tcp_retries2 重传几次就放弃(8 太大,卡十几分钟)
|
||||||
|
#
|
||||||
|
# 每轮:设参数 -> 打 60 次 HTTP -> 记录成功率/耗时分布 -> 下一轮
|
||||||
|
# 全部跑完回滚,最后按数据决定采用哪组。
|
||||||
|
set -u
|
||||||
|
|
||||||
|
BASE=https://relay.xxmxxs.top
|
||||||
|
UA='LanRemote/1.8.1'
|
||||||
|
N=60
|
||||||
|
|
||||||
|
# 保存原始值,跑完恢复
|
||||||
|
declare -A ORIG
|
||||||
|
for k in tcp_thin_linear_timeouts tcp_fack tcp_retries2 tcp_autocorking; do
|
||||||
|
ORIG[$k]=$(sysctl -n net.ipv4.$k 2>/dev/null || echo "")
|
||||||
|
done
|
||||||
|
|
||||||
|
restore() {
|
||||||
|
for k in "${!ORIG[@]}"; do
|
||||||
|
[ -n "${ORIG[$k]}" ] && sysctl -qw net.ipv4.$k="${ORIG[$k]}" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
measure() {
|
||||||
|
local label="$1"
|
||||||
|
local ok=0 fail=0 times=()
|
||||||
|
for i in $(seq 1 "$N"); do
|
||||||
|
out=$(curl -s -o /dev/null -w '%{http_code} %{time_total}' --max-time 25 \
|
||||||
|
-H "User-Agent: $UA" "$BASE/v1/health")
|
||||||
|
c=$(echo "$out" | awk '{print $1}')
|
||||||
|
ms=$(echo "$out" | awk '{printf "%d", $2*1000}')
|
||||||
|
if [ "$c" = "200" ]; then ok=$((ok+1)); times+=("$ms"); else fail=$((fail+1)); fi
|
||||||
|
done
|
||||||
|
local pct=$((ok * 100 / N))
|
||||||
|
local stats="无数据"
|
||||||
|
if [ "${#times[@]}" -gt 0 ]; then
|
||||||
|
stats=$(printf '%s\n' "${times[@]}" | sort -n | awk '{a[NR]=$1} END {
|
||||||
|
printf "中位 %d P90 %d P99 %d 最大 %d", a[int(NR/2)+1], a[int(NR*0.9)+1], a[int(NR*0.99)+1], a[NR]}')
|
||||||
|
fi
|
||||||
|
printf " %-34s %3d%% (%d/%d) %s ms\n" "$label" "$pct" "$ok" "$N" "$stats"
|
||||||
|
echo "$label|$pct|$ok|$stats" >> /tmp/tcpab.txt
|
||||||
|
}
|
||||||
|
|
||||||
|
rm -f /tmp/tcpab.txt
|
||||||
|
|
||||||
|
echo "════════ 原始值 ════════"
|
||||||
|
for k in "${!ORIG[@]}"; do printf " %-30s = %s\n" "$k" "${ORIG[$k]}"; done
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "════════ 各组合实测(每轮 $N 次)════════"
|
||||||
|
|
||||||
|
echo " [1/6] 基线(不改)"
|
||||||
|
measure "基线"
|
||||||
|
|
||||||
|
echo " [2/6] thin_linear_timeouts=1"
|
||||||
|
sysctl -qw net.ipv4.tcp_thin_linear_timeouts=1
|
||||||
|
measure "thin=1"
|
||||||
|
|
||||||
|
echo " [3/6] thin=1 + fack=1"
|
||||||
|
sysctl -qw net.ipv4.tcp_fack=1
|
||||||
|
measure "thin=1 fack=1"
|
||||||
|
|
||||||
|
echo " [4/6] thin=1 fack=1 + retries2=5"
|
||||||
|
sysctl -qw net.ipv4.tcp_retries2=5
|
||||||
|
measure "thin=1 fack=1 retries2=5"
|
||||||
|
|
||||||
|
echo " [5/6] thin=1 fack=1 retries2=5 + autocorking=0"
|
||||||
|
sysctl -qw net.ipv4.tcp_autocorking=0
|
||||||
|
measure "全部开启"
|
||||||
|
|
||||||
|
echo " [6/6] 回滚到基线再测一次(排除时间漂移)"
|
||||||
|
restore
|
||||||
|
measure "回滚后基线"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 汇总(按可用率排序)════════"
|
||||||
|
sort -t'|' -k2 -rn /tmp/tcpab.txt | while IFS='|' read -r label pct ok stats; do
|
||||||
|
printf " %-34s %3s%% %s\n" "$label" "$pct" "$stats"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 按数据决定 ════════"
|
||||||
|
BEST=$(sort -t'|' -k2 -rn /tmp/tcpab.txt | head -1)
|
||||||
|
BEST_LABEL=$(echo "$BEST" | cut -d'|' -f1)
|
||||||
|
BEST_PCT=$(echo "$BEST" | cut -d'|' -f2)
|
||||||
|
echo " 最好的是: $BEST_LABEL ($BEST_PCT%)"
|
||||||
|
|
||||||
|
case "$BEST_LABEL" in
|
||||||
|
"基线"|"回滚后基线")
|
||||||
|
echo " → 调优没有收益,保持默认(只留已验证过的 keepalive 那几项)"
|
||||||
|
restore
|
||||||
|
;;
|
||||||
|
"thin=1")
|
||||||
|
sysctl -qw net.ipv4.tcp_thin_linear_timeouts=1
|
||||||
|
echo " → 只启用 thin_linear_timeouts"
|
||||||
|
;;
|
||||||
|
"thin=1 fack=1")
|
||||||
|
sysctl -qw net.ipv4.tcp_thin_linear_timeouts=1
|
||||||
|
sysctl -qw net.ipv4.tcp_fack=1
|
||||||
|
echo " → 启用 thin_linear_timeouts + fack"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
sysctl -qw net.ipv4.tcp_thin_linear_timeouts=1
|
||||||
|
sysctl -qw net.ipv4.tcp_fack=1
|
||||||
|
sysctl -qw net.ipv4.tcp_retries2=5
|
||||||
|
sysctl -qw net.ipv4.tcp_autocorking=0
|
||||||
|
echo " → 全部启用"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo " 当前生效值:"
|
||||||
|
for k in tcp_thin_linear_timeouts tcp_fack tcp_retries2 tcp_autocorking; do
|
||||||
|
printf " %-30s = %s\n" "$k" "$(sysctl -n net.ipv4.$k 2>/dev/null)"
|
||||||
|
done
|
||||||
+133
@@ -0,0 +1,133 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 隧道对照 v3:验证「QUIC 失败是因为 MTU」这个假设,并选出最稳的组合。
|
||||||
|
#
|
||||||
|
# 已查明的事实:
|
||||||
|
# 1. cloudflared 用 QUIC 时反复报
|
||||||
|
# "failed to dial to edge with quic: timeout: no recent network activity"
|
||||||
|
# 连十几个 IP 才成功一次 —— 握手阶段就掉包。
|
||||||
|
# 2. 我们给 TCP 做了 MSS 钳制(--set-mss 1400),
|
||||||
|
# 但 **QUIC 走 UDP,TCPMSS 规则完全不生效**。
|
||||||
|
# 3. 路径 MTU 实测 1472(1500 的包不通)。
|
||||||
|
# QUIC 默认用 ~1452 字节的包,很容易被中间设备丢掉,
|
||||||
|
# 而 UDP 丢了不会回 ICMP,客户端只能等到超时。
|
||||||
|
#
|
||||||
|
# 所以对照三组:
|
||||||
|
# A. quic + 6(现状,MTU 1500) —— 预期握手仍不稳
|
||||||
|
# B. quic + 6 + wlan0 MTU 1400 —— 如果假设成立,握手会变稳
|
||||||
|
# C. http2 + 8(TCP 吃 MSS 钳制,连接多) —— 对照组
|
||||||
|
#
|
||||||
|
# 每组记录:连接建齐耗时 / 建齐几条 / 60 次 HTTP 可用率
|
||||||
|
set -u
|
||||||
|
|
||||||
|
BASE=https://relay.xxmxxs.top
|
||||||
|
UA='LanRemote/1.8.1'
|
||||||
|
SVC=/etc/systemd/system/lanremote-tunnel.service
|
||||||
|
BACKUP=/root/tunnel.bak.v3.$(date +%s)
|
||||||
|
cp -a "$SVC" "$BACKUP"
|
||||||
|
N=60
|
||||||
|
|
||||||
|
set_params() {
|
||||||
|
local proto="$1" conns="$2"
|
||||||
|
cp -a "$BACKUP" "$SVC"
|
||||||
|
sed -i "s/--protocol [a-z0-9]*/--protocol $proto/" "$SVC"
|
||||||
|
sed -i "s/--ha-connections [0-9]*/--ha-connections $conns/" "$SVC"
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl restart lanremote-tunnel
|
||||||
|
}
|
||||||
|
|
||||||
|
set_mtu() {
|
||||||
|
local mtu="$1"
|
||||||
|
ip link set wlan0 mtu "$mtu" 2>/dev/null && echo " wlan0 MTU -> $(cat /sys/class/net/wlan0/mtu)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 等连接建齐,返回耗时秒数
|
||||||
|
wait_conns() {
|
||||||
|
local want="$1" limit="$2" t0 t1 n
|
||||||
|
t0=$(date +%s)
|
||||||
|
while true; do
|
||||||
|
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=${n:-0}
|
||||||
|
t1=$(date +%s)
|
||||||
|
if [ "$n" -ge "$want" ] || [ $((t1 - t0)) -ge "$limit" ]; then
|
||||||
|
echo "$n $((t1 - t0))"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
sleep 3
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
run_group() {
|
||||||
|
local label="$1" proto="$2" conns="$3" mtu="$4"
|
||||||
|
echo "════════ $label ════════"
|
||||||
|
|
||||||
|
[ "$mtu" != "keep" ] && set_mtu "$mtu"
|
||||||
|
|
||||||
|
local mark; mark=$(wc -l < /root/lanremote-relay/tunnel.log 2>/dev/null || echo 0)
|
||||||
|
set_params "$proto" "$conns"
|
||||||
|
|
||||||
|
local res; res=$(wait_conns "$conns" 150)
|
||||||
|
local n; n=$(echo "$res" | awk '{print $1}')
|
||||||
|
local secs; s=$(echo "$res" | awk '{print $2}')
|
||||||
|
echo " 建齐 $n/$conns 条,用了 ${s} 秒"
|
||||||
|
|
||||||
|
# 这一轮建立过程中失败了多少次
|
||||||
|
local dialfail; dialfail=$(tail -n +$((mark+1)) /root/lanremote-relay/tunnel.log 2>/dev/null \
|
||||||
|
| grep -c 'Failed to dial' || echo 0)
|
||||||
|
echo " 建立期间 dial 失败 $dialfail 次"
|
||||||
|
|
||||||
|
# 可用率
|
||||||
|
local ok=0 times=()
|
||||||
|
for i in $(seq 1 "$N"); do
|
||||||
|
out=$(curl -s -o /dev/null -w '%{http_code} %{time_total}' --max-time 25 \
|
||||||
|
-H "User-Agent: $UA" "$BASE/v1/health")
|
||||||
|
c=$(echo "$out" | awk '{print $1}')
|
||||||
|
ms=$(echo "$out" | awk '{printf "%d", $2*1000}')
|
||||||
|
if [ "$c" = "200" ]; then ok=$((ok+1)); times+=("$ms"); fi
|
||||||
|
done
|
||||||
|
local pct=$((ok * 100 / N))
|
||||||
|
local stats="无数据"
|
||||||
|
if [ "${#times[@]}" -gt 0 ]; then
|
||||||
|
stats=$(printf '%s\n' "${times[@]}" | sort -n | awk '{a[NR]=$1} END {
|
||||||
|
printf "中位 %d P90 %d P99 %d 最大 %d", a[int(NR/2)+1], a[int(NR*0.9)+1], a[int(NR*0.99)+1], a[NR]}')
|
||||||
|
fi
|
||||||
|
printf " 可用率 %d%% (%d/%d) 耗时 %s ms\n" "$pct" "$ok" "$N" "$stats"
|
||||||
|
echo "$label|$proto|$conns|$mtu|$n|$s|$dialfail|$pct|$stats" >> /tmp/v3.txt
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
rm -f /tmp/v3.txt
|
||||||
|
echo " 原始 wlan0 MTU: $(cat /sys/class/net/wlan0/mtu)"
|
||||||
|
echo
|
||||||
|
|
||||||
|
run_group "A. quic+6 MTU1500" quic 6 keep
|
||||||
|
run_group "B. quic+6 MTU1400" quic 6 1400
|
||||||
|
run_group "C. http2+8 MTU1400" http2 8 keep
|
||||||
|
|
||||||
|
# 恢复 MTU
|
||||||
|
set_mtu 1500
|
||||||
|
|
||||||
|
echo "════════ 汇总 ════════"
|
||||||
|
printf " %-22s %-6s %-4s %-8s %-9s %-8s %s\n" "组" "协议" "连接" "建齐/耗时" "dial失败" "可用率" "延迟"
|
||||||
|
while IFS='|' read -r label proto conns mtu n s dialfail pct stats; do
|
||||||
|
printf " %-22s %-6s %-4s %-8s %-9s %-8s %s\n" \
|
||||||
|
"$label" "$proto" "$conns" "${n}/${conns} ${s}s" "$dialfail" "${pct}%" "$stats"
|
||||||
|
done < /tmp/v3.txt
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 决策:可用率优先,其次看建齐速度 ════════"
|
||||||
|
BEST=$(sort -t'|' -k8 -rn /tmp/v3.txt | head -1)
|
||||||
|
BL=$(echo "$BEST" | cut -d'|' -f1)
|
||||||
|
BP=$(echo "$BEST" | cut -d'|' -f2)
|
||||||
|
BC=$(echo "$BEST" | cut -d'|' -f3)
|
||||||
|
BM=$(echo "$BEST" | cut -d'|' -f4)
|
||||||
|
echo " 最好: $BL(可用率 $(echo "$BEST" | cut -d'|' -f8)%)"
|
||||||
|
|
||||||
|
cp -a "$BACKUP" "$SVC"
|
||||||
|
sed -i "s/--protocol [a-z0-9]*/--protocol $BP/" "$SVC"
|
||||||
|
sed -i "s/--ha-connections [0-9]*/--ha-connections $BC/" "$SVC"
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl restart lanremote-tunnel
|
||||||
|
echo " 已采用: --protocol $BP --ha-connections $BC"
|
||||||
|
echo " MTU: $(cat /sys/class/net/wlan0/mtu)"
|
||||||
|
echo " 备份: $BACKUP"
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 1.8.1 最终验收:从公网走一遍用户会走的全部路径
|
||||||
|
set -u
|
||||||
|
|
||||||
|
BASE=https://relay.xxmxxs.top
|
||||||
|
UA='LanRemote/1.8.1'
|
||||||
|
|
||||||
|
echo "════════ 1. 中转版本和隧道 ════════"
|
||||||
|
curl -s --max-time 15 "$BASE/v1/health" | head -c 130
|
||||||
|
echo
|
||||||
|
echo " 隧道连接: $(curl -s --max-time 5 http://127.0.0.1:20241/metrics 2>/dev/null | grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}') 条"
|
||||||
|
echo " 协议参数: $(grep -c 'protocol quic' /etc/systemd/system/lanremote-tunnel.service) (1=quic)"
|
||||||
|
echo " 长轮询: $(tail -30 /root/lanremote-relay/relay.log | grep '长轮询' | tail -1 | grep -oE '[0-9]+ 秒')"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 2. 官网 ════════"
|
||||||
|
curl -s --max-time 25 -H "User-Agent: $UA" "$BASE/" -o /tmp/site.html
|
||||||
|
echo " HTTP $(curl -s -o /dev/null -w '%{http_code}' --max-time 25 -H "User-Agent: $UA" "$BASE/") $(wc -c < /tmp/site.html) 字节"
|
||||||
|
echo " 下载按钮:"
|
||||||
|
grep -o 'href="/download/[^"]*"' /tmp/site.html | sort -u | sed 's/^/ /'
|
||||||
|
echo " 管理页字眼: $(grep -cE 'zhongzhuanzhan|管理后台|管理页' /tmp/site.html || echo 0) 处(应为 0)"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 3. 三个成品能下吗 ════════"
|
||||||
|
for f in ykxmz-setup-1.8.1.exe ykxmz-server-1.8.1.zip ykxmz-watch-1.8.1.apk; do
|
||||||
|
hdr=$(curl -sI --max-time 40 -H "User-Agent: $UA" "$BASE/download/$f")
|
||||||
|
code=$(echo "$hdr" | head -1 | awk '{print $2}')
|
||||||
|
len=$(echo "$hdr" | grep -i '^content-length' | tr -d '\r' | awk '{print $2}')
|
||||||
|
printf " %-30s HTTP %-4s %s 字节\n" "$f" "$code" "${len:-?}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 4. 长轮询还能跑通吗(2 次真实验证)════════"
|
||||||
|
STAMP=$(date +%s)
|
||||||
|
DEV="final-${STAMP}"
|
||||||
|
CODE=$(printf "%06d" $(( STAMP % 1000000 )))
|
||||||
|
REG=$(curl -s --max-time 30 -X POST "$BASE/v1/agent/register" \
|
||||||
|
-H 'Content-Type: application/json' -H "User-Agent: $UA" \
|
||||||
|
-d "{\"deviceId\":\"$DEV\",\"code\":\"$CODE\",\"name\":\"final\",\"version\":\"1.8.1\"}")
|
||||||
|
TOKEN=$(echo "$REG" | python3 -c "import sys,json; print(json.load(sys.stdin).get('token',''))" 2>/dev/null)
|
||||||
|
echo " 探针注册: $([ -n "$TOKEN" ] && echo 成功 || echo 失败)"
|
||||||
|
for i in 1 2; do
|
||||||
|
out=$(curl -s -o /dev/null -w '%{http_code} %{time_total}' --max-time 130 \
|
||||||
|
-X POST "$BASE/v1/agent/poll" -H "X-Token: $TOKEN" -H "User-Agent: $UA")
|
||||||
|
printf " 第 %d 次: 码 %s 耗时 %.1fs(应约 61 秒)\n" \
|
||||||
|
"$i" "$(echo "$out" | awk '{print $1}')" "$(echo "$out" | awk '{print $2}')"
|
||||||
|
done
|
||||||
|
KEY=$(cat /root/lanremote-relay/data/admin_key.txt 2>/dev/null)
|
||||||
|
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 " 探针已清理"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 5. 公网可用率(10 次)════════"
|
||||||
|
ok=0
|
||||||
|
for i in $(seq 1 10); do
|
||||||
|
c=$(curl -s -o /dev/null -w '%{http_code}' --max-time 20 -H "User-Agent: $UA" "$BASE/v1/health")
|
||||||
|
[ "$c" = "200" ] && ok=$((ok+1))
|
||||||
|
printf " %2d: %s\n" "$i" "$c"
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
echo " 可用 $ok/10"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 6. 中转有没有报错 ════════"
|
||||||
|
journalctl -u lanremote-relay --since '5 minutes ago' --no-pager 2>/dev/null \
|
||||||
|
| grep -icE 'traceback|nameerror|typeerror' | sed 's/^/ 错误行数: /'
|
||||||
|
tail -3 /root/lanremote-relay/relay.log | sed 's/^/ /'
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
r'''给静态资源加缓存头,让 Cloudflare 在边缘直接发。
|
||||||
|
|
||||||
|
== 为什么这是最大的一招 ==
|
||||||
|
|
||||||
|
基线实测(下载 15 MB 安装包走隧道回 4G 源站):
|
||||||
|
|
||||||
|
ykxmz-watch-1.8.1.apk 201523 字节 / 66.8 秒 = 3 KB/s
|
||||||
|
ykxmz-server-1.8.1.zip 214900 字节 / 3.1 秒 = 67 KB/s
|
||||||
|
ykxmz-setup-1.8.1.exe 只下到 237568/15067199 字节就停了
|
||||||
|
|
||||||
|
原因:relay 给所有响应都发 `Cache-Control: no-store`,
|
||||||
|
**Cloudflare 完全不缓存** —— 每一次下载、每一张图、每个 favicon
|
||||||
|
都要穿隧道回 4G 源站。而回源吞吐只有几 KB/s。
|
||||||
|
|
||||||
|
改成可缓存后,Cloudflare 会在用户附近的边缘节点直接发,
|
||||||
|
速度是"边缘到用户",跟 4G 那条腿无关。
|
||||||
|
|
||||||
|
== 各路径的策略 ==
|
||||||
|
|
||||||
|
/download/* public, max-age=604800, immutable
|
||||||
|
文件名带版本号(ykxmz-setup-1.8.1.exe),
|
||||||
|
同一名字的内容永不改变,所以可以放心长缓存。
|
||||||
|
/shot/* 已经是 public, max-age=86400(保持不变)
|
||||||
|
/favicon.ico public, max-age=604800(147 KB,之前每次 3.2 秒)
|
||||||
|
/ public, max-age=60
|
||||||
|
首页会列出当前版本,缓存久了下新版本会看不到按钮。
|
||||||
|
60 秒足够挡住刷新的重复回源,又不会让版本信息过期太久。
|
||||||
|
/v1/* no-store(接口,必须实时)
|
||||||
|
|
||||||
|
注意:Cloudflare 默认只缓存一部分扩展名,.exe/.zip/.apk 可能不在
|
||||||
|
默认列表里。所以改完必须**实测 cf-cache-status 是不是 HIT**,
|
||||||
|
不 HIT 就要在 Cloudflare 后台加一条 Cache Rule(我会告诉用户怎么加)。
|
||||||
|
'''
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
SRC = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote\relay\relay.py")
|
||||||
|
|
||||||
|
OLD_DL = """ self.send_header(
|
||||||
|
"Content-Disposition",
|
||||||
|
f"attachment; filename*=UTF-8''{quoted}")
|
||||||
|
self.send_header("Cache-Control", "no-store")"""
|
||||||
|
|
||||||
|
NEW_DL = """ self.send_header(
|
||||||
|
"Content-Disposition",
|
||||||
|
f"attachment; filename*=UTF-8''{quoted}")
|
||||||
|
# ★ 让 Cloudflare 在边缘缓存。
|
||||||
|
# 原来这里是 no-store,于是每次下载都要穿隧道回 4G 源站,
|
||||||
|
# 实测吞吐只有 3~67 KB/s(15 MB 的安装包根本下不动)。
|
||||||
|
# 文件名带版本号,同一名字内容永不改变,可以长缓存。
|
||||||
|
self.send_header(
|
||||||
|
"Cache-Control",
|
||||||
|
"public, max-age=604800, immutable")"""
|
||||||
|
|
||||||
|
# favicon 那块
|
||||||
|
OLD_ICON = """ self._send(200, icon, "image/x-icon")"""
|
||||||
|
NEW_ICON = """ self._send(200, icon, "image/x-icon",
|
||||||
|
{"Cache-Control": "public, max-age=604800"})"""
|
||||||
|
|
||||||
|
# 首页那块
|
||||||
|
OLD_SITE = """ self._send(200, _site_page().encode("utf-8"),
|
||||||
|
"text/html; charset=utf-8")
|
||||||
|
return"""
|
||||||
|
NEW_SITE = """ # 首页缓存 60 秒:挡住刷新的重复回源,
|
||||||
|
# 又不会让"当前版本/下载按钮"过期太久。
|
||||||
|
self._send(200, _site_page().encode("utf-8"),
|
||||||
|
"text/html; charset=utf-8",
|
||||||
|
{"Cache-Control": "public, max-age=60"})
|
||||||
|
return"""
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
ok_all = True
|
||||||
|
|
||||||
|
print(" 加缓存头:")
|
||||||
|
for old, new, label in (
|
||||||
|
(OLD_DL, NEW_DL, "下载:no-store -> public,max-age=604800,immutable"),
|
||||||
|
(OLD_ICON, NEW_ICON, "favicon:加 7 天缓存"),
|
||||||
|
(OLD_SITE, NEW_SITE, "首页:加 60 秒缓存"),
|
||||||
|
):
|
||||||
|
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())
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
r'''给官网首页加 60 秒缓存。
|
||||||
|
|
||||||
|
(download 那处已经用 edit 工具改好了:no-store ->
|
||||||
|
public,max-age=604800,immutable。)
|
||||||
|
|
||||||
|
首页原来走 _send 的默认 Cache-Control: no-store,
|
||||||
|
每次刷新都穿隧道回 4G 源站。加 60 秒缓存:
|
||||||
|
* 挡住"刷新就重新回源"的浪费
|
||||||
|
* 又不会让页面上的当前版本号 / 下载按钮过期太久
|
||||||
|
'''
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
SRC = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote\relay\relay.py")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
text = SRC.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
marker = 'self._send(200, _site_page().encode("utf-8"),\n'
|
||||||
|
idx = text.find(marker)
|
||||||
|
if idx < 0:
|
||||||
|
print(" ✗ 找不到首页的 _send")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# 打印现场,确认空白
|
||||||
|
end = text.find("\n", text.find("return", idx))
|
||||||
|
print(" 现场:")
|
||||||
|
for line in text[idx:end + 1].splitlines():
|
||||||
|
print(" %r" % line)
|
||||||
|
|
||||||
|
old = ('self._send(200, _site_page().encode("utf-8"),\n'
|
||||||
|
' "text/html; charset=utf-8")')
|
||||||
|
new = ('# 缓存 60 秒:挡住"刷新就重新回源"的浪费,\n'
|
||||||
|
' # 又不会让页面上的当前版本号和下载按钮过期太久。\n'
|
||||||
|
' self._send(200, _site_page().encode("utf-8"),\n'
|
||||||
|
' "text/html; charset=utf-8",\n'
|
||||||
|
' {"Cache-Control": "public, max-age=60"})')
|
||||||
|
|
||||||
|
if old not in text:
|
||||||
|
print()
|
||||||
|
print(" ✗ 精确串还是没匹配上(看上面的 repr 对齐空白)")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
text = text.replace(old, new, 1)
|
||||||
|
SRC.write_text(text, encoding="utf-8", newline="\n")
|
||||||
|
print()
|
||||||
|
print(" ✓ 已加首页缓存")
|
||||||
|
|
||||||
|
# 复核
|
||||||
|
for i, line in enumerate(
|
||||||
|
SRC.read_text(encoding="utf-8").splitlines(), 1):
|
||||||
|
if "max-age=60" in line:
|
||||||
|
print(" 第 %d 行: %s" % (i, line.strip()))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
+158
@@ -0,0 +1,158 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 中转全面基线测量。
|
||||||
|
#
|
||||||
|
# 目的:把"优化到什么程度"变成可量化的数字,而不是感觉。
|
||||||
|
# 五类指标,每类都记录成功率和耗时分布:
|
||||||
|
# A. 公网 HTTP 可用率(100 次 /v1/health)
|
||||||
|
# B. 长轮询(10 次真实 poll,每次挂 60 秒)
|
||||||
|
# C. 三个发布产物的下载成功率和吞吐
|
||||||
|
# D. 隧道连接数稳定性 + 重连次数
|
||||||
|
# E. 静态资源(官网 / 截图 / favicon)
|
||||||
|
#
|
||||||
|
# 用法: bash bench.sh [标签]
|
||||||
|
set -u
|
||||||
|
|
||||||
|
BASE=${BASE:-https://relay.xxmxxs.top}
|
||||||
|
UA='LanRemote/1.8.1'
|
||||||
|
LABEL=${1:-baseline}
|
||||||
|
OUT="/root/bench_${LABEL}.txt"
|
||||||
|
|
||||||
|
echo "════════════════════════════════════════════════════════" | tee "$OUT"
|
||||||
|
echo " 中转基准测量 · $LABEL · $(date '+%Y-%m-%d %H:%M:%S')" | tee -a "$OUT"
|
||||||
|
echo "════════════════════════════════════════════════════════" | tee -a "$OUT"
|
||||||
|
|
||||||
|
echo | tee -a "$OUT"
|
||||||
|
echo "── 隧道配置 ──" | tee -a "$OUT"
|
||||||
|
CAP=$(systemctl show lanremote-tunnel -p ExecStart --value 2>/dev/null | tr ';' '\n' | grep -oE 'argv\[\]=[^ ]*|--[a-z-]+|/[a-z/]*cloudflared' | tr '\n' ' ')
|
||||||
|
grep -oE '\-\-protocol [a-z0-9]+|\-\-ha-connections [0-9]+|\-\-edge-ip-version [0-9]+' /etc/systemd/system/lanremote-tunnel.service 2>/dev/null | tr '\n' ' ' | sed 's/^/ 参数: /' | tee -a "$OUT"
|
||||||
|
echo " 长轮询: $(tail -40 /root/lanremote-relay/relay.log 2>/dev/null | grep -oE '长轮询 [0-9]+ 秒' | tail -1)" | tee -a "$OUT"
|
||||||
|
echo " 连接数: $(curl -s --max-time 5 http://127.0.0.1:20241/metrics 2>/dev/null | grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')" | tee -a "$OUT"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- A
|
||||||
|
echo | tee -a "$OUT"
|
||||||
|
echo "── A. 公网 HTTP 可用率(100 次,间隔 0.4 秒)──" | tee -a "$OUT"
|
||||||
|
okA=0; failA=0; timesA=()
|
||||||
|
for i in $(seq 1 100); do
|
||||||
|
out=$(curl -s -o /dev/null -w '%{http_code} %{time_total}' --max-time 25 -H "User-Agent: $UA" "$BASE/v1/health")
|
||||||
|
code=$(echo "$out" | awk '{print $1}')
|
||||||
|
ms=$(echo "$out" | awk '{printf "%d", $2*1000}')
|
||||||
|
if [ "$code" = "200" ]; then okA=$((okA+1)); timesA+=("$ms"); else failA=$((failA+1)); fi
|
||||||
|
sleep 0.4
|
||||||
|
done
|
||||||
|
pctA=$((okA * 100 / 100))
|
||||||
|
echo " 可用率: $okA/100 = ${pctA}% 失败 $failA 次" | tee -a "$OUT"
|
||||||
|
if [ "${#timesA[@]}" -gt 0 ]; then
|
||||||
|
printf '%s\n' "${timesA[@]}" | sort -n | awk '{
|
||||||
|
a[NR]=$1
|
||||||
|
} END {
|
||||||
|
printf " 耗时: 最小 %d 中位 %d P90 %d P99 %d 最大 %d ms\n",
|
||||||
|
a[1], a[int(NR/2)+1], a[int(NR*0.9)+1], a[int(NR*0.99)+1], a[NR]
|
||||||
|
}' | tee -a "$OUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- E(快的先做)
|
||||||
|
echo | tee -a "$OUT"
|
||||||
|
echo "── E. 静态资源(各 10 次)──" | tee -a "$OUT"
|
||||||
|
for path in "/" "/shot/pc-main.png" "/favicon.ico"; do
|
||||||
|
ok=0; times=()
|
||||||
|
for i in $(seq 1 10); do
|
||||||
|
out=$(curl -s -o /dev/null -w '%{http_code} %{time_total}' --max-time 25 -H "User-Agent: $UA" "$BASE$path")
|
||||||
|
c=$(echo "$out" | awk '{print $1}')
|
||||||
|
ms=$(echo "$out" | awk '{printf "%d", $2*1000}')
|
||||||
|
[ "$c" = "200" ] && { ok=$((ok+1)); times+=("$ms"); }
|
||||||
|
sleep 0.3
|
||||||
|
done
|
||||||
|
stats=$(printf '%s\n' "${times[@]:-0}" | sort -n | awk '{a[NR]=$1} END {printf "中位 %d 最大 %d ms", a[int(NR/2)+1], a[NR]}')
|
||||||
|
printf " %-22s %2d/10 %s\n" "$path" "$ok" "$stats" | tee -a "$OUT"
|
||||||
|
done
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- C
|
||||||
|
echo | tee -a "$OUT"
|
||||||
|
echo "── C. 三个成品下载(完整下载,量吞吐)──" | tee -a "$OUT"
|
||||||
|
for f in ykxmz-setup-1.8.1.exe ykxmz-server-1.8.1.zip ykxmz-watch-1.8.1.apk; do
|
||||||
|
rm -f /tmp/dl.bin
|
||||||
|
out=$(curl -s -o /tmp/dl.bin -w '%{http_code} %{size_download} %{time_total}' \
|
||||||
|
--max-time 180 -H "User-Agent: $UA" "$BASE/download/$f")
|
||||||
|
code=$(echo "$out" | awk '{print $1}')
|
||||||
|
size=$(echo "$out" | awk '{print $2}')
|
||||||
|
secs=$(echo "$out" | awk '{print $3}')
|
||||||
|
kbps=$(echo "$secs $size" | awk '{if ($1 > 0) printf "%.0f", $2/$1/1024; else print 0}')
|
||||||
|
printf " %-30s HTTP %-4s %9s 字节 %6.1fs %6s KB/s\n" \
|
||||||
|
"$f" "$code" "$size" "$secs" "$kbps" | tee -a "$OUT"
|
||||||
|
done
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- D
|
||||||
|
echo | tee -a "$OUT"
|
||||||
|
echo "── D. 隧道连接数稳定性(采样 60 秒,每 2 秒一次)──" | tee -a "$OUT"
|
||||||
|
MARK=$(wc -l < /root/lanremote-relay/tunnel.log 2>/dev/null || echo 0)
|
||||||
|
minc=99; maxc=0; vals=()
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
n=$(curl -s --max-time 4 http://127.0.0.1:20241/metrics 2>/dev/null | grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')
|
||||||
|
[ -z "${n:-}" ] && n=0
|
||||||
|
[ "$n" -lt "$minc" ] && minc="$n"
|
||||||
|
[ "$n" -gt "$maxc" ] && maxc="$n"
|
||||||
|
vals+=("$n")
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
reconn=$(tail -n +$((MARK+1)) /root/lanremote-relay/tunnel.log 2>/dev/null | grep -c 'Registered tunnel connection' || echo 0)
|
||||||
|
lost=$(tail -n +$((MARK+1)) /root/lanremote-relay/tunnel.log 2>/dev/null | grep -c 'Lost connection' || echo 0)
|
||||||
|
iot=$(tail -n +$((MARK+1)) /root/lanremote-relay/tunnel.log 2>/dev/null | grep -c 'i/o timeout' || echo 0)
|
||||||
|
echo " 连接数: 最低 $minc 最高 $maxc 采样期间重连 $reconn 次 · 掉线 $lost 次 · i/o timeout $iot 次" | tee -a "$OUT"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- B
|
||||||
|
echo | tee -a "$OUT"
|
||||||
|
echo "── B. 长轮询(10 次真实 poll,每次挂 60 秒 ≈ 10 分钟)──" | tee -a "$OUT"
|
||||||
|
STAMP=$(date +%s)
|
||||||
|
DEV="bench-${LABEL}-${STAMP}"
|
||||||
|
CODE=$(printf "%06d" $(( STAMP % 1000000 )))
|
||||||
|
REG=$(curl -s --max-time 30 -X POST "$BASE/v1/agent/register" \
|
||||||
|
-H 'Content-Type: application/json' -H "User-Agent: $UA" \
|
||||||
|
-d "{\"deviceId\":\"$DEV\",\"code\":\"$CODE\",\"name\":\"bench\",\"version\":\"1.8.1\"}")
|
||||||
|
TOKEN=$(echo "$REG" | python3 -c "import sys,json; print(json.load(sys.stdin).get('token',''))" 2>/dev/null)
|
||||||
|
if [ -z "$TOKEN" ]; then
|
||||||
|
echo " [失败] 探针注册没拿到 token" | tee -a "$OUT"
|
||||||
|
else
|
||||||
|
okB=0; timesB=(); failsB=""
|
||||||
|
MARK2=$(wc -l < /root/lanremote-relay/tunnel.log 2>/dev/null || echo 0)
|
||||||
|
for i in $(seq 1 10); do
|
||||||
|
out=$(curl -s -o /dev/null -w '%{http_code} %{time_total}' --max-time 130 \
|
||||||
|
-X POST "$BASE/v1/agent/poll" -H "X-Token: $TOKEN" -H "User-Agent: $UA")
|
||||||
|
c=$(echo "$out" | awk '{print $1}')
|
||||||
|
secs=$(echo "$out" | awk '{printf "%d", $2*1000}')
|
||||||
|
if [ "$c" = "200" ]; then
|
||||||
|
okB=$((okB+1)); timesB+=("$secs")
|
||||||
|
printf " %2d: 200 %6dms\n" "$i" "$secs" | tee -a "$OUT"
|
||||||
|
else
|
||||||
|
failsB="$failsB ${i}:${c}"
|
||||||
|
printf " %2d: %-4s **失败**\n" "$i" "$c" | tee -a "$OUT"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo " 长轮询成功: $okB/10 = $((okB*10))% 失败:$failsB" | tee -a "$OUT"
|
||||||
|
if [ "${#timesB[@]}" -gt 0 ]; then
|
||||||
|
printf '%s\n' "${timesB[@]}" | sort -n | awk '{
|
||||||
|
a[NR]=$1
|
||||||
|
} END {
|
||||||
|
printf " 耗时: 最小 %d 中位 %d P90 %d 最大 %d ms(理想 ≈ 61000)\n",
|
||||||
|
a[1], a[int(NR/2)+1], a[int(NR*0.9)+1], a[NR]
|
||||||
|
}' | tee -a "$OUT"
|
||||||
|
fi
|
||||||
|
r2=$(tail -n +$((MARK2+1)) /root/lanremote-relay/tunnel.log 2>/dev/null | grep -c 'Registered tunnel connection' || echo 0)
|
||||||
|
echo " 这 10 分钟里隧道重连 $r2 次" | tee -a "$OUT"
|
||||||
|
KEY=$(cat /root/lanremote-relay/data/admin_key.txt 2>/dev/null)
|
||||||
|
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
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo | tee -a "$OUT"
|
||||||
|
echo "════════════════════════════════════════════════════════" | tee -a "$OUT"
|
||||||
|
echo " 基线存档: $OUT" | tee -a "$OUT"
|
||||||
|
|
||||||
|
# 顺便把关键数字汇总成一行,便于前后对比
|
||||||
|
{
|
||||||
|
echo
|
||||||
|
echo "===== 汇总($LABEL)====="
|
||||||
|
echo "A 公网可用率 ${pctA}% ($okA/100)"
|
||||||
|
echo "B 长轮询成功 ${okB:-0}/10"
|
||||||
|
echo "D 隧道连接 最低 ${minc} 最高 ${maxc} 重连 ${reconn} 掉线 ${lost}"
|
||||||
|
} | tee -a "$OUT"
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 测下载吞吐:缓存预热前后对比。
|
||||||
|
#
|
||||||
|
# 基线(未加缓存头时):
|
||||||
|
# setup.exe 237568 字节 / 13.3s = 17 KB/s (还截断了)
|
||||||
|
# server.zip 214900 字节 / 3.1s = 67 KB/s
|
||||||
|
# watch.apk 201523 字节 / 66.8s = 3 KB/s
|
||||||
|
set -u
|
||||||
|
|
||||||
|
BASE=https://relay.xxmxxs.top
|
||||||
|
UA='LanRemote/1.8.1'
|
||||||
|
FILES="ykxmz-setup-1.8.1.exe ykxmz-server-1.8.1.zip ykxmz-watch-1.8.1.apk"
|
||||||
|
|
||||||
|
echo "════════ 1. 预热:每个文件先请求一次,把内容推进 Cloudflare 缓存 ════════"
|
||||||
|
for f in $FILES; do
|
||||||
|
# 只取前 1 字节,避免真的下载 15 MB
|
||||||
|
st=$(curl -s -o /dev/null -w '%{http_code}' -r 0-0 --max-time 60 \
|
||||||
|
-H "User-Agent: $UA" "$BASE/download/$f")
|
||||||
|
cf=$(curl -s -D - -o /dev/null -r 0-0 --max-time 60 -H "User-Agent: $UA" \
|
||||||
|
"$BASE/download/$f" 2>/dev/null | tr -d '\r' | grep -i '^cf-cache-status' | awk '{print $2}')
|
||||||
|
printf " %-30s HTTP %s cf-cache-status=%s\n" "$f" "$st" "${cf:-无}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo " 等 5 秒让边缘缓存落定…"
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 2. 正式下载(完整文件,量吞吐)════════"
|
||||||
|
total_ok=0
|
||||||
|
for f in $FILES; do
|
||||||
|
rm -f /tmp/dl.bin
|
||||||
|
out=$(curl -s -o /tmp/dl.bin -D /tmp/dl.hdr -w '%{http_code} %{size_download} %{time_total}' \
|
||||||
|
--max-time 240 -H "User-Agent: $UA" "$BASE/download/$f")
|
||||||
|
code=$(echo "$out" | awk '{print $1}')
|
||||||
|
size=$(echo "$out" | awk '{print $2}')
|
||||||
|
secs=$(echo "$out" | awk '{print $3}')
|
||||||
|
cf=$(grep -i '^cf-cache-status' /tmp/dl.hdr | tr -d '\r' | awk '{print $2}')
|
||||||
|
age=$(grep -i '^age:' /tmp/dl.hdr | tr -d '\r' | awk '{print $2}')
|
||||||
|
kbps=$(echo "$secs $size" | awk '{if ($1 > 0) printf "%.0f", $2/$1/1024; else print 0}')
|
||||||
|
printf " %-30s HTTP %-4s %9s 字节 %6.1fs %6s KB/s cf=%s age=%s\n" \
|
||||||
|
"$f" "$code" "$size" "$secs" "$kbps" "${cf:-无}" "${age:-无}"
|
||||||
|
[ "$code" = "200" ] && total_ok=$((total_ok+1))
|
||||||
|
done
|
||||||
|
echo " 成功 $total_ok/3"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 3. 再下一次(应该全 HIT,更快)════════"
|
||||||
|
for f in $FILES; do
|
||||||
|
rm -f /tmp/dl.bin
|
||||||
|
out=$(curl -s -o /tmp/dl.bin -D /tmp/dl.hdr -w '%{http_code} %{size_download} %{time_total}' \
|
||||||
|
--max-time 240 -H "User-Agent: $UA" "$BASE/download/$f")
|
||||||
|
code=$(echo "$out" | awk '{print $1}')
|
||||||
|
size=$(echo "$out" | awk '{print $2}')
|
||||||
|
secs=$(echo "$out" | awk '{print $3}')
|
||||||
|
cf=$(grep -i '^cf-cache-status' /tmp/dl.hdr | tr -d '\r' | awk '{print $2}')
|
||||||
|
kbps=$(echo "$secs $size" | awk '{if ($1 > 0) printf "%.0f", $2/$1/1024; else print 0}')
|
||||||
|
printf " %-30s HTTP %-4s %9s 字节 %6.1fs %6s KB/s cf=%s\n" \
|
||||||
|
"$f" "$code" "$size" "$secs" "$kbps" "${cf:-无}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 4. 对比基线 ════════"
|
||||||
|
echo " 基线: setup 17 KB/s(截断)· server 67 KB/s · apk 3 KB/s"
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
r'''版本号 1.8.0 -> 1.8.1。
|
||||||
|
|
||||||
|
这次是 bug 修复版:局域网劫持 + 长轮询 60 秒。
|
||||||
|
(用户规矩:每次上传都要升版号。)
|
||||||
|
'''
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
|
||||||
|
OLD, NEW = "1.8.0", "1.8.1"
|
||||||
|
BOM = b"\xef\xbb\xbf"
|
||||||
|
|
||||||
|
TARGETS = [
|
||||||
|
"android/AndroidManifest.xml",
|
||||||
|
"android/build_apk.ps1",
|
||||||
|
"installer/ykxmz.iss",
|
||||||
|
"relay/relay.py",
|
||||||
|
"windows/lanremote/__init__.py",
|
||||||
|
"_tests/test_apk_contents.py",
|
||||||
|
"_tests/test_package_remote.py",
|
||||||
|
"_build/package_remote.py",
|
||||||
|
"_build/test_installer_cycle.py",
|
||||||
|
]
|
||||||
|
NEEDS_BOM = {".ps1"}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
print(" %s -> %s" % (OLD, NEW))
|
||||||
|
total = 0
|
||||||
|
for rel in TARGETS:
|
||||||
|
path = ROOT / rel
|
||||||
|
if not path.is_file():
|
||||||
|
print(" [跳过] %s 不存在" % rel)
|
||||||
|
continue
|
||||||
|
raw = path.read_bytes()
|
||||||
|
had_bom = raw.startswith(BOM)
|
||||||
|
text = raw.decode("utf-8-sig")
|
||||||
|
if OLD not in text:
|
||||||
|
print(" [跳过] %-40s 里面没有 %s" % (rel, OLD))
|
||||||
|
continue
|
||||||
|
count = text.count(OLD)
|
||||||
|
text = text.replace(OLD, NEW)
|
||||||
|
want_bom = path.suffix in NEEDS_BOM or had_bom
|
||||||
|
data = text.encode("utf-8")
|
||||||
|
if want_bom:
|
||||||
|
data = BOM + data
|
||||||
|
path.write_bytes(data)
|
||||||
|
print(" %-40s 改了 %d 处" % (rel, count))
|
||||||
|
total += count
|
||||||
|
print()
|
||||||
|
print(" 共改 %d 处" % total)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 看各类资源的响应头,判断有哪些能被 Cloudflare 缓存却因为 no-store 被挡了。
|
||||||
|
set -u
|
||||||
|
|
||||||
|
BASE=https://relay.xxmxxs.top
|
||||||
|
UA='LanRemote/1.8.1'
|
||||||
|
|
||||||
|
echo "════════ 各路径的缓存相关响应头 ════════"
|
||||||
|
for p in "/" "/v1/health" "/shot/pc-main.png" "/favicon.ico" \
|
||||||
|
"/download/ykxmz-server-1.8.1.zip"; do
|
||||||
|
echo " ── $p ──"
|
||||||
|
curl -sI --max-time 30 -H "User-Agent: $UA" "$BASE$p" 2>/dev/null \
|
||||||
|
| grep -iE '^HTTP|^cache-control|^cf-cache-status|^content-type|^content-length|^expires|^age' \
|
||||||
|
| tr -d '\r' | sed 's/^/ /'
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 静态图中的 cf-cache-status(HIT 说明缓存生效)════════"
|
||||||
|
for i in 1 2 3; do
|
||||||
|
s=$(curl -sI --max-time 20 -H "User-Agent: $UA" "$BASE/shot/pc-main.png" 2>/dev/null \
|
||||||
|
| grep -i '^cf-cache-status' | tr -d '\r' | awk '{print $2}')
|
||||||
|
echo " 第 $i 次: cf-cache-status = ${s:-?}"
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ relay 源码里缓存头的设置 ════════"
|
||||||
|
grep -n 'Cache-Control' /root/lanremote-relay/relay.py | sed 's/^/ /'
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 下载路由的实现(看有没有做流式/分块)════════"
|
||||||
|
sed -n '/if path.startswith("\/download\/")/,/^ # /p' /root/lanremote-relay/relay.py \
|
||||||
|
| head -50 | sed 's/^/ /'
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 看可靠性测试进度和隧道事件
|
||||||
|
set -u
|
||||||
|
|
||||||
|
echo "── 测试进程 ──"
|
||||||
|
pgrep -af 'tpr.sh|reliability' | head -3
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "── 隧道最近 30 分钟事件 ──"
|
||||||
|
reconn=$(journalctl -u lanremote-tunnel --since '30 minutes ago' --no-pager 2>/dev/null | grep -c 'Registered tunnel connection')
|
||||||
|
lost=$(journalctl -u lanremote-tunnel --since '30 minutes ago' --no-pager 2>/dev/null | grep -c 'Lost connection')
|
||||||
|
iot=$(journalctl -u lanremote-tunnel --since '30 minutes ago' --no-pager 2>/dev/null | grep -c 'i/o timeout')
|
||||||
|
echo " 重连 $reconn 次 · 掉线 $lost 次 · i/o timeout $iot 次"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "── 最近的掉线/重连明细 ──"
|
||||||
|
journalctl -u lanremote-tunnel --since '30 minutes ago' --no-pager 2>/dev/null \
|
||||||
|
| grep -E 'Registered tunnel connection|Lost connection' | tail -8 | sed 's/^/ /'
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "── watchdog 最近 12 行 ──"
|
||||||
|
tail -12 /root/lanremote-relay/watchdog.log 2>/dev/null | sed 's/^/ /'
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "── 隧道连接数和落点 ──"
|
||||||
|
curl -s --max-time 5 http://127.0.0.1:20241/metrics 2>/dev/null \
|
||||||
|
| grep -E '^cloudflared_tunnel_ha_connections|server_locations.*} 1' | sed 's/^/ /'
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "── 公网现在通不通(10 次快速探测)──"
|
||||||
|
ok=0
|
||||||
|
for i in $(seq 1 10); do
|
||||||
|
c=$(curl -s -o /dev/null -w '%{http_code}' --max-time 15 \
|
||||||
|
-H 'User-Agent: LanRemote/1.8.1' https://relay.xxmxxs.top/v1/health)
|
||||||
|
[ "$c" = "200" ] && ok=$((ok+1))
|
||||||
|
printf " %d: %s\n" "$i" "$c"
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
echo " 可用 $ok/10"
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
r'''临时调试:在首页分支里打一行日志,看它到底有没有被执行、extra 传的是什么。
|
||||||
|
|
||||||
|
(这个文件只改运行环境里的 relay.py,不改本地仓库。)
|
||||||
|
'''
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import paramiko
|
||||||
|
|
||||||
|
HOST = "192.168.10.105"
|
||||||
|
USER = "root"
|
||||||
|
PASSWORD = "1313144"
|
||||||
|
REMOTE = "/root/lanremote-relay/relay.py"
|
||||||
|
|
||||||
|
|
||||||
|
def run(client, cmd: str, timeout: int = 60) -> str:
|
||||||
|
_in, out, err = client.exec_command(cmd, timeout=timeout)
|
||||||
|
return (out.read().decode("utf-8", "replace")
|
||||||
|
+ err.read().decode("utf-8", "replace"))
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
client = paramiko.SSHClient()
|
||||||
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
client.connect(HOST, username=USER, password=PASSWORD, timeout=20,
|
||||||
|
look_for_keys=False, allow_agent=False)
|
||||||
|
try:
|
||||||
|
# 1. 看看首页分支前后到底长什么样
|
||||||
|
print(" 首页分支附近(1400-1415):")
|
||||||
|
print(run(client, "sed -n '1400,1415p' " + REMOTE))
|
||||||
|
|
||||||
|
# 2. 检查 _send 是否真的会跳过默认头
|
||||||
|
print(" _send 的头部逻辑(895-920):")
|
||||||
|
print(run(client, "sed -n '895,920p' " + REMOTE))
|
||||||
|
|
||||||
|
# 3. 用 python 直接调这份代码的 _send 看结果
|
||||||
|
probe = r'''
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, "/root/lanremote-relay")
|
||||||
|
import relay, io
|
||||||
|
|
||||||
|
captured = []
|
||||||
|
class Fake:
|
||||||
|
def send_response(self, code): captured.append(("__status__", code))
|
||||||
|
def send_header(self, k, v): captured.append((k, v))
|
||||||
|
def end_headers(self): pass
|
||||||
|
wfile = io.BytesIO()
|
||||||
|
|
||||||
|
f = Fake()
|
||||||
|
relay.Handler._send(f, 200, b"hi", "text/html; charset=utf-8",
|
||||||
|
{"Cache-Control": "public, max-age=60"})
|
||||||
|
cc = [v for k, v in captured if k.lower() == "cache-control"]
|
||||||
|
print(" _send 发出的 Cache-Control 值:", cc)
|
||||||
|
print(" 头数量:", len(cc))
|
||||||
|
'''
|
||||||
|
Path("/tmp/probe_send.py").write_text(probe, encoding="utf-8")
|
||||||
|
client.exec_command("cat > /tmp/probe_send.py <<'PYEOF'\n"
|
||||||
|
+ probe + "\nPYEOF\n")[1].channel.recv_exit_status()
|
||||||
|
print(" _send 实测:")
|
||||||
|
print(run(client, "python3 /tmp/probe_send.py 2>&1"))
|
||||||
|
|
||||||
|
# 4. 看 do_GET 里 "/" 前面有没有别的 return
|
||||||
|
print(" do_GET 的 return 位置(1174-1412):")
|
||||||
|
print(run(client, "awk 'NR>=1174 && NR<=1412 && "
|
||||||
|
"/return|if path/ {print NR\": \"$0}' " + REMOTE
|
||||||
|
+ " | head -40"))
|
||||||
|
finally:
|
||||||
|
client.close()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 查为什么首页还是 no-store,尽管代码里有 max-age=60
|
||||||
|
set -u
|
||||||
|
|
||||||
|
echo "════════ 1. 进程状态 ════════"
|
||||||
|
systemctl show lanremote-relay -p ActiveEnterTimestamp -p MainPID --value | sed 's/^/ /'
|
||||||
|
echo " 现在: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||||
|
echo " 进程命令行:"
|
||||||
|
ps -o pid,etime,cmd -p $(systemctl show lanremote-relay -p MainPID --value) 2>/dev/null | sed 's/^/ /'
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 2. 本地首页响应头(全部)════════"
|
||||||
|
curl -sI --max-time 15 http://127.0.0.1:8080/ 2>/dev/null | tr -d '\r' | sed 's/^/ /'
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 3. 换个端口直接跑这份代码,排除服务读的是别的文件 ════════"
|
||||||
|
cd /root/lanremote-relay
|
||||||
|
RELAY_PORT=18099 RELAY_BIND=127.0.0.1 timeout 8 python3 relay.py > /tmp/fresh.log 2>&1 &
|
||||||
|
sleep 4
|
||||||
|
echo " 新进程的首页头:"
|
||||||
|
curl -sI --max-time 6 http://127.0.0.1:18099/ 2>/dev/null | tr -d '\r' | grep -iE '^cache-control|^HTTP' | sed 's/^/ /'
|
||||||
|
wait 2>/dev/null || true
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 4. 服务实际读的文件是哪个 ════════"
|
||||||
|
systemctl cat lanremote-relay 2>/dev/null | grep -E 'WorkingDirectory|ExecStart' | sed 's/^/ /'
|
||||||
|
echo " WorkingDirectory 下的 relay.py md5: $(md5sum /root/lanremote-relay/relay.py 2>/dev/null | awk '{print $1}')"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 5. 有没有 __pycache__ 里的旧字节码在捣乱 ════════"
|
||||||
|
ls -la /root/lanremote-relay/__pycache__/ 2>/dev/null | sed 's/^/ /' || echo " (没有 __pycache__)"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 6. 源码里那段到底长什么样(带行号)════════"
|
||||||
|
grep -n -B3 -A6 'max-age=60' /root/lanremote-relay/relay.py | sed 's/^/ /'
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 查隧道为什么只有 3 条连接(配置 6)
|
||||||
|
set -u
|
||||||
|
|
||||||
|
echo "════════ 1. 当前连接数 ════════"
|
||||||
|
curl -s --max-time 5 http://127.0.0.1:20241/metrics 2>/dev/null \
|
||||||
|
| grep -E '^cloudflared_tunnel_ha_connections|server_locations.*} 1' | sed 's/^/ /'
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 2. cloudflared 进程 ════════"
|
||||||
|
ps -o pid,etime,rss,cmd -p $(systemctl show lanremote-tunnel -p MainPID --value) 2>/dev/null | sed 's/^/ /'
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 3. 最近 30 分钟的连接事件 ════════"
|
||||||
|
journalctl -u lanremote-tunnel --since '30 minutes ago' --no-pager 2>/dev/null \
|
||||||
|
| grep -E 'Registered|Lost|Unable|Unregistered|connIndex' | tail -20 | sed 's/^/ /'
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 4. cloudflared 的完整参数 ════════"
|
||||||
|
systemctl cat lanremote-tunnel 2>/dev/null | grep -A8 'ExecStart=' | sed 's/^/ /'
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 5. 4 个活跃连接都在哪个落点、延迟多少 ════════"
|
||||||
|
curl -s --max-time 5 http://127.0.0.1:20241/metrics 2>/dev/null \
|
||||||
|
| grep -E 'cloudflared_tunnel_(server_locations|connection_)' \
|
||||||
|
| grep -v '^#' | sed 's/^/ /'
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 6. 内存占用(MemoryMax=160M 可能把 cloudflared 撑爆)════════"
|
||||||
|
systemctl show lanremote-tunnel -p MemoryCurrent -p MemoryMax --value | sed 's/^/ /'
|
||||||
|
echo " ---"
|
||||||
|
echo " 设备总内存:"
|
||||||
|
free -m | sed 's/^/ /'
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 7. 日志里有没有 OOM 或被杀 ════════"
|
||||||
|
journalctl -u lanremote-tunnel --since '2 hours ago' --no-pager 2>/dev/null \
|
||||||
|
| grep -icE 'oom|killed|signal' | sed 's/^/ OOM/被杀 相关行数: /'
|
||||||
|
dmesg 2>/dev/null | grep -iE 'oom|killed process' | tail -5 | sed 's/^/ /' || echo " dmesg 里没有"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 8. 隧道日志末尾(看有没有在反复失败)════════"
|
||||||
|
tail -20 /root/lanremote-relay/tunnel.log 2>/dev/null | sed 's/^/ /'
|
||||||
@@ -17,7 +17,7 @@ import zipfile
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
|
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
|
||||||
VERSION = "1.8.0"
|
VERSION = "1.8.1"
|
||||||
NAME = f"ykxmz-{VERSION}"
|
NAME = f"ykxmz-{VERSION}"
|
||||||
|
|
||||||
AGENT_SRC = ROOT / "dist_exe" / "ykxmz"
|
AGENT_SRC = ROOT / "dist_exe" / "ykxmz"
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
r'''客户端长轮询超时 90 -> 120 秒。
|
||||||
|
|
||||||
|
原因(实测数据):
|
||||||
|
quic+6 跑 10 次真实长轮询,出现一次 **93589ms** 的长尾。
|
||||||
|
服务端 hold 是 60 秒,也就是网络在这一次多花了 33 秒。
|
||||||
|
而客户端超时设的是 90 秒 —— 93.6 > 90,那一次**必然被判失败**,
|
||||||
|
即使服务端其实已经正常返回了。
|
||||||
|
|
||||||
|
长尾来自 4G 丢包触发的 TCP 重传退避(1.4s 的 RTT 下,
|
||||||
|
几次重传就是几十秒)。这是链路特性,改协议也消不掉,
|
||||||
|
只能把客户端的容忍度放宽。
|
||||||
|
|
||||||
|
新值:120 秒 = 60(hold) + 60(网络余量)。
|
||||||
|
Cloudflare 对单请求的上限是 100 秒,所以服务端 hold 60 秒是安全的;
|
||||||
|
客户端 120 秒只是为了不"冤枉"那些慢但成功的请求。
|
||||||
|
'''
|
||||||
|
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 = [
|
||||||
|
(
|
||||||
|
"windows/lanremote/relay.py",
|
||||||
|
"""# 长轮询单次请求的超时。
|
||||||
|
# 必须显著大于服务端的 hold 时间(现在 60 秒),否则会在服务端
|
||||||
|
# 正常返回之前自己先超时 —— 那就变成"每次轮询都失败"了。
|
||||||
|
# 90 = 60 + 30 秒余量。
|
||||||
|
POLL_TIMEOUT = 90.0""",
|
||||||
|
"""# 长轮询单次请求的超时。
|
||||||
|
# 必须显著大于服务端的 hold 时间(现在 60 秒),否则会在服务端
|
||||||
|
# 正常返回之前自己先超时 —— 那就变成"每次轮询都失败"了。
|
||||||
|
# 120 = 60(hold) + 60(网络余量)。
|
||||||
|
# 为什么余量给这么大:实测出现过一次 93589ms 的长尾
|
||||||
|
# (4G 丢包触发 TCP 重传退避),原来设 90 秒会把它误判成失败。
|
||||||
|
POLL_TIMEOUT = 120.0""",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"android/src/com/lanremote/watch/RelayTransport.java",
|
||||||
|
""" /**
|
||||||
|
* 长轮询:中转那边挂 60 秒,客户端超时给宽一点。
|
||||||
|
*
|
||||||
|
* <p>必须显著大于服务端的 hold 时间,否则会在服务端正常返回
|
||||||
|
* 之前自己先超时 —— 那就变成"每次轮询都失败"了。
|
||||||
|
* 90 秒 = 60 + 30 秒余量。
|
||||||
|
*/
|
||||||
|
private static final int POLL_TIMEOUT_MS = 90000;""",
|
||||||
|
""" /**
|
||||||
|
* 长轮询:中转那边挂 60 秒,客户端超时给宽一点。
|
||||||
|
*
|
||||||
|
* <p>必须显著大于服务端的 hold 时间,否则会在服务端正常返回
|
||||||
|
* 之前自己先超时 —— 那就变成"每次轮询都失败"了。
|
||||||
|
* 120 秒 = 60(hold) + 60(网络余量)。
|
||||||
|
*
|
||||||
|
* <p>余量给这么大是因为实测出现过一次 93589ms 的长尾
|
||||||
|
* (4G 丢包触发 TCP 重传退避)。设 90 秒会把那次
|
||||||
|
* 其实成功的请求误判成失败。
|
||||||
|
*/
|
||||||
|
private static final int POLL_TIMEOUT_MS = 120000;""",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
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 "120000" in text or "120.0" in text:
|
||||||
|
print(" • %-44s 已经是新值" % rel)
|
||||||
|
continue
|
||||||
|
print(" ✗ %-44s 找不到锚点" % rel)
|
||||||
|
ok_all = False
|
||||||
|
continue
|
||||||
|
text = text.replace(old, new, 1)
|
||||||
|
data = text.encode("utf-8")
|
||||||
|
if had_bom:
|
||||||
|
data = BOM + data
|
||||||
|
path.write_bytes(data)
|
||||||
|
print(" ✓ %-44s 已改" % rel)
|
||||||
|
|
||||||
|
print()
|
||||||
|
if not ok_all:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
for rel, token in (
|
||||||
|
("windows/lanremote/relay.py", "POLL_TIMEOUT = 120.0"),
|
||||||
|
("android/src/com/lanremote/watch/RelayTransport.java",
|
||||||
|
"POLL_TIMEOUT_MS = 120000"),
|
||||||
|
):
|
||||||
|
text = (ROOT / rel).read_text(encoding="utf-8-sig")
|
||||||
|
print(" %s %s" % ("✓" if token in text else "✗", rel))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -22,6 +22,7 @@ from __future__ import annotations
|
|||||||
import json
|
import json
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
import urllib.error
|
import urllib.error
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 查还有哪些内核网络调优可用(只读)
|
||||||
|
set -u
|
||||||
|
|
||||||
|
echo "════════ 1. 排队规则(qdisc)候选 ════════"
|
||||||
|
echo " 当前: $(sysctl -n net.core.default_qdisc)"
|
||||||
|
echo " 内核里有没有 fq / fq_codel / cake:"
|
||||||
|
find /lib/modules/$(uname -r)/kernel/net/sched/ -name '*.ko*' 2>/dev/null \
|
||||||
|
| xargs -n1 basename 2>/dev/null | grep -iE 'fq|codel|cake|htb|tbf' | head -10 | sed 's/^/ /'
|
||||||
|
echo " 已加载的相关模块:"
|
||||||
|
lsmod 2>/dev/null | grep -iE 'fq|codel|cake|sch_' | sed 's/^/ /' || echo " (没有)"
|
||||||
|
echo " ---"
|
||||||
|
echo " 试加载 fq_codel(-n = 只检查不真加载):"
|
||||||
|
modprobe -n sch_fq_codel 2>&1 | head -2 | sed 's/^/ /'
|
||||||
|
echo " 试加载 fq:"
|
||||||
|
modprobe -n sch_fq 2>&1 | head -2 | sed 's/^/ /'
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 2. 现在的 qdisc 长什么样 ════════"
|
||||||
|
tc qdisc show dev wlan0 2>/dev/null | sed 's/^/ /' || echo " (tc 没装)"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 3. TCP 重传相关的可调项(当前值)════════"
|
||||||
|
for k in tcp_retries1 tcp_retries2 tcp_rto_min tcp_rto_max tcp_recovery \
|
||||||
|
tcp_early_retrans tcp_fack tcp_thin_linear_timeouts tcp_abort_on_overflow \
|
||||||
|
tcp_limit_output_bytes tcp_moderate_rcvbuf tcp_autocorking; do
|
||||||
|
v=$(sysctl -n net.ipv4.$k 2>/dev/null)
|
||||||
|
[ -n "$v" ] && printf " %-38s = %s\n" "$k" "$v"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 4. 这些项的合法范围 ════════"
|
||||||
|
ls /proc/sys/net/ipv4/ 2>/dev/null | grep -E '^tcp_(retries|rto|recovery|early|fack|thin|abort|limit|moderate|autocork)' | sed 's/^/ /'
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 5. 丢包/重传的实时增量(10 秒采样)════════"
|
||||||
|
a=$(netstat -s 2>/dev/null | grep -E 'TCPTimeouts|segments retransmitted|TCPLostRetransmit' | awk '{s+=$1} END {print s}')
|
||||||
|
sleep 10
|
||||||
|
b=$(netstat -s 2>/dev/null | grep -E 'TCPTimeouts|segments retransmitted|TCPLostRetransmit' | awk '{s+=$1} END {print s}')
|
||||||
|
echo " 10 秒内新增重传/超时相关计数: $((b - a))"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 6. 基线测量还在跑吗 ════════"
|
||||||
|
pgrep -af 'bench.sh' | head -2 | sed 's/^/ /' || echo " 已结束"
|
||||||
|
if [ -f /root/bench_baseline.txt ]; then
|
||||||
|
echo " 已写入的行数: $(wc -l < /root/bench_baseline.txt)"
|
||||||
|
echo " 最近几行:"
|
||||||
|
tail -6 /root/bench_baseline.txt | sed 's/^/ /'
|
||||||
|
fi
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 只读调研:找出还剩下哪些能优化的地方。
|
||||||
|
# 不动任何配置(基线测量正在跑)。
|
||||||
|
set -u
|
||||||
|
|
||||||
|
echo "════════ 1. 内核和拥塞控制 ════════"
|
||||||
|
echo " 内核: $(uname -r)"
|
||||||
|
echo " 可用拥塞控制算法: $(sysctl -n net.ipv4.tcp_available_congestion_control 2>/dev/null)"
|
||||||
|
echo " 当前算法: $(sysctl -n net.ipv4.tcp_congestion_control 2>/dev/null)"
|
||||||
|
echo " 可用排队规则: $(sysctl -n net.core.default_qdisc 2>/dev/null)"
|
||||||
|
echo " ---"
|
||||||
|
echo " BBR 模块在不在:"
|
||||||
|
ls /lib/modules/$(uname -r)/kernel/net/ipv4/ 2>/dev/null | grep -iE 'bbr|tcp_' | head -8 | sed 's/^/ /'
|
||||||
|
echo " modprobe 试试(只查不加载):"
|
||||||
|
modprobe -n tcp_bbr 2>&1 | head -2 | sed 's/^/ /' || echo " 不能加载"
|
||||||
|
echo " 内核有没有编进 BBR:"
|
||||||
|
grep -c 'CONFIG_TCP_CONG_BBR' /boot/config-$(uname -r) 2>/dev/null | sed 's/^/ config 文件: /' || echo " 没有 config 文件"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 2. 当前 TCP 调优项 ════════"
|
||||||
|
for k in \
|
||||||
|
net.ipv4.tcp_keepalive_time net.ipv4.tcp_keepalive_intvl net.ipv4.tcp_keepalive_probes \
|
||||||
|
net.ipv4.tcp_mtu_probing net.ipv4.tcp_slow_start_after_idle net.ipv4.tcp_fastopen \
|
||||||
|
net.ipv4.tcp_rmem net.ipv4.tcp_wmem net.core.rmem_max net.core.wmem_max \
|
||||||
|
net.ipv4.tcp_retries2 net.ipv4.tcp_syn_retries net.ipv4.tcp_synack_retries \
|
||||||
|
net.ipv4.tcp_window_scaling net.ipv4.tcp_timestamps net.ipv4.tcp_sack \
|
||||||
|
net.ipv4.tcp_fack net.ipv4.tcp_recovery net.ipv4.tcp_notsent_lowat \
|
||||||
|
net.core.netdev_max_backlog net.ipv4.tcp_max_syn_backlog net.ipv4.ip_local_port_range; do
|
||||||
|
v=$(sysctl -n "$k" 2>/dev/null)
|
||||||
|
[ -n "$v" ] && printf " %-42s = %s\n" "$k" "$v"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 3. 网络接口和 MTU ════════"
|
||||||
|
ip -br addr 2>/dev/null | sed 's/^/ /'
|
||||||
|
echo " ---"
|
||||||
|
for i in $(ls /sys/class/net/ 2>/dev/null | grep -v lo); do
|
||||||
|
mtu=$(cat /sys/class/net/$i/mtu 2>/dev/null)
|
||||||
|
speed=$(cat /sys/class/net/$i/speed 2>/dev/null || echo '?')
|
||||||
|
echo " $i: MTU=$mtu speed=${speed}Mb"
|
||||||
|
done
|
||||||
|
echo " ---"
|
||||||
|
echo " MSS 钳制规则:"
|
||||||
|
iptables -t mangle -S 2>/dev/null | grep TCPMSS | sed 's/^/ /' || echo " 没有"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 4. 实际路径 MTU 探测 ════════"
|
||||||
|
for target in 1.1.1.1 198.41.192.227; do
|
||||||
|
echo " 到 $target 的最大不分片包:"
|
||||||
|
for sz in 1500 1472 1450 1420 1400 1380 1300; do
|
||||||
|
if ping -c 1 -W 3 -M do -s $((sz-28)) "$target" > /dev/null 2>&1; then
|
||||||
|
echo " $sz 字节: 通"
|
||||||
|
break
|
||||||
|
else
|
||||||
|
echo " $sz 字节: 不通"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 5. cloudflared 可选参数(和重连/传输有关的)════════"
|
||||||
|
cloudflared tunnel run --help 2>&1 | grep -E '^\s+--(retries|protocol|edge|ha-connections|region|grace|compression|tcp)' | sed 's/^/ /'
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 6. 隧道当前完整配置 ════════"
|
||||||
|
systemctl cat lanremote-tunnel 2>/dev/null | grep -vE '^\s*#' | grep -v '^$' | sed 's/^/ /'
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 7. cloudflared 的 originRequest 配置 ════════"
|
||||||
|
cat /root/.cloudflared/config.yml 2>/dev/null | sed 's/^/ /'
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 8. 4G 网卡的重传统计(看链路质量)════════"
|
||||||
|
ip -s link show 2>/dev/null | head -20 | sed 's/^/ /'
|
||||||
|
echo " ---"
|
||||||
|
netstat -s 2>/dev/null | grep -iE 'retrans|timeout|reset|error|失败' | head -10 | sed 's/^/ /'
|
||||||
@@ -25,7 +25,7 @@ import time
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
|
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
|
||||||
SETUP = ROOT / "dist" / "ykxmz-setup-1.8.0.exe"
|
SETUP = ROOT / "dist" / "ykxmz-setup-1.8.1.exe"
|
||||||
APP = Path(r"C:\Program Files\ykxmz")
|
APP = Path(r"C:\Program Files\ykxmz")
|
||||||
DATA = Path(r"C:\ProgramData\ykxmz")
|
DATA = Path(r"C:\ProgramData\ykxmz")
|
||||||
EXE = APP / "ykxmz.exe"
|
EXE = APP / "ykxmz.exe"
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ DOCS_ALLOWLIST = {
|
|||||||
"LICENSE.txt",
|
"LICENSE.txt",
|
||||||
"项目规矩.md", # 这份文件本身就是在讲这条规矩
|
"项目规矩.md", # 这份文件本身就是在讲这条规矩
|
||||||
"免费托管方案.md", # 讲托管方案,不涉及
|
"免费托管方案.md", # 讲托管方案,不涉及
|
||||||
|
# 开发流程文档:里面要写"发布脚本会检查更新说明里有没有管理页字眼",
|
||||||
|
# 属于描述规则本身,不是对外宣传材料。
|
||||||
|
"双平台发布.md",
|
||||||
|
"EzHub-SSH诊断报告.md", # 给服务端运维看的排查报告
|
||||||
}
|
}
|
||||||
|
|
||||||
# 官网源码里这些词也不许有(landing.py 渲染出来的页面)
|
# 官网源码里这些词也不许有(landing.py 渲染出来的页面)
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
r'''把当前版本的三个成品上传到中转的下载目录。
|
||||||
|
|
||||||
|
用法:
|
||||||
|
python _build/upload_release.py 1.8.1
|
||||||
|
|
||||||
|
会自动:
|
||||||
|
* 本地确认三个成品都在(安装包 exe / 服务端 zip / 手表 apk)
|
||||||
|
* 删掉设备上的旧版本文件
|
||||||
|
* 上传新的四个文件(apk 还带一份说明)
|
||||||
|
* 确认中转的 /v1/downloads 能列出来
|
||||||
|
|
||||||
|
发布规则(用户定的):
|
||||||
|
* Windows 成品必须是**安装包 exe**,不是 agent zip
|
||||||
|
* 服务端给 zip,手表直接给 APK
|
||||||
|
* **每次都升版号**(用户原话:自行更新版号)
|
||||||
|
'''
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
|
||||||
|
sys.path.insert(0, str(ROOT / "_build"))
|
||||||
|
|
||||||
|
from devssh import run, upload # noqa: E402
|
||||||
|
|
||||||
|
REMOTE_DIR = "/root/lanremote-relay/downloads"
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
version = sys.argv[1] if len(sys.argv) > 1 else "1.8.1"
|
||||||
|
|
||||||
|
files = [
|
||||||
|
("dist/ykxmz-setup-%s.exe" % version,
|
||||||
|
"ykxmz-setup-%s.exe" % version),
|
||||||
|
("dist/ykxmz-server-%s.zip" % version,
|
||||||
|
"ykxmz-server-%s.zip" % version),
|
||||||
|
("dist/ykxmz-watch-%s.apk" % version,
|
||||||
|
"ykxmz-watch-%s.apk" % version),
|
||||||
|
("dist/ykxmz-watch-%s-说明.txt" % version,
|
||||||
|
"ykxmz-watch-%s-说明.txt" % version),
|
||||||
|
]
|
||||||
|
|
||||||
|
print(" 版本 %s" % version)
|
||||||
|
print()
|
||||||
|
print(" 本地成品:")
|
||||||
|
missing = []
|
||||||
|
for local, _remote in files:
|
||||||
|
path = ROOT / local
|
||||||
|
if path.is_file():
|
||||||
|
print(" ✓ %-38s %7.2f MB"
|
||||||
|
% (path.name, path.stat().st_size / 1024 / 1024))
|
||||||
|
else:
|
||||||
|
print(" ✗ %-38s 不存在" % local)
|
||||||
|
missing.append(local)
|
||||||
|
if missing:
|
||||||
|
print()
|
||||||
|
print(" [失败] 少了 %d 个,先构建" % len(missing))
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# 清掉设备上所有旧版本(任何不是当前版本的 ykxmz-*)
|
||||||
|
print()
|
||||||
|
print(" 清理设备上的旧版本:")
|
||||||
|
_c, out, _e = run("ls %s/ 2>/dev/null" % REMOTE_DIR, timeout=60)
|
||||||
|
for name in (out or "").strip().splitlines():
|
||||||
|
name = name.strip()
|
||||||
|
if not name or version in name:
|
||||||
|
continue
|
||||||
|
if not name.startswith("ykxmz-"):
|
||||||
|
continue
|
||||||
|
run("rm -f '%s/%s'" % (REMOTE_DIR, name), timeout=30)
|
||||||
|
print(" - 删掉 %s" % name)
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(" 上传:")
|
||||||
|
for local, remote_name in files:
|
||||||
|
path = ROOT / local
|
||||||
|
upload(str(path), "%s/%s" % (REMOTE_DIR, remote_name), 0o644)
|
||||||
|
print(" ✓ %s" % remote_name)
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(" 设备上的下载目录:")
|
||||||
|
_c, out, _e = run("ls -la %s/" % REMOTE_DIR, timeout=60)
|
||||||
|
for line in (out or "").strip().splitlines():
|
||||||
|
print(" " + line)
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(" 中转 /v1/downloads:")
|
||||||
|
_c, out, _e = run(
|
||||||
|
"curl -s --max-time 10 http://127.0.0.1:8080/v1/downloads", timeout=60)
|
||||||
|
print(" " + (out or "").strip()[:500])
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(" 官网下载按钮会指向:")
|
||||||
|
for _local, remote_name in files:
|
||||||
|
if remote_name.endswith((".exe", ".zip", ".apk")):
|
||||||
|
print(" /download/%s" % remote_name)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 部署缓存头改动,并验证 Cloudflare 是否真的缓存了。
|
||||||
|
#
|
||||||
|
# 关键疑问:Cloudflare 默认只缓存一批扩展名,
|
||||||
|
# .exe / .zip / .apk **可能不在**默认列表里。
|
||||||
|
# 所以不能只看"我发了 Cache-Control"就算完,
|
||||||
|
# 必须看响应头里的 cf-cache-status 有没有变成 HIT。
|
||||||
|
set -u
|
||||||
|
|
||||||
|
BASE=https://relay.xxmxxs.top
|
||||||
|
UA='LanRemote/1.8.1'
|
||||||
|
|
||||||
|
echo "════════ 1. 重启中转让缓存头生效 ════════"
|
||||||
|
systemctl restart lanremote-relay
|
||||||
|
sleep 6
|
||||||
|
echo " 中转: $(systemctl is-active lanremote-relay)"
|
||||||
|
curl -s --max-time 10 http://127.0.0.1:8080/v1/health | head -c 80
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 2. 本机直连看缓存头(确认代码生效)════════"
|
||||||
|
for p in "/" "/download/ykxmz-server-1.8.1.zip" "/download/ykxmz-setup-1.8.1.exe"; do
|
||||||
|
echo " ── $p ──"
|
||||||
|
curl -sI --max-time 15 "http://127.0.0.1:8080$p" 2>/dev/null \
|
||||||
|
| grep -iE '^HTTP|^cache-control|^content-length|^content-type' \
|
||||||
|
| tr -d '\r' | sed 's/^/ /'
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 3. 等隧道恢复 ════════"
|
||||||
|
for i in $(seq 1 20); do
|
||||||
|
c=$(curl -s -o /dev/null -w '%{http_code}' --max-time 12 -H "User-Agent: $UA" "$BASE/v1/health")
|
||||||
|
printf " 第 %d 次: %s\n" "$i" "$c"
|
||||||
|
[ "$c" = "200" ] && break
|
||||||
|
sleep 3
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 4. 走公网看 cf-cache-status(关键)════════"
|
||||||
|
echo " 对每个资源连打 3 次,看 MISS -> HIT"
|
||||||
|
for p in "/" "/shot/pc-main.png" "/favicon.ico" \
|
||||||
|
"/download/ykxmz-server-1.8.1.zip" \
|
||||||
|
"/download/ykxmz-setup-1.8.1.exe" \
|
||||||
|
"/download/ykxmz-watch-1.8.1.apk"; do
|
||||||
|
echo " ── $p ──"
|
||||||
|
for i in 1 2 3; do
|
||||||
|
hdr=$(curl -sI --max-time 30 -H "User-Agent: $UA" "$BASE$p" 2>/dev/null | tr -d '\r')
|
||||||
|
st=$(echo "$hdr" | grep -i '^cf-cache-status' | awk '{print $2}')
|
||||||
|
cc=$(echo "$hdr" | grep -i '^cache-control' | cut -d' ' -f2-)
|
||||||
|
age=$(echo "$hdr" | grep -i '^age:' | awk '{print $2}')
|
||||||
|
printf " 第 %d 次: cf-cache-status=%-8s age=%-6s cache-control=%s\n" \
|
||||||
|
"$i" "${st:-无}" "${age:-无}" "${cc:-无}"
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 5. 结论 ════════"
|
||||||
|
echo " 如果 .exe/.zip/.apk 一直是 MISS/无,说明 Cloudflare 默认不缓存这些扩展名,"
|
||||||
|
echo " 需要在 Cloudflare 后台加一条 Cache Rule(我会给出具体配置)。"
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 验证首页缓存是否生效(修了 _send 重复头之后)
|
||||||
|
set -u
|
||||||
|
|
||||||
|
BASE=https://relay.xxmxxs.top
|
||||||
|
UA='LanRemote/1.8.1'
|
||||||
|
|
||||||
|
echo "════════ 1. 本机直连的缓存头(确认代码生效)════════"
|
||||||
|
for p in "/" "/download/ykxmz-setup-1.8.1.exe" "/shot/pc-main.png"; do
|
||||||
|
cc=$(curl -sI --max-time 15 "http://127.0.0.1:8080$p" 2>/dev/null \
|
||||||
|
| grep -i '^cache-control' | tr -d '\r')
|
||||||
|
printf " %-38s %s\n" "$p" "${cc:-(无)}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 2. 走公网看 cf-cache-status ════════"
|
||||||
|
for p in "/" "/shot/pc-main.png" "/favicon.ico" "/download/ykxmz-setup-1.8.1.exe"; do
|
||||||
|
echo " ── $p ──"
|
||||||
|
for i in 1 2 3; do
|
||||||
|
hdr=$(curl -sI --max-time 30 -H "User-Agent: $UA" "$BASE$p" 2>/dev/null | tr -d '\r')
|
||||||
|
st=$(echo "$hdr" | grep -i '^cf-cache-status' | awk '{print $2}')
|
||||||
|
cc=$(echo "$hdr" | grep -i '^cache-control' | cut -d' ' -f2-)
|
||||||
|
age=$(echo "$hdr" | grep -i '^age:' | awk '{print $2}')
|
||||||
|
printf " 第 %d 次: status=%-8s age=%-7s cc=%s\n" \
|
||||||
|
"$i" "${st:-无}" "${age:-无}" "${cc:-无}"
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 3. 重复头检查(不该有两个 Cache-Control)════════"
|
||||||
|
n=$(curl -sI --max-time 15 "http://127.0.0.1:8080/" 2>/dev/null | grep -ci '^cache-control')
|
||||||
|
echo " 本机首页的 Cache-Control 头数量: $n (应为 1)"
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 用真正的 GET 验证缓存头(curl -I 发的是 HEAD,之前看的一直是 HEAD 的响应!)
|
||||||
|
set -u
|
||||||
|
|
||||||
|
BASE=https://relay.xxmxxs.top
|
||||||
|
UA='LanRemote/1.8.1'
|
||||||
|
|
||||||
|
echo "════════ 1. 本机直连 · 真 GET(-D - 把响应头 dump 出来)════════"
|
||||||
|
for p in "/" "/download/ykxmz-setup-1.8.1.exe" "/shot/pc-main.png"; do
|
||||||
|
cc=$(curl -s -D - -o /dev/null --max-time 15 "http://127.0.0.1:8080$p" 2>/dev/null \
|
||||||
|
| grep -i '^cache-control' | tr -d '\r')
|
||||||
|
printf " GET %-38s %s\n" "$p" "${cc:-(无)}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 2. 对比 HEAD(两者应该一致)════════"
|
||||||
|
for p in "/" "/download/ykxmz-setup-1.8.1.exe"; do
|
||||||
|
cc=$(curl -sI --max-time 15 "http://127.0.0.1:8080$p" 2>/dev/null \
|
||||||
|
| grep -i '^cache-control' | tr -d '\r')
|
||||||
|
printf " HEAD %-38s %s\n" "$p" "${cc:-(无)}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 3. 走公网 · 真 GET 看 cf-cache-status ════════"
|
||||||
|
for p in "/" "/shot/pc-main.png" "/download/ykxmz-server-1.8.1.zip"; do
|
||||||
|
echo " ── $p ──"
|
||||||
|
for i in 1 2 3; do
|
||||||
|
hdr=$(curl -s -D - -o /dev/null --max-time 30 -H "User-Agent: $UA" "$BASE$p" 2>/dev/null | tr -d '\r')
|
||||||
|
st=$(echo "$hdr" | grep -i '^cf-cache-status' | awk '{print $2}')
|
||||||
|
cc=$(echo "$hdr" | grep -i '^cache-control' | cut -d' ' -f2-)
|
||||||
|
age=$(echo "$hdr" | grep -i '^age:' | awk '{print $2}')
|
||||||
|
printf " 第 %d 次: status=%-8s age=%-7s cc=%s\n" \
|
||||||
|
"$i" "${st:-无}" "${age:-无}" "${cc:-无}"
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
done
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 验证 GET 和 HEAD 的缓存头现在一致了
|
||||||
|
set -u
|
||||||
|
|
||||||
|
LOCAL=http://127.0.0.1:8080
|
||||||
|
BASE=https://relay.xxmxxs.top
|
||||||
|
UA='LanRemote/1.8.1'
|
||||||
|
|
||||||
|
echo "════════ 1. GET vs HEAD 逐路径对比(必须一致)════════"
|
||||||
|
printf " %-38s %-30s %s\n" "路径" "GET" "HEAD"
|
||||||
|
for p in "/" "/favicon.ico" "/shot/pc-main.png" \
|
||||||
|
"/download/ykxmz-setup-1.8.1.exe" "/v1/health"; do
|
||||||
|
g=$(curl -s -D - -o /dev/null --max-time 15 "$LOCAL$p" 2>/dev/null \
|
||||||
|
| grep -i '^cache-control' | cut -d' ' -f2- | tr -d '\r')
|
||||||
|
h=$(curl -sI --max-time 15 "$LOCAL$p" 2>/dev/null \
|
||||||
|
| grep -i '^cache-control' | cut -d' ' -f2- | tr -d '\r')
|
||||||
|
mark="✓"
|
||||||
|
[ "$g" != "$h" ] && mark="✗ 不一致"
|
||||||
|
printf " %-38s %-30s %s %s\n" "$p" "${g:-无}" "${h:-无}" "$mark"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 2. HEAD 的下载要报正确的 Content-Length ════════"
|
||||||
|
for f in ykxmz-setup-1.8.1.exe ykxmz-watch-1.8.1.apk; do
|
||||||
|
len=$(curl -sI --max-time 20 "$LOCAL/download/$f" 2>/dev/null \
|
||||||
|
| grep -i '^content-length' | awk '{print $2}' | tr -d '\r')
|
||||||
|
ctype=$(curl -sI --max-time 20 "$LOCAL/download/$f" 2>/dev/null \
|
||||||
|
| grep -i '^content-type' | cut -d' ' -f2- | tr -d '\r')
|
||||||
|
printf " %-30s %10s 字节 %s\n" "$f" "${len:-?}" "${ctype:-?}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "════════ 3. 走公网 HEAD 也应该报告可缓存 ════════"
|
||||||
|
for p in "/download/ykxmz-setup-1.8.1.exe"; do
|
||||||
|
h=$(curl -sI --max-time 30 -H "User-Agent: $UA" "$BASE$p" 2>/dev/null | tr -d '\r')
|
||||||
|
echo " $p"
|
||||||
|
echo "$h" | grep -iE '^cache-control|^content-length|^cf-cache-status|^content-type' | sed 's/^/ /'
|
||||||
|
done
|
||||||
@@ -38,10 +38,32 @@ def main() -> int:
|
|||||||
files = [str(p) for p in TARGETS if p.is_file()]
|
files = [str(p) for p in TARGETS if p.is_file()]
|
||||||
print(f" 检查 {len(files)} 个 Python 文件…")
|
print(f" 检查 {len(files)} 个 Python 文件…")
|
||||||
|
|
||||||
proc = subprocess.run([sys.executable, "-m", "pyflakes"] + files,
|
# ⚠ 不能一次把 450 多个文件全塞进命令行 ——
|
||||||
capture_output=True, text=True,
|
# Windows 的 CreateProcess 有命令行长度上限(约 32767 字符),
|
||||||
encoding="utf-8", errors="replace")
|
# 超了直接 FileNotFoundError: [WinError 206] 文件名或扩展名太长,
|
||||||
lines = [line for line in (proc.stdout or "").splitlines() if line.strip()]
|
# 而且报错看起来像"文件找不到",很容易误判。
|
||||||
|
# 所以分批调用,每批的总长度控制住。
|
||||||
|
all_lines = []
|
||||||
|
batch, batch_len = [], 0
|
||||||
|
LIMIT = 6000 # 单批命令行里文件路径的总字符数预算(留足余量)
|
||||||
|
batches = []
|
||||||
|
for path in files:
|
||||||
|
if batch and batch_len + len(path) + 1 > LIMIT:
|
||||||
|
batches.append(batch)
|
||||||
|
batch, batch_len = [], 0
|
||||||
|
batch.append(path)
|
||||||
|
batch_len += len(path) + 1
|
||||||
|
if batch:
|
||||||
|
batches.append(batch)
|
||||||
|
|
||||||
|
for chunk in batches:
|
||||||
|
proc = subprocess.run([sys.executable, "-m", "pyflakes"] + chunk,
|
||||||
|
capture_output=True, text=True,
|
||||||
|
encoding="utf-8", errors="replace")
|
||||||
|
all_lines.extend(
|
||||||
|
line for line in (proc.stdout or "").splitlines() if line.strip())
|
||||||
|
|
||||||
|
lines = all_lines
|
||||||
|
|
||||||
if not lines:
|
if not lines:
|
||||||
print(" ✓ 没有未定义名 / 未使用导入等问题")
|
print(" ✓ 没有未定义名 / 未使用导入等问题")
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user