Socket.IO 是一个用于实现客户端与服务器之间“低延迟、双向、基于事件通信”的开源库。
可以把它简单理解为:
Socket.IO = WebSocket 通信能力 + 心跳检测 + 自动重连 + 事件系统 + 房间广播 + 断线处理等工程化能力。
它特别适合聊天室、在线会议状态、实时通知、协同编辑、在线客服、设备状态监控等需要服务器主动向浏览器推送数据的场景。
Socket.IO 与 WebSocket 的关系
Socket.IO 经常使用 WebSocket 传输数据,但它并不等于 WebSocket。
| 对比项 | 原生 WebSocket | Socket.IO |
|---|---|---|
| 定位 | 浏览器原生通信协议/API | 基于事件的实时通信库 |
| 消息形式 | 字符串或二进制数据 | 自定义事件与参数 |
| 心跳检测 | 需要自己实现 | 内置 |
| 自动重连 | 需要自己实现 | 内置 |
| 重连退避 | 需要自己实现 | 内置指数退避 |
| HTTP 长轮询降级 | 不支持 | 支持 |
| 消息确认 ACK | 需要自己设计 | 内置 |
| 房间与广播 | 需要自己实现 | 内置 |
| 多路复用 | 需要自己实现 | 支持 Namespace |
| 协议兼容 | 标准 WebSocket | Socket.IO 自有协议 |
特别需要注意:
- 原生 WebSocket 客户端不能直接连接 Socket.IO 服务器。
- Socket.IO 客户端也不能直接连接普通 WebSocket 服务器。
- 因为 Socket.IO 会在底层数据上增加自己的协议元数据。官方文档说明
例如:
// 这通常无法连接普通 WebSocket 服务constsocket=io("wss://example.com/websocket");只有服务端也是 Socket.IO 协议时才能正常连接。
它是怎么工作的
Socket.IO 可以分为两层:
一般连接过程是:
- 客户端首先与服务器握手。
- 获取会话 ID、心跳间隔和超时时间。
- 默认情况下可能先通过 HTTP 长轮询建立连接。
- 检测环境支持后,尝试升级为 WebSocket。
- 连接期间通过 Ping/Pong 检测连接是否仍然有效。
- 连接断开后,客户端自动尝试重新连接。
握手数据大致如下:
{"sid":"FSDjX-WRwSA4zTZMALqx","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":20000}其中:
sid:当前连接的会话 IDupgrades:可以升级到的传输方式pingInterval:服务器发送 Ping 的时间间隔pingTimeout:等待 Pong 的超时时间
这是 Socket.IO 降低 WebSocket“假连接”问题的重要机制。运作原理
核心功能
1. 基于事件通信
使用原生 WebSocket 时,通常需要自己约定消息格式:
websocket.send(JSON.stringify({type:"meeting-status",data:{status:"started"}}));Socket.IO 可以直接发送事件:
socket.emit("meeting-status",{status:"started"});接收事件:
socket.on("meeting-status",(data)=>{console.log(data.status);});这样业务代码更加直观,不需要自己根据type分发消息。
2. 自动重连
如果网络切换、服务器重启、代理超时或浏览器暂时卡顿导致连接断开,Socket.IO 客户端会自动重连,并采用退避策略避免大量客户端同时冲击服务器。
constsocket=io("https://example.com",{reconnection:true,reconnectionAttempts:10,reconnectionDelay:1000,reconnectionDelayMax:5000});但自动重连只负责“重新建立连接”,不意味着断线期间的业务数据一定能够恢复。
3. 心跳检测
Socket.IO 内置 Ping/Pong 心跳机制。
如果服务器没有按时收到客户端的 Pong,或者客户端长期没有收到服务器的 Ping,连接就会被判断为断开,从而避免连接实际上已经失效,但前端仍显示“已连接”的情况。
不过,如果浏览器 JS 主线程长时间卡住、电脑休眠或后台页面被严重节流,心跳也可能无法及时处理,从而触发断线。
4. ACK 消息确认
发送方可以要求接收方返回确认结果:
socket.timeout(5000).emit("upload-audio",{meetingId:"123",chunkId:"chunk-001"},(error,response)=>{if(error){console.log("5 秒内没有收到确认");return;}console.log("服务器已确认",response);});服务端:
socket.on("upload-audio",async(data,callback)=>{awaitsaveAudio(data);callback({success:true});});ACK 很适合你之前提到的音频 PCM 数据发送场景:
- 前端发送初始化请求。
- 等待服务器 ACK。
- ACK 前产生的 PCM 数据进入 FIFO。
- 收到 ACK 后按顺序发送。
- 每个关键数据包可以继续使用 ACK 或序列号确认。
5. 房间 Room
可以把不同客户端加入不同房间:
io.on("connection",(socket)=>{socket.on("join-meeting",(meetingId)=>{socket.join(`meeting:${meetingId}`);});});只向特定会议里的用户广播:
io.to("meeting:123").emit("transcription",{text:"这是实时转写内容"});常见用途包括:
- 一个会议对应一个房间
- 一个项目对应一个房间
- 一个用户对应一个私人房间
- 一个部门对应一个通知房间
6. Namespace 命名空间
Namespace 可以在同一条底层连接上划分多个逻辑通道:
constmeetingSocket=io("/meeting");constnotificationSocket=io("/notification");constadminSocket=io("/admin");适合将会议、通知和后台管理等业务模块隔离。
7. 断线状态恢复
Socket.IO 支持连接状态恢复,可以在短暂断线后尝试恢复:
- Socket ID
- 加入的房间
- Socket 上保存的数据
- 断线期间错过的部分数据包
服务端需要开启:
constio=newServer(httpServer,{connectionStateRecovery:{maxDisconnectionDuration:2*60*1000,skipMiddlewares:true}});客户端重新连接后可以判断:
socket.on("connect",()=>{if(socket.recovered){console.log("连接状态恢复成功");}else{console.log("需要重新同步完整状态");}});恢复并不保证每次都成功,因此业务仍然需要准备“重新拉取完整状态”的方案。连接状态恢复
一个最小使用示例
安装服务端:
npminstallsocket.io安装客户端:
npminstallsocket.io-clientNode.js 服务端:
import{createServer}from"node:http";import{Server}from"socket.io";consthttpServer=createServer();constio=newServer(httpServer,{cors:{origin:"http://localhost:5173"}});io.on("connection",(socket)=>{console.log("客户端连接:",socket.id);socket.on("message",(message,callback)=>{console.log("收到消息:",message);io.emit("message",message);callback?.({success:true});});socket.on("disconnect",(reason)=>{console.log("连接断开:",reason);});});httpServer.listen(3000);前端:
import{io}from"socket.io-client";constsocket=io("http://localhost:3000");socket.on("connect",()=>{console.log("连接成功:",socket.id);socket.emit("message",{text:"你好"},(response)=>{console.log("服务器确认:",response);});});socket.on("message",(message)=>{console.log("收到消息:",message);});socket.on("disconnect",(reason)=>{console.log("连接断开:",reason);});socket.on("connect_error",(error)=>{console.log("连接失败:",error.message);});需要特别注意的消息可靠性
Socket.IO 能保证已经到达的消息顺序,但默认消息送达语义是“最多一次”。
也就是说:
- 消息发送过程中突然断线,不能确定服务端是否收到。
- 客户端重新连接后,这条正在发送的消息不会自动重试。
- 客户端离线期间,服务端发出的消息默认不会自动补发。
- ACK 超时重试可能导致服务器收到重复消息。
因此,关键业务还应加入:
唯一消息 ID + 序列号 + ACK + 超时重试 + 服务端幂等处理 + 数据持久化 + 重连后的缺失数据补偿例如音频分片、会议状态变更、支付或重要指令,都不能只依赖 Socket.IO 的自动重连。消息可达性保证
优点和缺点
优点:
- 对 WebSocket 进行了完整的工程化封装
- 自动处理心跳、断线和重连
- 事件式 API 容易开发
- Room、Namespace 和广播能力完善
- 支持 Node.js、浏览器、React Native,以及多种第三方语言客户端
- 非常适合中小型实时应用快速开发
缺点:
- 不是标准 WebSocket 协议,前后端必须使用兼容的 Socket.IO 实现
- 比原生 WebSocket 多一层协议和少量消息开销
- 自动重连不等于业务数据绝对可靠
- 多服务器部署需要配置 Adapter、负载均衡等
- 如果只需要极简、高吞吐的二进制数据流,原生 WebSocket 可能更合适
- 不适合作为移动应用长期后台推送方案,后台通知更适合 APNs、FCM 等系统