Skip to main content
Version: 4.x

房间

房间是一个任意通道,套接字可以是 joinleave。它可用于向一部分客户端广播事件:

¥A room is an arbitrary channel that sockets can join and leave. It can be used to broadcast events to a subset of clients:

Broadcasting to all clients in a roomBroadcasting to all clients in a room
info

请注意,房间是仅服务器的概念(即客户端无权访问其已加入的房间列表)。

¥Please note that rooms are a server-only concept (i.e. the client does not have access to the list of rooms it has joined).

加入和离开

¥Joining and leaving

你可以调用 join 将套接字订阅给定通道:

¥You can call join to subscribe the socket to a given channel:

io.on("connection", (socket) => {
socket.join("some room");
});

然后在广播或触发时只需使用 toin (它们是相同的):

¥And then simply use to or in (they are the same) when broadcasting or emitting:

io.to("some room").emit("some event");

或排除房间:

¥Or exclude a room:

io.except("some room").emit("some event");

你还可以同时向多个房间发送:

¥You can also emit to several rooms at the same time:

io.to("room1").to("room2").to("room3").emit("some event");

在这种情况下,执行 union:至少在一个房间中的每个套接字都会收到该事件一次(即使该套接字位于两个或更多房间中)。

¥In that case, a union is performed: every socket that is at least in one of the rooms will get the event once (even if the socket is in two or more rooms).

你还可以从给定的套接字向房间广播:

¥You can also broadcast to a room from a given socket:

io.on("connection", (socket) => {
socket.to("some room").emit("some event");
});

在这种情况下,房间中除发送者之外的每个套接字都将收到该事件。

¥In that case, every socket in the room excluding the sender will get the event.

Broadcasting to all clients in a room excepting the senderBroadcasting to all clients in a room excepting the sender

要离开通道,你可以按照与 join 相同的方式调用 leave

¥To leave a channel you call leave in the same fashion as join.

示例用例

¥Sample use cases

  • 将数据广播到给定用户的每个设备/选项卡

    ¥broadcast data to each device / tab of a given user

function computeUserIdFromHeaders(headers) {
// to be implemented
}

io.on("connection", async (socket) => {
const userId = await computeUserIdFromHeaders(socket.handshake.headers);

socket.join(userId);

// and then later
io.to(userId).emit("hi");
});
  • 发送有关给定实体的通知

    ¥send notifications about a given entity

io.on("connection", async (socket) => {
const projects = await fetchProjects(socket);

projects.forEach(project => socket.join("project:" + project.id));

// and then later
io.to("project:4321").emit("project updated");
});

断开

¥Disconnection

断开连接后,套接字 leave 自动连接它们所属的所有通道,你无需进行特殊拆卸。

¥Upon disconnection, sockets leave all the channels they were part of automatically, and no special teardown is needed on your part.

你可以通过监听 disconnecting 事件来获取 Socket 所在的房间:

¥You can fetch the rooms the Socket was in by listening to the disconnecting event:

io.on("connection", socket => {
socket.on("disconnecting", () => {
console.log(socket.rooms); // the Set contains at least the socket ID
});

socket.on("disconnect", () => {
// socket.rooms.size === 0
});
});

使用多个 Socket.IO 服务器

¥With multiple Socket.IO servers

全局广播 一样,向房间广播也适用于多个 Socket.IO 服务器。

¥Like global broadcasting, broadcasting to rooms also works with multiple Socket.IO servers.

你只需要将默认的 适配器 替换为 Redis Adapter 即可。有关它的更多信息 此处

¥You just need to replace the default Adapter by the Redis Adapter. More information about it here.

Broadcasting to all clients in a room with RedisBroadcasting to all clients in a room with Redis

实现细节

¥Implementation details

"room" 功能是通过我们所说的适配器来实现的。该适配器是一个服务器端组件,负责:

¥The "room" feature is implemented by what we call an Adapter. This Adapter is a server-side component which is responsible for:

  • 存储 Socket 实例和房间之间的关系

    ¥storing the relationships between the Socket instances and the rooms

  • 向所有(或一部分)客户端广播事件

    ¥broadcasting events to all (or a subset of) clients

你可以找到默认内存适配器 此处 的代码。

¥You can find the code of the default in-memory adapter here.

基本上,它由两个 ES6 映射 组成:

¥Basically, it consists in two ES6 Maps:

  • sidsMap<SocketId, Set<Room>>

  • roomsMap<Room, Set<SocketId>>

调用 socket.join("the-room") 将导致:

¥Calling socket.join("the-room") will result in:

  • sids Map 中,将 "the-room" 添加到由套接字 ID 标识的 Set 中

    ¥in the sids Map, adding "the-room" to the Set identified by the socket ID

  • rooms Map 中,添加由字符串 "the-room" 标识的 Set 中的套接字 ID

    ¥in the rooms Map, adding the socket ID in the Set identified by the string "the-room"

然后在广播时使用这两个映射:

¥Those two maps are then used when broadcasting:

  • 向所有套接字(io.emit())的广播循环遍历 sids Map,并将数据包发送到所有套接字

    ¥a broadcast to all sockets (io.emit()) loops through the sids Map, and send the packet to all sockets

  • 到给定房间 (io.to("room21").emit()) 的广播循环遍历 rooms 映射中的 Set,并将数据包发送到所有匹配的套接字

    ¥a broadcast to a given room (io.to("room21").emit()) loops through the Set in the rooms Map, and sends the packet to all matching sockets

你可以通过以下方式访问这些对象:

¥You can access those objects with:

// main namespace
const rooms = io.of("/").adapter.rooms;
const sids = io.of("/").adapter.sids;

// custom namespace
const rooms = io.of("/my-namespace").adapter.rooms;
const sids = io.of("/my-namespace").adapter.sids;

注意:

¥Notes:

  • 这些对象不应该直接修改,你应该始终使用 socket.join(...)socket.leave(...) 代替。

    ¥those objects are not meant to be directly modified, you should always use socket.join(...) and socket.leave(...) instead.

  • multi-server 设置中,roomssids 对象不在 Socket.IO 服务器之间共享(房间在一台服务器上只能有 "exist",而不能在另一台服务器上)。

    ¥in a multi-server setup, the rooms and sids objects are not shared between the Socket.IO servers (a room may only "exist" on one server and not on another).

房间事件

¥Room events

socket.io@3.1.0 开始,底层适配器将触发以下事件:

¥Starting with socket.io@3.1.0, the underlying Adapter will emit the following events:

  • create-room(参数:房间)

    ¥create-room (argument: room)

  • delete-room(参数:房间)

    ¥delete-room (argument: room)

  • join-room(参数:房间、id)

    ¥join-room (argument: room, id)

  • leave-room(参数:房间、id)

    ¥leave-room (argument: room, id)

示例:

¥Example:

io.of("/").adapter.on("create-room", (room) => {
console.log(`room ${room} was created`);
});

io.of("/").adapter.on("join-room", (room, id) => {
console.log(`socket ${id} has joined room ${room}`);
});