起因:一间宿舍,值不值得买个路由器?
我住公司宿舍,自己拉了宽带。房间不大,一根网线进门,接到电脑上就完事了。至于手机和平板,本来想着买个路由器,但转念一想:
- 房间就这么大,电脑的无线网卡覆盖整个屋子绰绰有余;
- 公司宿舍免电费,电脑本来就常年开着;
- 电脑上跑着 sing-box,代理分流和广告过滤都配好了。如果手机、平板直接连电脑开的热点,这些规则原封不动地就继承过去了,连软路由都省得折腾。
省钱、省事、还省了一台设备。于是 Windows 设置里点开「移动热点」,手机平板连上,完美。
直到我发现,手机隔三差五就会莫名其妙切到流量。
症状:手机以为自己连着,其实早就断了
具体表现是这样的:
- 手机 Wi-Fi 图标一直在,信号满格;
- 但网页打不开,过一会儿系统自己跳到了移动数据;
- 电脑这边热点还开着,平板也时不时出同样的问题;
- 手动关掉 Wi-Fi 再打开,立刻就好了。
一开始我以为是手机的「智能切换」太敏感,于是插上 USB,用 adb 看看手机到底是怎么想的。
第一站:手机为什么认为 Wi-Fi 不可用
dumpsys connectivity 里 Wi-Fi 被标成了 EXITING、isUsable:false,默认网络已经换成了移动数据。往前翻 dumpsys wifi 的记录,时间线非常清楚:
20:39:09 DATA_STALL_BAD_TX txSuccessDelta=0 txRetriesDelta=64 txBadDelta=5 rxSuccessDelta=0
20:39:14 wlan_assist 申请建立移动数据
20:39:17 FIRMWARE_ALERT ...
20:39:51 IpReachabilityMonitor: 192.168.5.1 NUD_FAILED ← 连网关都不通了
两秒内发了 64 次重传,一个都没成功,也一个包都没收到。可当时的信号强度是 -41 dBm,非常好。信号好却发不出去,不像是干扰,更像是对面根本不理它了。
我一开始还怀疑是路由器兼容性,直到想起来:这个“路由器”就是我自己的电脑。
第二站:交叉对比电脑日志
电脑这边打开事件查看器里的 Microsoft-Windows-WLAN-AutoConfig/Operational,两边时钟对齐(只差 0.5 秒),结果一目了然:
| 时间 | 电脑(热点) | 手机 |
|---|---|---|
| 20:38:40 | 11004 Wireless security stopped,手机的会话被结束 | 毫无察觉,依然“已连接” |
| 20:39:09 | — | 发包全部失败 |
| 20:39:14 | — | 切到移动数据 |
| 20:40:35 | 手机重新连上 | 恢复 |
是电脑先把手机踢了,而且没通知手机。 手机那边还挂着一个死连接,要等自己的检测机制超时才会切走。
第三站:挨个排除,全都没用
接下来就是经典的“网上能搜到的方法都试一遍”环节:
- 电源管理:网卡「允许计算机关闭此设备」本来就是关的,电源计划里无线适配器也是最高性能。❌
- 更新网卡驱动:Intel AX201 从 24.10 升到 24.70(2026 年 7 月的版本)。❌ 照断不误。
- 关闭数据包合并(Packet Coalescing)。❌
- 怀疑后台扫描:电脑的 WLAN 在当热点的同时还会扫描周围网络,扫描时要切离信道。于是把所有保存的 Wi-Fi 改成手动连接,再用
netsh wlan set autoconfig enabled=no interface="WLAN"关掉自动配置。❌ 还是断。
第四站:抓驱动日志,找到真正的“凶手”
既然表面的设置都没用,那就看看底层到底发生了什么。用 netsh trace 抓 WLAN 相关的 ETW 日志,等它断一次:
netsh trace start provider=Microsoft-Windows-WLAN-AutoConfig provider=Microsoft-Windows-NWiFi `
provider=Microsoft-Windows-WLAN-Driver provider=Microsoft-Windows-WiFiHotspotService `
capture=no report=disabled maxsize=1024 filemode=circular tracefile=wlan.etl
断开那一瞬间的记录:
22:59:24.231 Received Security Packet: EXTAP_PORT_DOWN
22:59:24.231 FSM Current state AP_Started, event Upcall_Port_Down
22:59:24.231 Port down for peer 0x2E080E88xxxx ← 手机的 MAC
22:59:24.231 SecMgr Transition SUCCESS --> DEACTIVATE --> INACTIVE
22:59:24.231 RequestAdapterSync Error: 0xC0000001
22:59:24.231 Wireless security stopped.
Upcall_Port_Down 是网卡驱动主动上报给 Windows 的:“这个客户端下线了”。之前没有任何 Windows 发起的踢人、密钥更新失败或者服务报错。热点服务、ICS、电源管理全都是被动接到通知的。
至于驱动为什么这么判断,具体原因写在 Intel 的私有日志里,外人解不出来。
后来一搜,发现这不是我一个人的问题。Intel 社区从 2022 年起就有人报告 AX201 + Windows 移动热点,手机空闲熄屏后被踢,症状一模一样,普遍的说法是驱动 22.160 之后就出问题了,大家的办法基本是回退老驱动。我装的已经是 24.70,四年过去了,依然没修。
解决:一个“土办法”
回退老驱动我不太想。太老的驱动在新系统上不一定装得上,Windows 更新也可能又给你升回去。
换个思路:从现象看,设备一空闲、手机熄屏省电,就会被踢。驱动大概是一段时间收不到对方的帧,就按“不活跃”处理了。
那就别让它空闲。电脑定时往热点下的每台设备发个包,让链路一直有东西在走。
最朴素的版本就是每隔几秒 ping 一次。挂上之后,真的不断了。
做成了一个小工具
光一个 ping 循环不太好用,就顺手包装了一下:
- 自动发现热点下的所有设备:读热点网卡的 ARP 表,按 MAC 记住设备,IP 变了也能跟上;
- 两种模式:自动保活全部设备,或者只保活勾选的设备;
- 图形界面:启动、停止、注册开机服务、移除开机服务;
- 后台运行和界面分离:关掉界面,保活照样跑。注册开机服务后,以 SYSTEM 身份开机自启,不用登录;
- 日志:记录设备连续失联、恢复,以及热点的断开事件,方便判断是真被踢了还是暂时没回应。
核心逻辑其实就这么几行:
$if = (Get-NetIPAddress -IPAddress $gateway).InterfaceIndex
$clients = Get-NetNeighbor -InterfaceIndex $if -AddressFamily IPv4 |
? { $_.State -ne 'Permanent' -and $_.LinkLayerAddress -notmatch '^(00-){5}00$|^(FF-){5}FF$' }
foreach ($c in $clients) {
(New-Object System.Net.NetworkInformation.Ping).SendPingAsync($c.IPAddress, 800) | Out-Null
}
实验性功能:硬件 ACK 模式
ping 有个小代价:手机收到 ping 要唤醒 CPU 来回复。间隔 10 秒的话,一天就是八千多次短暂唤醒。
但仔细想想,真正让网卡觉得“设备还活着”的,很可能不是 ping 的回包,而是更底层的东西:电脑发出任何一个无线帧,手机的 Wi-Fi 芯片都会在硬件层自动回一个 802.11 ACK。
所以我加了个「硬件 ACK 模式」:不 ping,改为用 WinPcap 往设备的 MAC 地址直接发一个 60 字节的空以太网帧,协议类型用 0x88B5(IEEE 保留给本地实验用的)。
- 手机芯片收到后会回 ACK,这一步不需要 CPU;
- 这种没人认识的协议类型,系统一般会直接丢掉,理论上不会唤醒手机。
实测开着这个模式,手机和平板也没有再被踢过。不过要说明两点:
- “不唤醒手机 CPU”目前只是推理,我还没有实测耗电;
- 它需要装 WinPcap 或 Npcap。这个模式发出去收不到回复,没法判断设备在不在线,只能通过热点的断开事件来确认有没有被踢。
脚本地址:hotspot-keepalive
使用前请注意
- 适用场景:Windows 移动热点,网卡是 Intel(至少 AX201 确认有这个问题),客户端空闲后被踢、手机显示“已连接但无法上网”。
- 网关地址:我的热点网段改成了
192.168.5.x。Windows 默认是192.168.137.1,用之前在界面里改成你自己的。 - 耗电:ping 模式会让客户端少睡一点,间隔可以按需调大,找到刚好不断线的值。
- 治标不治本:这是在绕过驱动的行为,不是修复驱动。哪天 Intel 修好了,这个工具就可以删了。
顺便说几个排查中的发现
- 手机自己也在 ping 网关:抓包发现 ColorOS 大约每 7 秒会 ping 一次热点网关,应该是系统在检测链路质量。但这点流量显然不够让网卡满意。
- PowerShell 5.1 的坑:函数返回单个 CIM 对象时,
.Count是空的。我的界面一度把“有 1 个在运行”判断成“没在运行”,还会重复启动。老老实实用@(...).Length。 - 中文脚本要带 BOM:PowerShell 5.1 读取不带 BOM 的 UTF-8 脚本会按系统代码页解析,中文全乱。