关于 web-realtime-socket-io
面向开发者的 Socket.IO 客户端实践指南,涵盖类型化事件、身份验证、确认送达、房间、命名空间与连接恢复。内容侧重客户端模式,并说明其与原生 WebSocket 的差异。
主要功能
- 01类型化客户端与服务端事件契约
- 02重连时刷新身份凭据
- 03带确认的发送与重试
- 04房间与命名空间管理
- 05连接恢复与生命周期处理
适用场景
- 01实现类型化实时消息
- 02重连后刷新凭据
- 03发送需要确认的事件
- 04分离聊天与管理通道
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ agents-inc/ skills --skill web-realtime-socket-io安装后在 Claude Code 中输入/web-realtime-socket-io确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
metadata.yamlreference.mdexamples/authentication.mdcore.mdrooms.md详细介绍
Socket.IO 实时通信模式
快速指南: Socket.IO 是构建在 WebSocket 之上的协议,并非 WebSocket 的一种实现——它的客户端和普通 WebSocket 服务端无法双向通信。这一层带来的功能包括传输回退、自动重连、房间、命名空间和确认机制,压缩后约为 14.5KB。会影响选择的事实包括:
auth接受一个每次重连时都会重新运行的函数,timeout和ackTimeout是两种不同的计时器,而socket.recovered(v4.6.0+)会告知你错过的事件是否已重放,还是需要完整刷新状态。
详细资源:
- examples/core.md — 类型化 socket 工厂、连接与事件钩子、带确认的发送、离线队列、易失性事件、Manager 复用
- examples/authentication.md — 令牌认证、重连时刷新、按命名空间认证、Cookie 认证、认证状态机
- examples/rooms.md — 房间管理器与钩子、多房间聊天、命名空间 socket、有条件的命名空间访问
- reference.md — 客户端选项、socket 和 Manager 事件、断开连接原因、对比表、检查清单
适用路径
- 连接到默认命名空间的一条连接 —
io(url, options)会同时创建 socket 和它的 Manager。这是 examples/core.md 中的模式,适用于大多数应用。 - 通过一条连接访问多个命名空间 — 自行构造
new Manager(url),并为每个命名空间调用manager.socket("/chat")。这些命名空间共享单个传输连接,也可以各自携带auth,从而实现按命名空间授权。请参阅 examples/rooms.md。
针对同一 URL 多次调用 io() 会打开第二条传输连接,而不是复用连接——由 Manager 负责共享同一连接。
<critical_requirements>
编写 Socket.IO 代码前
声明 ServerToClientEvents 和 ClientToServerEvents,并用它们为 socket 指定类型。 事件名称在运行时是字符串,因此这样能将永远不会触发的监听器 "mesage" 变成编译错误。
通过 auth 选项传递令牌。 令牌会在握手中传递,而不是放在 URL 中,因此不会出现在服务器日志、浏览器历史记录和代理日志里;而 auth 的函数形式会在每次重新连接时重新求值,避免刷新的令牌过期。
移除你添加的每个监听器,并使用相同的函数引用。 在 useEffect 清理函数中调用 socket.off(event, handler),可避免处理程序在重新渲染时不断叠加,并确保每次挂载只处理一条消息一次。
处理 connect_error 和 manager 的 reconnect_failed。 两者涵盖了用户可能遇到的两种故障,否则用户只会看到界面停止更新。
在 connect 后检查 socket.recovered(v4.6.0+)。 它能告知服务器是否重放了错过的事件,或客户端是否需要完整刷新状态。
</critical_requirements>
自动检测: socket.io-client、io()、Manager、manager.socket()、socket.emit、socket.on、socket.off、emitWithAck、socket.timeout()、ackTimeout、socket.volatile、socket.recovered、socket.active、connect_error、reconnect_attempt、reconnect_failed、ServerToClientEvents、ClientToServerEvents、autoConnect、reconnectionDelayMax
适用于:
- 需要投递确认的双向消息传递
- 用于定向广播的房间和命名空间
- 带凭据刷新的重新连接和连接状态恢复
- 需要轮询回退的受限网络
- 客户端与服务器之间的类型化事件契约
其他部分介绍:
- 令牌的来源和刷新方式——socket 会发送
auth返回的内容。 - 接收数据的存储方式和渲染方式——socket 钩子会返回消息和连接状态,仅此而已。
- 服务器自己的房间成员关系、命名空间中间件和连接状态恢复配置;本技能介绍客户端部分,并指出服务器端需要启用的功能。
- 不使用房间、确认或回退机制的双向通道——底层的原生 WebSocket API 才负责这一点,而此协议层封装了它,并不会直接暴露它。
Socket.IO 以增加包体积和协议兼容性为代价,提供了四种通常需要手动编写的功能:传输回退、重新连接、服务器端分组和确认。
- 传输方式是抽象的。 客户端先通过 HTTP 长轮询建立连接,待 WebSocket 可用后再升级,因此即使网络阻止升级,也会降级运行而非失败。
- 房间是服务器端概念。 客户端请求加入,由服务器决定;客户端永远不会获知自己加入了哪些房间。
- 命名空间属于协议层。 客户端显式连接到
/chat或/admin,各自拥有独立的中间件和auth,并共享同一传输。 - 连接状态恢复(v4.6.0+) 会重放短暂断连期间错过的事件,恢复窗口由服务器配置,默认值为两分钟。
CONNECTING -> CONNECTED <-> (events) -> DISCONNECTING -> DISCONNECTED
| |
(error) <- reconnect <- (disconnect)
<decision_framework>
命名空间还是房间
What is being separated?
+-- A distinct feature area, with its own auth or middleware?
| -> namespace — the client connects to it, e.g. /chat, /admin
+-- A set of users inside one feature, for targeted broadcast?
| -> room — server-side only, joined on request
+-- Neither — one channel for everything?
-> the default namespace "/"
如何进行身份验证
auth 选项适用于令牌场景,其函数形式可确保令牌在重新连接时保持最新。对于会话 Cookie,请设置 withCredentials: true 并将 auth 留空——服务器的 CORS 配置必须允许凭据,这样才能正常工作。如果某个命名空间需要更高权限,请通过 manager.socket("/admin", { auth }) 为该命名空间单独设置 auth,而不是在应用代码中进行限制。
选择哪种投递保证
普通的 emit 是即发即弃。如果发送方需要确认消息已送达,请添加确认:单次调用使用 socket.timeout(ms).emitWithAck(...);或者使用 ackTimeout 配合 retries(v4.6.0+),让客户端自行重传——这意味着服务端处理程序必须具备幂等性,因为同一个数据包可能会到达两次。对于后续更新会取代当前值的数据——例如光标位置、在线状态 ping——socket.volatile.emit() 会丢弃数据,而不是将其排队。
发送二进制数据
二进制负载可直接放在事件中;协议会序列化 ArrayBuffer、Buffer 和 Blob,包括与 JSON 混合的对象中的这些类型。大文件需要自行分块——先发送元数据,再发送带确认的分块,这样就能报告进度,并在失败后恢复传输。
</decision_framework>
核心模式
模式 1:类型化事件接口
v4 会在编译时强制检查两个方向的类型。
interface ServerToClientEvents {
"message:received": (message: ChatMessage) => void;
error: (error: SocketError) => void;
}
interface ClientToServerEvents {
"message:send": (content: string, ack: (res: SendResult) => void) => void;
}
type TypedSocket = Socket<ServerToClientEvents, ClientToServerEvents>;
完整代码:examples/core.md
模式 2:客户端配置
timeout 是连接超时时间,默认值为 20000ms。ackTimeout 是每次 emit 的确认计时器,需要与 retries 一同配置——尽管名称相似,两者并无关联。
const socket: TypedSocket = io(url, {
auth: { token },
reconnectionAttempts: MAX_RECONNECTION_ATTEMPTS,
reconnectionDelay: RECONNECTION_DELAY_MS,
timeout: CONNECTION_TIMEOUT_MS,
transports: ["websocket", "polling"],
});
完整代码:examples/core.md
模式 3:连接生命周期
Socket 级事件描述 Socket;Manager 级事件(socket.io)描述底层连接。只有后一组事件会显示重连进度。
socket.on("connect", () => {
if (!socket.recovered) refetchState();
});
socket.on("disconnect", (reason) => {
// socket.active === true means a reconnect is already scheduled
});
socket.on("connect_error", (error) => showError(error));
socket.io.on("reconnect_attempt", (attempt) => showReconnecting(attempt));
socket.io.on("reconnect_failed", () => showPermanentFailure());
完整代码:examples/core.md
模式 4:确认
两种形式,一种保证。emitWithAck 会等待单次响应;ackTimeout 配合 retries 会自动重传。
const response = await socket
.timeout(ACK_TIMEOUT_MS)
.emitWithAck("message:send", content);
// or: let the client retry, and make the server handler idempotent
const socket = io(url, { ackTimeout: ACK_TIMEOUT_MS, retries: MAX_RETRIES });
socket.emit("message:send", content, (response) => confirm(response));
完整代码:examples/core.md
模式 5:身份验证令牌处理
auth 对象只会在构造时读取一次。每次尝试连接之前(包括重连时),都会调用 auth 函数——这就是会话能否在令牌刷新后继续维持的区别。
const socket = io(url, {
auth: (cb) => {
cb({ token: getToken() });
},
});
// or update it just before the retry
socket.io.on("reconnect_attempt", () => {
socket.auth = { token: getToken() };
});
完整代码:examples/authentication.md
模式 6:通过一个连接使用多个命名空间
Manager 管理传输层;每个命名空间的 Socket 都通过该连接传输,并且可以携带自己的凭据。
const manager = new Manager(url, { autoConnect: false });
const chatSocket = manager.socket("/chat");
const adminSocket = manager.socket("/admin", { auth: { token: adminToken } });
manager.connect();
完整代码:examples/rooms.md
模式 7:清理监听器
off 根据函数引用进行匹配,因此处理程序必须绑定到稳定的引用,而不能使用内联箭头函数。
useEffect(() => {
const handler = (msg: Message) => setMessages((prev) => [...prev, msg]);
socket.on("message", handler);
return () => {
socket.off("message", handler);
};
}, [socket]);
完整代码:examples/core.md
<red_flags>
风险信号
运行时会出错:
- 连接 URL 的查询字符串中包含令牌——它会出现在服务器日志、浏览器历史记录以及中间经过的每个代理中——请将其放入
auth。 - 调用了
socket.on(...),却没有对应的off——每次挂载都会累积处理程序,每条消息都会按累积的次数处理。 - 将内联箭头函数传给
off——它与注册时的引用不同,因此不会移除任何内容。 - 在未连接的套接字上调用
socket.emit()——消息会被静默丢弃——请检查socket.connected或将消息加入队列。 - 将
socket.id用作用户标识符——每次重新连接都会重新生成——请使用服务器签发的用户 ID 作为键。 - 长时间会话使用静态
auth对象——令牌会过期,之后每次重新连接都会被拒绝——请使用函数形式。 - 设置了
ackTimeout,却没有设置retries——它所依赖的重传行为永远不会启用。
容易引起意外的行为:
- 该协议与普通 WebSocket 客户端双向不兼容;无法使用此库连接非 Socket.IO 服务器。
- 默认传输顺序是先轮询,再升级——不是先使用套接字。
- 只有在服务器启用了连接状态恢复时,
socket.recovered才可能为 true。 - 多个命名空间共用一个传输,因此传输故障会导致它们全部同时断开。
- 客户端无法看到房间——没有客户端 API 可以报告它加入了哪些房间。
- 设计上,
volatile.emit()会在拥塞时丢弃消息;任何必须送达的消息都不应使用它。 - 启用
retries后,同一个数据包可能会被送达多次,因此服务器处理程序必须具备幂等性。