Socket.IO
很多团队第一次做实时能力时,会先在两个方向里选一个:
- 直接用原生
WebSocket - 用
Socket.IO
这两条路线都能做实时通信,但关注点不一样。
- 原生
WebSocket更轻,更贴近协议本身 Socket.IO更像一层更完整的实时通信框架,帮前后端补上房间、事件、自动重连、确认回调等常见能力
先说结论:它不是 WebSocket 的简单别名
Socket.IO 底层常会优先使用 WebSocket,但它不是浏览器原生 WebSocket API 的直接包装。
它额外提供了:
- 事件模型
- 自动重连
- 心跳管理
- rooms
- namespaces
- ack 回调
- 中间件
- 更完整的客户端和服务端协作方式
- 在某些环境下的降级能力
所以更稳的理解是:
WebSocket是协议和基础通道Socket.IO是一层更高的实时应用框架
什么时候更适合用 Socket.IO
更常见的场景通常是:
- 聊天
- 在线客服
- 协同看板
- 通知中心
- 多人房间
- 业务事件很多,前后端都想按事件名协作
- 团队更在意交付速度,而不是尽量贴近底层协议
如果需求只是:
- 单条稳定流式消息
- 极简双向通信
- 强控制协议细节
原生 WebSocket 仍然很值得优先考虑。
它在补什么短板
原生 WebSocket 真正难的地方,往往不在连上,而在连接之后:
- 怎么做自动重连
- 怎么做事件路由
- 怎么做房间广播
- 怎么做鉴权中间件
- 怎么做客户端确认
- 怎么做连接状态恢复
Socket.IO 这层就是在把这些高频工程问题标准化。
客户端最小示例
import { io } from 'socket.io-client'
const socket = io('https://example.com', {
transports: ['websocket'],
withCredentials: true,
})
socket.on('connect', () => {
console.log('connected', socket.id)
})
socket.on('message:new', (payload) => {
console.log(payload)
})
socket.emit('message:send', { text: 'hello' })
这里最直观的差异是:
- 不是自己处理原始 message 字符串
- 而是直接按事件名监听和发送
服务端最小示例
import { createServer } from 'node:http'
import { Server } from 'socket.io'
const httpServer = createServer()
const io = new Server(httpServer, {
cors: {
origin: 'http://localhost:3000',
credentials: true,
},
})
io.on('connection', (socket) => {
socket.on('message:send', (payload) => {
io.emit('message:new', payload)
})
})
httpServer.listen(8080)
事件模型为什么适合业务代码
Socket.IO 的日常心智模型更像“事件总线”:
message:sendmessage:newroom:joinroom:leftpresence:update
这在业务上很直观,因为前后端可以直接围绕事件名协作,而不是先定义一层通用 message 协议再自己分发。
emit 和 on 之外,更常用的是 ack
很多业务消息并不是“发了就完”,而是需要确认服务端有没有收到、有没有处理成功。
socket.emit('order:create', payload, (result) => {
console.log('server ack', result)
})
服务端:
socket.on('order:create', async (payload, ack) => {
const order = await createOrder(payload)
ack({ ok: true, orderId: order.id })
})
这很适合:
- 创建消息
- 确认状态
- 回包式实时操作
rooms 为什么是高频能力
聊天、协同、直播、多人看板这些场景里,消息很少是“发给所有人”。
更常见的是:
- 某个会话房间
- 某个项目空间
- 某个用户私有频道
Socket.IO 的 room 能力就很实用:
socket.join(`room:${roomId}`)
io.to(`room:${roomId}`).emit('message:new', payload)
常见用法:
- 房间消息广播
- 用户个人通知
- 同一用户多端同步
namespace 适合什么场景
namespace 比 room 更像“不同业务域的连接入口”。
例如:
/chat/admin/metrics
const chat = io.of('/chat')
const admin = io.of('/admin')
大多数业务先用 room 就够了。namespace 一般在:
- 权限边界明显不同
- 中间件和事件集合完全不同
- 想把连接入口拆开
时才更值得单独用。
鉴权通常怎么接
1. Cookie 会话
如果前后端同域或已处理跨域凭证,Cookie 会是比较省心的一种方式。
2. 握手时传 token
const socket = io('https://example.com', {
auth: {
token: accessToken,
},
})
服务端中间件:
io.use((socket, next) => {
const token = socket.handshake.auth.token
if (!token) return next(new Error('unauthorized'))
const user = verifyToken(token)
socket.data.user = user
next()
})