mcpField
W
web-realtime-socket-io

web-realtime-socket-io

github.com/agents-inc开发2669 次安装2 天前更新
代码仓库

关于 web-realtime-socket-io

面向开发者的 Socket.IO 客户端实践指南,涵盖类型化事件、身份验证、确认送达、房间、命名空间与连接恢复。内容侧重客户端模式,并说明其与原生 WebSocket 的差异。

主要功能

  1. 01类型化客户端与服务端事件契约
  2. 02重连时刷新身份凭据
  3. 03带确认的发送与重试
  4. 04房间与命名空间管理
  5. 05连接恢复与生命周期处理

适用场景

  1. 01实现类型化实时消息
  2. 02重连后刷新凭据
  3. 03发送需要确认的事件
  4. 04分离聊天与管理通道

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/agents-inc/skills --skill web-realtime-socket-io

安装后在 Claude Code 中输入/web-realtime-socket-io确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

metadata.yaml
reference.md
examples/
authentication.md
core.md
rooms.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 后,同一个数据包可能会被送达多次,因此服务器处理程序必须具备幂等性。

评论

支持 Markdown