Socket.IO React Native 示例:移动 App 通过 socket.io-client 连接实时服务器的完整实战指南
本文基于 socket.io 仓库中的 React Native 示例,完整讲解如何在 React Native 0.73 项目中接入 Socket.IO:从启动 Metro 与移动端 App、部署 Node 端 Socket.IO 服务器,到用 socket.io-client 建立连接、监听连接状态与传输层升级(polling 到 WebSocket 的切换)。读完本文,你将掌握一个可直接在 Android/iOS 模拟器上运行并验证实时通信链路的最小可用工程。
示例工程结构与版本依赖
该示例位于 examples/ReactNativeExample 目录,是一个用 @react-native-community/cli 引导生成的标准 React Native 工程,在此基础上叠加了 Socket.IO 客户端与服务器两部分代码:
- App.tsx:主界面,展示连接状态与当前使用的传输层;
- socket.js:单例 Socket.IO 客户端,指向局域网 IP 的服务器;
- server/index.js:独立的 Node 端 Socket.IO 服务器;
- index.js 与 app.json:React Native 入口注册与应用名配置;
- metro.config.js:Metro 打包器默认配置;
- __tests__/App.test.tsx:App 组件的 Jest 渲染测试。
从 package.json 可以看到关键版本约束,这也是本示例可运行的适用前提:
| 依赖 | 版本 | 说明 |
|---|---|---|
| react-native | 0.73.6 | React Native 运行时 |
| react | 18.2.0 | React 版本 |
| socket.io-client | ^4.7.5 | 客户端实时通信库 |
| typescript | 5.0.4 | 配合 @react-native/typescript-config 使用 |
| node | >=18 | engines 字段声明的 Node 版本下限 |
服务端的 server/package.json 单独声明了 socket.io: ^4.7.5,并将 "type": "module" 设为 ESM 模式,因此服务器代码使用 import 语法。
Step 1:启动 Metro 服务器
示例的 README 要求先完成 React Native 官方环境搭建(至"创建新应用"步骤)。随后,在工程根目录启动 Metro——React Native 内置的 JavaScript 打包器:
# using npm
npm start
# OR using Yarn
yarn start
npm start 实际执行的是 package.json 中的 react-native start 脚本,Metro 会监听文件变化并为模拟器/设备提供 JS bundle。注意:此终端需要保持运行。
Step 2:启动 Android 或 iOS 应用
让 Metro 在独立终端中运行,另开一个终端,从工程根目录启动目标平台的 App:
Android
# using npm
npm run android
# OR using Yarn
yarn android
iOS
# using npm
npm run ios
# OR using Yarn
yarn ios
对应的 package.json 脚本分别为 react-native run-android 与 react-native run-ios。配置正确时,App 应很快出现在 Android 模拟器或 iOS 模拟器中;当然,也可以在 Android Studio 或 Xcode 中直接运行。
Step 3:启动 Socket.IO 服务器
这是示例中最关键的一步,README 给出的操作是:
cd server
npm install
npm start
npm start 执行 node index.js,服务端代码非常精简(server/index.js):
import { Server } from 'socket.io';
const io = new Server();
io.on('connection', (socket) => {
console.log(`connect: ${socket.id}`, socket.request.headers);
socket.on('disconnect', () => {
console.log(`disconnect: ${socket.id}`);
});
});
io.listen(3000);
它完成三件事:
new Server()创建 Socket.IO 服务器实例;- 监听
connection事件,打印客户端 socket id 与请求头,客户端断开时打印disconnect; io.listen(3000)独立监听 3000 端口(不挂载到 Express 等 HTTP 框架上)。
这样客户端每连入/断开一个连接,终端都会出现形如 connect: <socket.id> 的日志,可以作为服务端验证握手成功的依据。
Step 4:客户端连接——为什么必须使用局域网 IP
socket.js 是整个示例客户端的核心:
import { io } from 'socket.io-client';
export const socket = io('http://192.168.0.10:3000'); // use the IP address of your machine
这里有两个值得注意的技术点:
- 必须替换为开发机的 IP 地址(注释明确要求 "use the IP address of your machine")。因为 Android 模拟器与 iOS 模拟器运行在独立网络栈中,从模拟器访问
localhost:3000指向的是模拟器自身而非宿主机,所以必须使用宿主机在局域网中的可达 IP(如示例中的192.168.0.10)。 - 单例导出:
socket以模块顶层单例形式导出,整个 App 共享同一个 Socket.IO 连接与底层的 Engine.IO 引擎实例。
App.tsx 通过 import { socket } from './socket' 引入该单例并挂载事件监听(App.tsx):
const [isConnected, setIsConnected] = useState(false);
const [transport, setTransport] = useState('N/A');
useEffect(() => {
if (socket.connected) {
onConnect();
}
function onConnect() {
setIsConnected(true);
setTransport(socket.io.engine.transport.name);
socket.io.engine.on('upgrade', (transport) => {
setTransport(transport.name);
});
}
function onDisconnect() {
setIsConnected(false);
setTransport('N/A');
}
socket.on('connect', onConnect);
socket.on('disconnect', onDisconnect);
return () => {
socket.off('connect', onConnect);
socket.off('disconnect', onDisconnect);
};
}, []);
return (
<View style={styles.container}>
<Text>Status: { isConnected ? 'connected' : 'disconnected' }</Text>
<Text>Transport: { transport }</Text>
</View>
);
界面最终只显示两行信息:连接状态(connected / disconnected)和当前传输层名称(Transport: <name>)。这段代码是理解 Socket.IO 客户端连接模型的一个非常好的最小样本:
- 先判断再监听:
socket是单例,useEffect执行时连接可能已建立,所以先用socket.connected判断并立即调用onConnect(),避免错过connect事件; - 连接状态由
connect/disconnect事件驱动:connect置为已连接,disconnect复位为未连接并把传输层清为N/A; - 传输层通过
socket.io.engine.transport.name读取:socket.io是Manager,其engine是 Engine.IO 的Socket实例,transport即当前生效的底层传输; - 监听
upgrade事件感知传输层切换:当 Engine.IO 从长轮询升级到 WebSocket 时,会触发upgrade事件并携带新的 transport 对象,示例据此实时更新界面显示的传输层名称; - 组件卸载时解绑:
useEffect的清理函数用socket.off移除两个监听器,防止 React 组件重挂载造成监听器泄漏。
传输层升级机制的源码佐证
upgrade 事件并非 Socket.IO 层的事件,而是来自底层的 Engine.IO 客户端。从 engine.io-client 的 Socket 实现 可以看到:
upgrade?: boolean选项控制"客户端是否应尝试将传输层从当前类型升级到 WebSocket";- Engine.IO 的
Socket定义了upgrade: (transport: Transport) => void事件,并有对应的upgradeError事件; upgrades: string[]表示可升级到的传输层列表,默认连接流程会先建立 polling 连接,再按升级路径尝试 WebSocket(见 socket.ts 中 upgrade 相关注释 与事件定义)。
因此在示例 UI 中,你通常会看到 Transport 先显示 polling(XHR 长轮询),随后在升级到 WebSocket 后刷新为 websocket——这正是 App.tsx 里监听 upgrade 事件的意义。
修改 App 并热重载(Step 4)
README 的最后一步是修改代码并观察热重载:
- 用文本编辑器打开
App.tsx修改内容; - Android:按两次
R键,或从开发者菜单选择 Reload(Windows/Linux 为Ctrl+M,macOS 为Cmd+M); - iOS:在模拟器中按
Cmd+R重新加载应用。
由于 Metro 在 Step 1 中一直在运行,保存 App.tsx 后重新加载即可看到 Socket.IO 连接信息与界面变化。此外 App.test.tsx 提供了一个最简的 Jest 渲染测试(renderer.create(<App />)),可用 npm test 执行,验证组件能被渲染。
运行前提与实操注意事项
- Node 版本:客户端工程 package.json 声明
"node": ">=18",请确保 Node 18+; - 服务器端口:server/index.js 固定监听 3000 端口,若被占用需自行调整并同步修改 socket.js 中的 URL;
- IP 必须可达:
socket.js中的192.168.0.10只是占位示例,必须替换为运行服务器的开发机局域网 IP,否则模拟器无法建立连接(这是移动端调试与浏览器调试最常见的差异点); - 两端版本对齐:客户端
socket.io-client ^4.7.5与服务端socket.io ^4.7.5均为 4.x 系列,遵循仓库 docs/socket.io-protocol 中描述的 v5 协议世代(Socket.IO 4.x 客户端/服务端协议),跨大版本混用需自行验证兼容性。
小结
这个示例的价值在于用最少的代码串通了"React Native 端 + Node 端"的完整实时通信链路:server/index.js 展示了 Socket.IO 服务器的最小独立部署方式,socket.js 展示了移动端连接单例的建立方式与局域网 IP 的使用要求,App.tsx 则演示了如何读取连接状态、当前传输层,以及如何通过 Engine.IO 的 upgrade 事件观察"轮询升级到 WebSocket"这一 Socket.IO 的默认传输层策略。按 README 的四个步骤(Metro → 移动端 App → Socket.IO 服务器 → 修改与热重载)操作,即可在模拟器上得到一个可验证收发链路的工作示例。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00