ssh连接pi400调试时,有时会断线,但可能pi400与wifi还是连接的,请帮写个分析脚本,看看它与wifi连接稳定性,与业务相关的youtube/cloudflare/github的连接稳定性。
传到 Pi 后直接用:
# 传到 Pi
scp netwatch.sh [email protected]:~/
# 加执行权限
chmod +x ~/netwatch.sh
# 单次完整报告(排查当前状态)
bash ~/netwatch.sh
# 持续监控(每60秒检测一次,同时写日志)
bash ~/netwatch.sh --watch --log
脚本检测四个层面:
WiFi 层:信号强度(dBm)并给出优/良/差评级、频率、连接速率、RX丢弃包数。信号低于 -80dBm 基本可以确定是断线根源。
网关层:ping 本地路由器,RTT 和丢包率。网关丢包说明 WiFi 本身不稳,跟外网无关。
业务节点:分别 ping YouTube、Cloudflare R2、Cloudflare Pages、GitHub、Gemini API,每个节点显示丢包率和 RTT,颜色区分正常/警告/故障。
SSH 断线原因:检查 sshd 的 ClientAliveInterval 配置,为 0 时直接给出修复命令;同时抓取过去 24 小时的 SSH 断线日志;顺带检测 CPU 温度,Pi 过热会触发降频影响 WiFi 驱动稳定性。
SSH 断线最常见的两个原因:一是 sshd 没开 keepalive,长时间无操作后连接被路由器 NAT 超时踢掉;二是 WiFi 信号弱导致间歇性断联。脚本的输出能直接定位是哪种。
#!/bin/bash
# ─────────────────────────────────────────────────────────────
# netwatch.sh — Pi 400 网络稳定性监控
# 监控:WiFi 信号 / 本地网关 / 业务节点连通性
# 用法:
# 单次报告: bash netwatch.sh
# 持续监控: bash netwatch.sh --watch
# 写入日志: bash netwatch.sh --watch --log
# ─────────────────────────────────────────────────────────────
# ── 配置区 ────────────────────────────────────────────────────
INTERVAL=60 # 持续监控时的检测间隔(秒)
PING_COUNT=5 # 每次 ping 的包数
PING_TIMEOUT=5 # ping 超时秒数
LOG_FILE="$HOME/netwatch.log"
LOG_MAX_LINES=5000 # 日志超过此行数时自动截断
# 业务节点(名称:地址)
declare -A TARGETS=(
["YouTube"]="www.youtube.com"
["Cloudflare-R2"]="pub-1b62f0f9bae84486a796cbf53a3a27ee.r2.dev"
["Cloudflare-Pages"]="pabloye.pages.dev"
["GitHub"]="github.com"
["Gemini-API"]="generativelanguage.googleapis.com"
["DNS-Primary"]="8.8.8.8"
["DNS-Secondary"]="1.1.1.1"
)
# ── 颜色 ──────────────────────────────────────────────────────
if [ -t 1 ]; then
RED='\033[0;31m'; YELLOW='\033[0;33m'
GREEN='\033[0;32m'; BLUE='\033[0;34m'
CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
else
RED=''; YELLOW=''; GREEN=''; BLUE=''; CYAN=''; BOLD=''; NC=''
fi
# ── 工具函数 ──────────────────────────────────────────────────
ts() { date '+%Y-%m-%d %H:%M:%S'; }
log() {
local line="$(ts) $*"
echo -e "$line"
if [ "$DO_LOG" = "1" ]; then
echo "$line" >> "$LOG_FILE"
# 自动截断过长日志
local lines
lines=$(wc -l < "$LOG_FILE")
if [ "$lines" -gt "$LOG_MAX_LINES" ]; then
tail -n $((LOG_MAX_LINES / 2)) "$LOG_FILE" > "${LOG_FILE}.tmp"
mv "${LOG_FILE}.tmp" "$LOG_FILE"
echo "$(ts) [INFO] 日志已自动截断至 $((LOG_MAX_LINES / 2)) 行" >> "$LOG_FILE"
fi
fi
}
section() { log "${BOLD}${BLUE}── $1 ──${NC}"; }
# ── WiFi 状态 ─────────────────────────────────────────────────
check_wifi() {
section "WiFi 状态"
local iface
iface=$(iw dev 2>/dev/null | awk '/Interface/{print $2}' | head -1)
if [ -z "$iface" ]; then
log "${RED}[WARN] 未找到无线网卡${NC}"
return
fi
# SSID 和 BSSID
local ssid bssid
ssid=$(iw dev "$iface" link 2>/dev/null | awk '/SSID/{print $2}')
bssid=$(iw dev "$iface" link 2>/dev/null | awk '/Connected to/{print $3}')
[ -z "$ssid" ] && ssid="(未连接)"
log " 接口: ${CYAN}${iface}${NC} SSID: ${CYAN}${ssid}${NC} BSSID: ${bssid}"
# 信号强度
local signal freq bitrate
signal=$(iw dev "$iface" link 2>/dev/null | awk '/signal/{print $2, $3}')
freq=$(iw dev "$iface" link 2>/dev/null | awk '/freq/{print $2}')
bitrate=$(iw dev "$iface" link 2>/dev/null | awk '/tx bitrate/{print $3, $4}')
log " 信号: ${CYAN}${signal:-N/A}${NC} 频率: ${freq:-N/A} MHz 速率: ${bitrate:-N/A}"
# 信号质量评级
if [ -n "$signal" ]; then
local dbm
dbm=$(echo "$signal" | awk '{print $1}')
if [ "$dbm" -ge -60 ] 2>/dev/null; then
log " 质量: ${GREEN}优秀 (≥-60 dBm)${NC}"
elif [ "$dbm" -ge -70 ] 2>/dev/null; then
log " 质量: ${GREEN}良好 (-70~-60 dBm)${NC}"
elif [ "$dbm" -ge -80 ] 2>/dev/null; then
log " 质量: ${YELLOW}一般 (-80~-70 dBm),可能出现丢包${NC}"
else
log " 质量: ${RED}差 (<-80 dBm),建议靠近路由器${NC}"
fi
fi
# IP 地址
local ip
ip=$(ip addr show "$iface" 2>/dev/null | awk '/inet /{print $2}' | head -1)
log " IP地址: ${CYAN}${ip:-未获取}${NC}"
# 网关
GATEWAY=$(ip route show default 2>/dev/null | awk '{print $3}' | head -1)
log " 网关: ${CYAN}${GATEWAY:-未知}${NC}"
# WiFi 驱动统计(丢包/重传)
local rx_drop tx_retry
rx_drop=$(cat /sys/class/net/"$iface"/statistics/rx_dropped 2>/dev/null)
tx_retry=$(iw dev "$iface" station dump 2>/dev/null | awk '/tx retries/{print $3}')
[ -n "$rx_drop" ] && log " RX丢弃: ${rx_drop}包 TX重传: ${tx_retry:-N/A}次"
}
# ── 网关连通性 ────────────────────────────────────────────────
check_gateway() {
section "本地网关"
if [ -z "$GATEWAY" ]; then
GATEWAY=$(ip route show default 2>/dev/null | awk '{print $3}' | head -1)
fi
if [ -z "$GATEWAY" ]; then
log " ${RED}[FAIL] 无法获取网关地址${NC}"
return
fi
ping_target "网关" "$GATEWAY"
}
# ── 通用 Ping 检测 ────────────────────────────────────────────
ping_target() {
local name="$1" host="$2"
local result loss avg_rtt
result=$(ping -c "$PING_COUNT" -W "$PING_TIMEOUT" "$host" 2>&1)
if echo "$result" | grep -q "Network is unreachable\|Name or service not known"; then
log " ${RED}[FAIL]${NC} ${BOLD}${name}${NC} (${host}) — 网络不可达或 DNS 失败"
return 1
fi
loss=$(echo "$result" | grep -oP '\d+(?=% packet loss)')
avg_rtt=$(echo "$result" | grep -oP 'rtt.*= [\d.]+/\K[\d.]+')
if [ -z "$loss" ]; then
log " ${RED}[FAIL]${NC} ${BOLD}${name}${NC} (${host}) — 无响应"
return 1
fi
local status_color="$GREEN" status="OK"
if [ "$loss" -ge 50 ]; then
status_color="$RED"; status="严重丢包"
elif [ "$loss" -ge 20 ]; then
status_color="$RED"; status="丢包"
elif [ "$loss" -ge 5 ]; then
status_color="$YELLOW"; status="轻微丢包"
fi
local rtt_color="$GREEN"
if [ -n "$avg_rtt" ]; then
local rtt_int=${avg_rtt%.*}
if [ "$rtt_int" -ge 300 ] 2>/dev/null; then
rtt_color="$RED"
elif [ "$rtt_int" -ge 150 ] 2>/dev/null; then
rtt_color="$YELLOW"
fi
fi
log " [${status_color}${status}${NC}] ${BOLD}${name}${NC} (${host}) — 丢包: ${status_color}${loss}%${NC} RTT: ${rtt_color}${avg_rtt:-N/A} ms${NC}"
}
# ── 业务节点检测 ──────────────────────────────────────────────
check_targets() {
section "业务节点连通性"
for name in YouTube Cloudflare-R2 Cloudflare-Pages GitHub Gemini-API DNS-Primary DNS-Secondary; do
ping_target "$name" "${TARGETS[$name]}"
done
}
# ── SSH 断线原因分析 ──────────────────────────────────────────
check_ssh_stability() {
section "SSH 断线原因分析"
# keepalive 设置
local ka_interval ka_count
ka_interval=$(sshd -T 2>/dev/null | awk '/clientaliveinterval/{print $2}')
ka_count=$(sshd -T 2>/dev/null | awk '/clientalivecountmax/{print $2}')
if [ -n "$ka_interval" ]; then
log " sshd ClientAliveInterval: ${ka_interval}s CountMax: ${ka_count}"
if [ "${ka_interval:-0}" -eq 0 ]; then
log " ${YELLOW}[建议] 服务端未开启 keepalive,长时间无操作会断线${NC}"
log " ${CYAN}修复:sudo sh -c 'echo \"ClientAliveInterval 60\" >> /etc/ssh/sshd_config'${NC}"
log " ${CYAN} sudo sh -c 'echo \"ClientAliveCountMax 3\" >> /etc/ssh/sshd_config'${NC}"
log " ${CYAN} sudo systemctl restart sshd${NC}"
else
log " ${GREEN}服务端 keepalive 已配置${NC}"
fi
fi
# 最近的 SSH 断线记录
log " 最近 SSH 断线记录(最近10条):"
local disconnects
disconnects=$(journalctl -u ssh --since "24 hours ago" 2>/dev/null \
| grep -i "disconnect\|broken pipe\|timeout\|reset by peer" \
| tail -10)
if [ -n "$disconnects" ]; then
echo "$disconnects" | while IFS= read -r line; do
log " ${YELLOW}${line}${NC}"
done
else
log " ${GREEN}最近 24 小时内无异常断线记录${NC}"
fi
# 系统负载(Pi 过热也会导致 WiFi 不稳)
local load temp
load=$(uptime | awk -F'load average:' '{print $2}')
temp=$(vcgencmd measure_temp 2>/dev/null | grep -oP '[\d.]+')
log " 系统负载:${load}"
if [ -n "$temp" ]; then
local temp_color="$GREEN"
[ "$(echo "$temp > 70" | bc -l 2>/dev/null)" = "1" ] && temp_color="$RED"
[ "$(echo "$temp > 60" | bc -l 2>/dev/null)" = "1" ] && temp_color="$YELLOW"
log " CPU温度: ${temp_color}${temp}°C${NC}(>70°C 会触发降频,影响 WiFi 驱动稳定性)"
fi
}
# ── 单次完整报告 ──────────────────────────────────────────────
run_once() {
log "${BOLD}${BLUE}════════════════════════════════════════${NC}"
log "${BOLD} Pi 400 网络稳定性报告${NC}"
log "${BOLD}${BLUE}════════════════════════════════════════${NC}"
check_wifi
check_gateway
check_targets
check_ssh_stability
log "${BOLD}${BLUE}════════════════════════════════════════${NC}"
}
# ── 持续监控模式 ──────────────────────────────────────────────
run_watch() {
log "持续监控模式启动(间隔 ${INTERVAL}s,Ctrl+C 停止)"
[ "$DO_LOG" = "1" ] && log "日志写入: $LOG_FILE"
local round=0
while true; do
round=$((round + 1))
log "${BOLD}${BLUE}══ 第 ${round} 轮检测 ══${NC}"
check_wifi
check_gateway
check_targets
log "下次检测: ${INTERVAL}s 后"
sleep "$INTERVAL"
done
}
# ── 入口 ──────────────────────────────────────────────────────
DO_WATCH=0
DO_LOG=0
for arg in "$@"; do
case "$arg" in
--watch) DO_WATCH=1 ;;
--log) DO_LOG=1 ;;
--help)
echo "用法: bash netwatch.sh [--watch] [--log]"
echo " (无参数) 单次完整报告"
echo " --watch 持续监控,每 ${INTERVAL}s 检测一次"
echo " --log 同时写入 $LOG_FILE"
exit 0 ;;
esac
done
if [ "$DO_WATCH" = "1" ]; then
run_watch
else
run_once
fi
运行测试效果:
bash netwatch.sh
2026-04-06 11:13:53 ════════════════════════════════════════
2026-04-06 11:13:53 Pi 400 网络稳定性报告
2026-04-06 11:13:53 ════════════════════════════════════════
2026-04-06 11:13:53 ── WiFi 状态 ──
2026-04-06 11:13:53 接口: wlan0 SSID: MOVISTAR-WIFI7-5C90 BSSID: dc:08:da:84:5c:9e
2026-04-06 11:13:53 信号: -58 dBm 频率: 5180 MHz 速率: 325.0 MBit/s
2026-04-06 11:13:54 质量: 优秀 (≥-60 dBm)
2026-04-06 11:13:54 IP地址: 192.168.1.38/24
2026-04-06 11:13:55 网关: 192.168.1.1
2026-04-06 11:13:55 RX丢弃: 0包 TX重传: N/A次
2026-04-06 11:13:55 ── 本地网关 ──
2026-04-06 11:13:59 [OK] 网关 (192.168.1.1) — 丢包: 0% RTT: 2.560 ms
2026-04-06 11:13:59 ── 业务节点连通性 ──
2026-04-06 11:14:03 [OK] YouTube (www.youtube.com) — 丢包: 0% RTT: 22.292 ms
2026-04-06 11:14:07 [OK] Cloudflare-R2 (pub-1b62f0f9bae84486a796cbf53a3a27ee.r2.dev) — 丢包: 0% RTT: 18.568 ms
2026-04-06 11:14:11 [OK] Cloudflare-Pages (pabloye.pages.dev) — 丢包: 0% RTT: 9.701 ms
2026-04-06 11:14:16 [OK] GitHub (github.com) — 丢包: 0% RTT: 53.299 ms
2026-04-06 11:14:20 [OK] Gemini-API (generativelanguage.googleapis.com) — 丢包: 0% RTT: 11.181 ms
2026-04-06 11:14:24 [OK] DNS-Primary (8.8.8.8) — 丢包: 0% RTT: 12.489 ms
2026-04-06 11:14:28 [OK] DNS-Secondary (1.1.1.1) — 丢包: 0% RTT: 9.895 ms
2026-04-06 11:14:28 ── SSH 断线原因分析 ──
2026-04-06 11:14:28 最近 SSH 断线记录(最近10条):
2026-04-06 11:14:28 最近 24 小时内无异常断线记录
2026-04-06 11:14:28 系统负载: 0.00, 0.00, 0.00
2026-04-06 11:14:28 CPU温度: 32.1°C(>70°C 会触发降频,影响 WiFi 驱动稳定性)
2026-04-06 11:14:28 ════════════════════════════════════════
监控ssh连线情况,分析断线原因
#!/bin/bash
# ─────────────────────────────────────────────────────────────
# ssh_hang_diag.sh — SSH 卡死根因诊断
# 在 Pi 400 上运行,持续采样网络/系统状态,卡顿时留下现场快照
#
# 用法:
# 后台持续采样: bash ssh_hang_diag.sh --daemon
# 单次快照: bash ssh_hang_diag.sh --snap
# 查看报告: bash ssh_hang_diag.sh --report
# ─────────────────────────────────────────────────────────────
INTERVAL=10 # 采样间隔(秒)
LOG_DIR="$HOME/ssh_hang_logs"
SNAP_LOG="$LOG_DIR/snapshots.log"
EVENT_LOG="$LOG_DIR/events.log"
WIFI_IFACE=$(iw dev 2>/dev/null | awk '/Interface/{print $2}' | head -1)
GATEWAY=$(ip route show default 2>/dev/null | awk '{print $3}' | head -1)
# 卡顿判定阈值
PING_LOSS_WARN=20 # 网关丢包率超过此值记录事件
SIGNAL_WARN=-75 # WiFi 信号低于此值记录事件
TEMP_WARN=70 # CPU 温度超过此值记录事件
mkdir -p "$LOG_DIR"
ts() { date '+%Y-%m-%d %H:%M:%S'; }
# ── 单次快照:采集所有关键指标 ───────────────────────────────
take_snapshot() {
local t; t=$(ts)
# 1. WiFi 信号
local signal freq tx_bitrate rx_bitrate
signal=$(iw dev "$WIFI_IFACE" link 2>/dev/null | awk '/signal/{print $2}')
freq=$(iw dev "$WIFI_IFACE" link 2>/dev/null | awk '/freq/{print $2}')
tx_bitrate=$(iw dev "$WIFI_IFACE" link 2>/dev/null | awk '/tx bitrate/{print $3}')
rx_bitrate=$(iw dev "$WIFI_IFACE" link 2>/dev/null | awk '/rx bitrate/{print $3}')
# 2. 网卡统计(丢弃/错误包)
local rx_drop rx_err tx_drop tx_err
rx_drop=$(cat /sys/class/net/"$WIFI_IFACE"/statistics/rx_dropped 2>/dev/null)
rx_err=$(cat /sys/class/net/"$WIFI_IFACE"/statistics/rx_errors 2>/dev/null)
tx_drop=$(cat /sys/class/net/"$WIFI_IFACE"/statistics/tx_dropped 2>/dev/null)
tx_err=$(cat /sys/class/net/"$WIFI_IFACE"/statistics/tx_errors 2>/dev/null)
# 3. 网关 ping(3包快速探测)
local ping_result loss rtt
ping_result=$(ping -c 3 -W 2 -q "$GATEWAY" 2>/dev/null)
loss=$(echo "$ping_result" | grep -oP '\d+(?=% packet loss)')
rtt=$(echo "$ping_result" | grep -oP 'rtt.*= [\d.]+/\K[\d.]+')
# 4. CPU 温度
local temp
temp=$(vcgencmd measure_temp 2>/dev/null | grep -oP '[\d.]+')
# 5. 系统负载 + 内存
local load mem_free
load=$(awk '{print $1}' /proc/loadavg)
mem_free=$(awk '/MemAvailable/{print $2}' /proc/meminfo)
mem_free_mb=$((mem_free / 1024))
# 6. 活跃 SSH 连接数
local ssh_conns
ssh_conns=$(ss -tn state established '( dport = :22 or sport = :22 )' 2>/dev/null | grep -c ESTAB || echo 0)
# 写入采样日志
echo "$t signal=${signal}dBm freq=${freq}MHz tx=${tx_bitrate}Mbps rx=${rx_bitrate}Mbps" \
"rxdrop=${rx_drop} rxerr=${rx_err} txdrop=${tx_drop} txerr=${tx_err}" \
"gw_loss=${loss}% gw_rtt=${rtt}ms temp=${temp}C" \
"load=${load} mem=${mem_free_mb}MB ssh=${ssh_conns}" >> "$SNAP_LOG"
# 检查是否触发阈值 → 写事件日志
local events=()
if [ -n "$loss" ] && [ "$loss" -ge "$PING_LOSS_WARN" ]; then
events+=("网关丢包=${loss}%")
fi
if [ -n "$signal" ] && [ "$signal" -le "$SIGNAL_WARN" ] 2>/dev/null; then
events+=("WiFi信号弱=${signal}dBm")
fi
if [ -n "$temp" ] && [ "$(echo "$temp >= $TEMP_WARN" | bc -l 2>/dev/null)" = "1" ]; then
events+=("CPU过热=${temp}C")
fi
if [ "${rx_err:-0}" -gt 0 ] || [ "${tx_err:-0}" -gt 0 ]; then
events+=("网卡错误 rx_err=${rx_err} tx_err=${tx_err}")
fi
if [ "${#events[@]}" -gt 0 ]; then
local event_str
event_str=$(IFS=', '; echo "${events[*]}")
echo "$t [EVENT] $event_str" >> "$EVENT_LOG"
echo "⚠️ $t 检测到异常: $event_str"
fi
echo "$t OK signal=${signal}dBm loss=${loss}% rtt=${rtt}ms temp=${temp}C"
}
# ── 生成分析报告 ──────────────────────────────────────────────
generate_report() {
echo ""
echo "════════════════════════════════════════"
echo " SSH 卡死根因分析报告"
echo " 数据来自: $LOG_DIR"
echo "════════════════════════════════════════"
if [ ! -f "$SNAP_LOG" ]; then
echo "暂无采样数据,请先运行 --daemon 模式采集一段时间"
return
fi
local total_lines
total_lines=$(wc -l < "$SNAP_LOG")
echo "采样记录: ${total_lines} 条"
echo "时间范围: $(head -1 "$SNAP_LOG" | cut -d' ' -f1-2) ~ $(tail -1 "$SNAP_LOG" | cut -d' ' -f1-2)"
echo ""
# WiFi 信号统计
echo "── WiFi 信号分布 ──"
local signals
signals=$(grep -oP 'signal=\K-\d+' "$SNAP_LOG")
if [ -n "$signals" ]; then
local avg min max
avg=$(echo "$signals" | awk '{s+=$1; c++} END {printf "%.0f", s/c}')
min=$(echo "$signals" | sort -n | head -1)
max=$(echo "$signals" | sort -n | tail -1)
echo " 平均: ${avg} dBm 最低: ${min} dBm 最高: ${max} dBm"
local weak_count
weak_count=$(echo "$signals" | awk -v t="$SIGNAL_WARN" '$1 <= t' | wc -l)
echo " 信号 ≤${SIGNAL_WARN}dBm 次数: ${weak_count}/${total_lines}"
[ "$weak_count" -gt 0 ] && echo " → 信号弱是SSH卡顿的高概率原因"
fi
echo ""
# 网关丢包统计
echo "── 网关丢包分布 ──"
local losses
losses=$(grep -oP 'gw_loss=\K\d+' "$SNAP_LOG")
if [ -n "$losses" ]; then
local avg_loss
avg_loss=$(echo "$losses" | awk '{s+=$1; c++} END {printf "%.1f", s/c}')
local loss_count
loss_count=$(echo "$losses" | awk -v t="$PING_LOSS_WARN" '$1 >= t' | wc -l)
echo " 平均丢包率: ${avg_loss}%"
echo " 丢包 ≥${PING_LOSS_WARN}% 次数: ${loss_count}/${total_lines}"
[ "$loss_count" -gt 0 ] && echo " → WiFi 间歇性丢包,SSH 缓冲区积压导致卡死"
fi
echo ""
# 温度统计
echo "── CPU 温度分布 ──"
local temps
temps=$(grep -oP 'temp=\K[\d.]+' "$SNAP_LOG")
if [ -n "$temps" ]; then
local avg_temp max_temp
avg_temp=$(echo "$temps" | awk '{s+=$1; c++} END {printf "%.1f", s/c}')
max_temp=$(echo "$temps" | sort -n | tail -1)
echo " 平均: ${avg_temp}°C 峰值: ${max_temp}°C"
local hot_count
hot_count=$(echo "$temps" | awk -v t="$TEMP_WARN" '$1 >= t' | wc -l)
[ "$hot_count" -gt 0 ] && echo " ⚠️ 超过${TEMP_WARN}°C 次数: ${hot_count} — 过热会触发降频影响WiFi驱动"
fi
echo ""
# 事件时间线
echo "── 异常事件时间线 ──"
if [ -f "$EVENT_LOG" ] && [ -s "$EVENT_LOG" ]; then
cat "$EVENT_LOG"
else
echo " 无异常事件记录(网络状态良好)"
fi
echo ""
# 根因判断
echo "── 根因判断 ──"
local has_cause=0
local weak_pct loss_pct
weak_pct=$(grep -oP 'signal=\K-\d+' "$SNAP_LOG" | awk -v t="$SIGNAL_WARN" \
'BEGIN{c=0;tot=0} {tot++; if($1<=t)c++} END{if(tot>0)printf "%.0f", c/tot*100; else print 0}')
loss_pct=$(grep -oP 'gw_loss=\K\d+' "$SNAP_LOG" | awk -v t="$PING_LOSS_WARN" \
'BEGIN{c=0;tot=0} {tot++; if($1>=t)c++} END{if(tot>0)printf "%.0f", c/tot*100; else print 0}')
if [ "${weak_pct:-0}" -ge 10 ]; then
echo " ★ 主因:WiFi 信号弱(${weak_pct}% 的时间低于 ${SIGNAL_WARN}dBm)"
echo " 建议:Pi 靠近路由器,或换用 5GHz 频段"
has_cause=1
fi
if [ "${loss_pct:-0}" -ge 5 ]; then
echo " ★ 主因:网关间歇性丢包(${loss_pct}% 的采样有丢包)"
echo " 建议:检查路由器负载,换用有线网络"
has_cause=1
fi
if [ "$has_cause" -eq 0 ]; then
echo " 网络层未发现明显问题"
echo " 可能原因:sshd 未配置 keepalive(见下方修复建议)"
fi
echo ""
# SSH keepalive 检查
echo "── SSH Keepalive 配置 ──"
local ka
ka=$(sshd -T 2>/dev/null | awk '/clientaliveinterval/{print $2}')
if [ "${ka:-0}" -eq 0 ]; then
echo " ⚠️ sshd 未开启 keepalive(ClientAliveInterval=0)"
echo " 修复命令(在 Pi 上执行):"
echo " sudo sh -c 'echo \"ClientAliveInterval 15\" >> /etc/ssh/sshd_config'"
echo " sudo sh -c 'echo \"ClientAliveCountMax 3\" >> /etc/ssh/sshd_config'"
echo " sudo systemctl restart sshd"
else
echo " ✅ ClientAliveInterval=${ka}s(已配置)"
fi
echo ""
echo "════════════════════════════════════════"
}
# ── 入口 ──────────────────────────────────────────────────────
case "${1:-}" in
--daemon)
echo "后台采样启动(间隔 ${INTERVAL}s),日志: $SNAP_LOG"
echo "停止: kill \$(pgrep -f ssh_hang_diag)"
while true; do
take_snapshot
sleep "$INTERVAL"
done
;;
--snap)
take_snapshot
;;
--report)
generate_report
;;
*)
echo "用法:"
echo " bash ssh_hang_diag.sh --daemon # 后台持续采样(建议跑1小时以上)"
echo " bash ssh_hang_diag.sh --snap # 单次快照"
echo " bash ssh_hang_diag.sh --report # 生成分析报告"
;;
esac
绝对可以! 这份脚本写得非常专业且对症,它简直就是为了抓出树莓派 SSH 断线元凶而量身定制的“电子法医”。
SSH 断线通常感觉像是一门“玄学”,但归根结底无非是网络层、硬件层或软件配置的问题。你上传的这个脚本,刚好完美覆盖了这三大类嫌疑犯。
🔍 这个脚本能帮你检测出什么?
WiFi 信号盲区/波动 (网络层)
它会每 10 秒记录一次你的
tx/rx bitrate(收发速率)和signal(信号强度,单位 dBm)。如果报告显示信号经常掉到 -75dBm 以下,说明 Pi 400 所在的位置有物理遮挡或干扰,导致 SSH 缓冲区积压最终卡死。路由器/网关丢包 (网络层)
脚本会高频 Ping 你的默认网关(路由器)。如果出现局域网内的严重丢包(
gw_loss),说明是路由器负载太高,或者是 WiFi 频段(比如 2.4GHz)太拥堵了。CPU 过热降频 (硬件层)
Pi 400 如果持续跑重负载任务,温度超过 70°C(
temp),系统会触发降频自保。在极端情况下,过热会导致 WiFi 芯片组极其不稳定甚至掉线。NAT 会话超时/保活缺失 (软件层)
这也是最常见的原因。脚本最后的报告功能会直接读取系统配置,检查你的 Pi 是否开启了
ClientAliveInterval(心跳保活包)。如果没开,路由器会无情地踢掉长时间没有数据传输的安静连接。
🚀 最佳食用指南(实战操作)
为了抓到现行,我建议你按照以下步骤来跑这个脚本:
第一步:在“保护罩”里启动后台监控
因为你的 SSH 会断线,如果直接运行脚本,断线时脚本可能也会跟着被杀掉。所以我们要用之前提到的 screen 或 tmux 来跑它:
Bash
screen -S ssh_monitor
bash ssh_hang_diag.sh --daemon
按下 Ctrl+A 然后按 D 把窗口切到后台,让它自己静静地记录。
第二步:该干嘛干嘛,等待案发
去正常使用你的 Pi,或者干脆放着不管,直到下一次 SSH 卡死、断线发生。
第三步:重连并提取“尸检报告”
当你发现 SSH 断了,重新连上去,然后直接运行报告命令:
Bash
bash ssh_hang_diag.sh --report
此时,脚本会把过去的采样数据进行汇总,直接告诉你主因是什么(是 WiFi 弱、经常丢包,还是因为没开 Keepalive)。拿着这份报告,你就能一击必杀,彻底解决断线问题了。