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
|
||||
|
||||
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
|
||||
VERSION = "1.8.0"
|
||||
VERSION = "1.8.1"
|
||||
NAME = f"ykxmz-{VERSION}"
|
||||
|
||||
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 subprocess
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
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
|
||||
|
||||
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")
|
||||
DATA = Path(r"C:\ProgramData\ykxmz")
|
||||
EXE = APP / "ykxmz.exe"
|
||||
|
||||
@@ -54,6 +54,10 @@ DOCS_ALLOWLIST = {
|
||||
"LICENSE.txt",
|
||||
"项目规矩.md", # 这份文件本身就是在讲这条规矩
|
||||
"免费托管方案.md", # 讲托管方案,不涉及
|
||||
# 开发流程文档:里面要写"发布脚本会检查更新说明里有没有管理页字眼",
|
||||
# 属于描述规则本身,不是对外宣传材料。
|
||||
"双平台发布.md",
|
||||
"EzHub-SSH诊断报告.md", # 给服务端运维看的排查报告
|
||||
}
|
||||
|
||||
# 官网源码里这些词也不许有(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
|
||||
Reference in New Issue
Block a user