手机 App 通过局域网 WebSocket 控制机器人
一、需求背景
我们有一个人形机器人(天轶 2.0 Pro),运行 Python + LangGraph 的 AI Agent,通过 ROS2 控制双臂和灵巧手。另外有一个 Expo React Native 手机 App。
目标: 手机 App 通过 WiFi 局域网直连机器人,发送控制指令(手势、机械臂动作、语音等),不经过云端后端。
技术方案:
- 机器人端:FastAPI WebSocket Server,监听 8765 端口
- 手机端:React Native WebSocket Client
- 发现机制:UDP 广播 / HTTP 子网扫描
二、遇到的问题链
整个调通过程中,我们依次遇到了 5 个问题,每个都会导致连接失败,但表现几乎一样(连不上)。排查顺序很重要。
三、问题 1:不同子网,网络不通
现象
手机和机器人都连了校园网 WiFi,但互相 ping 不通,WebSocket 连接超时。
排查
手机 IP: 172.16.73.217
机器人 IP: 172.16.77.134前三段不一样(73 vs 77),说明被分配到了不同 VLAN。
原因
校园网/企业网通常做 VLAN 隔离,同一个 WiFi SSID 下的设备可能在不同子网,广播包和直连都不通。
解决
用手机开热点,机器人连手机热点,确保在同一个 192.168.x.0/24 子网内。
经验
永远先确认基础连通性:
# 确认 IP 在同一子网
ping <对方IP>如果 ping 不通,上层协议都不用看了。
问题 2:防火墙阻止入站连接
现象
同一子网(手机热点下),ping 通了,但 TCP 连接建立不了。
排查
# 检查防火墙
sudo ufw status
sudo iptables -L INPUT -n
# 放行端口
sudo iptables -I INPUT -p tcp --dport 8765 -j ACCEPT
sudo iptables -I INPUT -p udp --dport 9999 -j ACCEPT原因
Linux 默认可能有 iptables 规则阻止入站。
解决
放行 8765/tcp 和 9999/udp。
经验
ping 通不代表端口通。用 Test-NetConnection(Windows)或 nc -zv(Linux)测试具体端口:
Test-NetConnection 192.168.242.186 -Port 8765问题 3:websockets 库严格校验握手头
现象
Test-NetConnection 显示 TCP 端口可达(TcpTestSucceeded: True),但 WebSocket 握手失败,浏览器显示 readyState: 3。
排查
服务器日志出现:
websockets.exceptions.InvalidUpgrade: invalid Connection header: keep-alive原因
Python websockets 库 v14+ 对 HTTP Upgrade 请求头做了严格校验。某些客户端(浏览器、代理)发送的 Connection: keep-alive 或缺少 Connection: Upgrade 头会被直接拒绝。
解决
从 websockets.serve() 换成 FastAPI WebSocket(基于 uvicorn/starlette),对握手头更宽容:
# 之前(严格)
import websockets
server = await websockets.serve(handler, "0.0.0.0", 8765)
# 之后(宽容)
from fastapi import FastAPI, WebSocket
app = FastAPI()
@app.websocket("/ws")
async def websocket_endpoint(ws: WebSocket):
await ws.accept()
...验证方法
用 Python 客户端隔离问题——如果 Python 能连但浏览器不能,就是握手头的问题:
python -c "import asyncio, websockets; asyncio.run(websockets.connect('ws://IP:8765/ws'))"经验
WebSocket 握手本质是 HTTP Upgrade。 不同客户端发的头不一样,服务端库的严格程度也不同。遇到握手失败时:
- 看服务端日志里的具体错误
- 用多种客户端测试(Python、curl、浏览器)隔离问题
- 优先选择对头校验宽容的服务端实现(FastAPI/Starlette > websockets 库)
问题 4:Android 禁止明文流量(Cleartext Traffic)
现象
电脑 Python 能连 WebSocket,但手机 App 连接失败。HTTP 和 WebSocket 都不通。
原因
Android 9 (API 28) 起默认禁止明文 HTTP/WS 流量(非 TLS)。ws:// 和 http:// 都会被系统网络层直接拦截,App 收到的是连接失败,服务器端完全没有日志。
解决
Expo 项目需要用 expo-build-properties 插件:
npm install expo-build-propertiesapp.json:
{
"plugins": [
["expo-build-properties", {
"android": {
"usesCleartextTraffic": true
}
}]
]
}注意: 直接在 app.json 的 android 节写 usesClearTextTraffic 是无效的,必须通过插件注入。
经验
Android 明文流量限制是最隐蔽的坑:
- 服务器端完全没有任何日志(请求根本没发出去)
- 表现和网络不通一模一样
- 只影响 Android,iOS 和电脑不受影响
排查清单:
- 电脑能连但手机不能 → 大概率是 Android 安全策略
- 检查是否用了
ws://或http://(非 TLS) - 确认
usesCleartextTraffic已正确注入到 AndroidManifest.xml
问题 5:react-native-udp 原生模块编译失败
现象
EAS 云端构建 APK 失败:
ENOENT: no such file or directory, open '.../android/gradlew'以及原生模块编译错误。
原因
react-native-udp是原生模块,最后更新于 2021 年,和 React Native 0.81 + 新架构不兼容- 本地生成的
android/目录被.gitignore忽略,EAS 上传时不包含
解决
- 移除
react-native-udp,改用纯 JS 的 HTTP 子网扫描 - 删除本地
android/目录,让 EAS 在云端自动 prebuild
经验
选择 React Native 原生依赖时要谨慎:
- 检查最后更新时间和 RN 版本兼容性
- 优先选择纯 JS 实现或 Expo 官方维护的模块
- 如果必须用原生模块,确认它支持新架构(Fabric/TurboModules)
四、最终可用的技术栈
| 层 | 机器人端 | 手机端 |
|---|---|---|
| 传输 | FastAPI WebSocket (/ws) | React Native WebSocket |
| 发现 | HTTP /ping 端点 | 并发扫描子网 254 个 IP |
| 端口 | 8765 (TCP) | — |
| 协议 | JSON over WebSocket | JSON over WebSocket |
| 网络 | 手机热点 / 独立路由器 | 同上 |
五、排查流程图(以后遇到类似问题)
手机连不上机器人?
│
├─ Step 1: 确认同一子网
│ └─ 两边 IP 前三段一样吗?
│ ├─ 不一样 → 换网络(热点/路由器)
│ └─ 一样 → 继续
│
├─ Step 2: ping 测试
│ └─ ping <机器人IP>
│ ├─ 不通 → 检查 AP 隔离、防火墙
│ └─ 通 → 继续
│
├─ Step 3: 端口测试
│ └─ telnet/nc/Test-NetConnection <IP> <Port>
│ ├─ 不通 → 防火墙、服务没启动
│ └─ 通 → 继续
│
├─ Step 4: 用电脑 Python/curl 测试 WebSocket
│ └─ 能连吗?
│ ├─ 不能 → 服务端问题(看日志)
│ └─ 能 → 继续
│
├─ Step 5: 手机 App 测试
│ └─ 能连吗?
│ ├─ 不能 → Android cleartext 限制
│ │ 检查 usesCleartextTraffic
│ └─ 能 → 通了!
│
└─ 补充: 搜索/发现不工作?
├─ UDP 广播 → 热点可能不转发广播包
└─ HTTP 扫描 → 检查超时时间、并发数六、关键教训
- 分层排查,从底层往上: 物理网络 → IP 连通 → 端口可达 → 协议握手 → 应用层
- 用最简工具隔离问题: ping、telnet、Python 单行脚本,比直接调试 App 快 10 倍
- Android 安全策略是隐形杀手: 明文流量限制不会在服务端留下任何痕迹
- WebSocket ≠ TCP: 握手阶段是 HTTP,受 HTTP 头校验、代理、CSP 等影响
- 校园网/企业网不适合局域网直连: VLAN 隔离、AP 隔离都会阻止设备互通
- 选依赖看维护状态: 原生模块的 RN 版本兼容性是定时炸弹