房间
房间是一个任意通道,套接字可以是 join
和 leave
。它可用于向一部分客户端广播事件:
¥A room is an arbitrary channel that sockets can join
and leave
. It can be used to broadcast events to a subset of clients:
请注意,房间是仅服务器的概念(即客户端无权访问其已加入的房间列表)。
¥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");
});
然后在广播或触发时只需使用 to
或 in
(它们是相同的):
¥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.
要离开通道,你可以按照与 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.
实现细节
¥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:
sids
:Map<SocketId, Set<Room>>
rooms
:Map<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 thesids
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 therooms
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(...)
andsocket.leave(...)
instead.在 multi-server 设置中,
rooms
和sids
对象不在 Socket.IO 服务器之间共享(房间在一台服务器上只能有 "exist",而不能在另一台服务器上)。¥in a multi-server setup, the
rooms
andsids
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}`);
});