房间

sockets可以joinleave房间。它可用于向一部分客户端广播事件:

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

房间 - 图3信息

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

加入和离开

您可以调用join以将socket订阅到给定的频道:

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

然后在广播或发射时简单地使用toin(它们是相同的):

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

您可以同时发射到多个房间:

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

在这种情况下,将执行联合):至少在其中一个房间中的每个socket都将获得一次事件(即使socket在两个或更多房间中)。

您还可以从给定的socket广播到房间:

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

在这种情况下,房间中发送者之外的每个socket都会收到该事件。

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

要离开频道,您调用leave的方式与join相同。

默认房间

Socket.IO 中的每一个socket都由一个随机的、不可猜测的、唯一的标识符Socket#id。为了您的方便,每个socket都会自动加入一个由其自己的 id 标识的房间。

这使得实现私人消息变得容易:

  1. io.on("connection", (socket) => {
  2. socket.on("private message", (anotherSocketId, msg) => {
  3. socket.to(anotherSocketId).emit("private message", socket.id, msg);
  4. });
  5. });

示例用例

  • 向给定用户的每个设备/选项卡广播数据
  1. io.on("connection", async (socket) => {
  2. const userId = await computeUserIdFromHeaders(socket);
  3. socket.join(userId);
  4. // and then later
  5. io.to(userId).emit("hi");
  6. });
  • 发送有关给定实体的通知
  1. io.on("connection", async (socket) => {
  2. const projects = await fetchProjects(socket);
  3. projects.forEach(project => socket.join("project:" + project.id));
  4. // and then later
  5. io.to("project:4321").emit("project updated");
  6. });

断开

断开连接后,leave会自动将它们所属的所有通道连接起来,您不需要进行特殊的拆卸。

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

  1. io.on("connection", socket => {
  2. socket.on("disconnecting", () => {
  3. console.log(socket.rooms); // the Set contains at least the socket ID
  4. });
  5. socket.on("disconnect", () => {
  6. // socket.rooms.size === 0
  7. });
  8. });

使用多个 Socket.IO 服务器

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

您只需要将默认的Adapter替换为 Redis Adapter。更多关于它的信息在这里

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

实施细节

“房间”功能由我们称为适配器的东西实现。该适配器是一个服务器端组件,负责:

  • 存储 Socket 实例和房间之间的关系
  • 向所有(或部分)客户端广播事件

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

基本上,它包含两个ES6 Maps:

  • sids: Map<SocketId, Set<Room>>
  • rooms: Map<Room, Set<SocketId>>

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

  • sids Map中,将“the-room”添加到由Socket ID 标识的 Set
  • rooms Map 中,将Socket ID 添加到由字符串“the-room”标识的 Set 中

然后在广播时使用这两个地图:

  • 对所有套接字的广播(io.emit())循环通过sidsMap,并将数据包发送到所有sockets
  • 对给定房间的广播 ( io.to("room21").emit())循环通过roomsMap 中的 Set,并将数据包发送到所有匹配的sockets

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

  1. // main namespace
  2. const rooms = io.of("/").adapter.rooms;
  3. const sids = io.of("/").adapter.sids;
  4. // custom namespace
  5. const rooms = io.of("/my-namespace").adapter.rooms;
  6. const sids = io.of("/my-namespace").adapter.sids;

笔记:

  • 这些对象并不意味着直接修改,您应该始终使用socket.join(…)socket.leave(…)来代替。
  • 多服务器设置中,roomssids对象不会在 Socket.IO 服务器之间共享(房间可能只“存在”在一个服务器上而不是另一个服务器上)。

房间事件

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

  • create-room (argument: room)
  • delete-room (argument: room)
  • join-room (argument: room, id)
  • leave-room (argument: room, id)

例子:

  1. io.of("/").adapter.on("create-room", (room) => {
  2. console.log(`room ${room} was created`);
  3. });
  4. io.of("/").adapter.on("join-room", (room, id) => {
  5. console.log(`socket ${id} has joined room ${room}`);
  6. });