跳至主要内容
版本: 4.x

服务器 API

服务器

Server in the class diagram for the serverServer in the class diagram for the server

相关文档页面

构造函数

new Server(httpServer[, options])

import { createServer } from "http";
import { Server } from "socket.io";

const httpServer = createServer();
const io = new Server(httpServer, {
// options
});

io.on("connection", (socket) => {
// ...
});

httpServer.listen(3000);

可以在 这里 找到所有可用选项的完整列表。

new Server(port[, options])

import { Server } from "socket.io";

const io = new Server(3000, {
// options
});

io.on("connection", (socket) => {
// ...
});

可以在 这里 找到所有可用选项的完整列表。

new Server(options)

import { Server } from "socket.io";

const io = new Server({
// options
});

io.on("connection", (socket) => {
// ...
});

io.listen(3000);

可以在 这里 找到所有可用选项的完整列表。

事件

事件: 'connect'

事件: "connection" 的同义词。

事件: 'connection'

  • socket (Socket) 与客户端的套接字连接

在客户端连接时触发。

io.on("connection", (socket) => {
// ...
});

事件: 'new_namespace'

在创建新的命名空间时触发

io.on("new_namespace", (namespace) => {
// ...
});

这在以下情况下很有用

  • 将共享中间件附加到每个命名空间
io.on("new_namespace", (namespace) => {
namespace.use(myMiddleware);
});
io.of(/\/nsp-\w+/);

io.on("new_namespace", (namespace) => {
console.log(namespace.name);
});

属性

server.engine

对底层 Engine.IO 服务器的引用。见 这里

server.sockets

主命名空间 (/) 的别名。

io.sockets.emit("hi", "everyone");
// is equivalent to
io.of("/").emit("hi", "everyone");

方法

server.adapter([value])

设置适配器 value。默认为 socket.io 附带的 Adapter 实例,它是基于内存的。见 socket.io-adapter。如果没有提供参数,此方法将返回当前值。

import { Server } from "socket.io"; 
import { createAdapter } from "@socket.io/redis-adapter";
import { createClient } from "redis";

const io = new Server();

const pubClient = createClient({ host: "localhost", port: 6379 });
const subClient = pubClient.duplicate();

io.adapter(createAdapter(pubClient, subClient));

// redis@3
io.listen(3000);

// redis@4
Promise.all([pubClient.connect(), subClient.connect()]).then(() => {
io.listen(3000);
});

server.attach(httpServer[, options])

使用提供的 optionsServer 附加到 httpServer

import { createServer } from "http";
import { Server } from "socket.io";

const httpServer = createServer();
const io = new Server();

io.attach(httpServer);

io.on("connection", (socket) => {
// ...
});

httpServer.listen(3000);

server.attach(port[, options])

使用提供的 options 在给定的 port 上附加 Server

import { Server } from "socket.io";

const io = new Server();

io.attach(3000);

io.on("connection", (socket) => {
// ...
});

server.attachApp(app[, options])

将 Socket.IO 服务器附加到 µWebSockets.js 应用程序

import { App } from "uWebSockets.js";
import { Server } from "socket.io";

const app = App();
const io = new Server();

io.attachApp(app);

io.on("connection", (socket) => {
// ...
});

app.listen(3000, (token) => {
if (!token) {
console.warn("port already in use");
}
});

server.bind(engine)

仅供高级使用。将服务器绑定到特定的 engine.io Server(或兼容 API)实例。

import { Server } from "socket.io";
import { Server as Engine } from "engine.io";

const engine = new Engine();
const io = new Server();

io.bind(engine);

engine.listen(3000);

server.close([callback])

关闭 Socket.IO 服务器并断开所有客户端的连接。callback 参数是可选的,将在所有连接关闭时调用。

info

这也将关闭底层的 HTTP 服务器。

import { createServer } from "http";
import { Server } from "socket.io";

const PORT = 3030;
const io = new Server(PORT);

io.close();

const httpServer = createServer();

httpServer.listen(PORT); // PORT is free to use

io.attach(httpServer);
note

仅关闭底层的 HTTP 服务器是不够的,因为它只会阻止服务器接受新的连接,但使用 WebSocket 连接的客户端不会立即断开连接。

参考: https://node.org.cn/api/http.html#serverclosecallback

server.disconnectSockets([close])

在 v4.0.0 中添加

io.of("/").disconnectSockets(close) 的别名。

// make all Socket instances disconnect
io.disconnectSockets();

// make all Socket instances in the "room1" room disconnect (and close the low-level connection)
io.in("room1").disconnectSockets(true);
tip

此方法也适用于多个 Socket.IO 服务器的集群,并使用兼容的适配器,例如 Postgres 适配器

在这种情况下,如果您只想影响给定节点上的套接字实例,则需要使用 local 标志

// make all Socket instances that are currently connected on the given node disconnect
io.local.disconnectSockets();

这里

server.emit(eventName[, ...args])

历史
版本更改
v4.5.0io.emit() 现在支持确认。
v1.0.0初始实现。

向主命名空间中的所有连接的客户端发出事件。

io.emit("hello");

可以包含任意数量的参数,并且支持所有可序列化数据结构

io.emit("hello", 1, "2", { "3": 4 }, Buffer.from([5]));

在接收方

socket.on("hello", (arg1, arg2, arg3, arg4) => {
console.log(arg1); // 1
console.log(arg2); // "2"
console.log(arg3); // { "3": 4 }
console.log(arg4); // ArrayBuffer or Buffer, depending on the platform
});
info

参数将自动序列化,因此不需要调用 JSON.stringify()

您可以使用 to()except() 将数据包发送到特定客户端

// the “hello” event will be broadcast to all connected clients that are either
// in the "room1" room or in the "room2" room, excluding those in the "room3" room
io.to("room1").to("room2").except("room3").emit("hello");

从版本 4.5.0 开始,现在可以在广播时使用确认

io.timeout(10000).emit("some-event", (err, responses) => {
if (err) {
// some clients did not acknowledge the event in the given delay
} else {
console.log(responses); // one response per client
}
});
caution

在这种情况下,必须调用 timeout()

server.emitWithAck(eventName[, ...args])

在 v4.6.0 中添加

基于 Promise 的广播版本,并期望从所有目标客户端收到确认

try {
const responses = await io.timeout(10000).emitWithAck("some-event");
console.log(responses); // one response per client
} catch (e) {
// some clients did not acknowledge the event in the given delay
}

上面的示例等效于

io.timeout(10000).emit("some-event", (err, responses) => {
if (err) {
// some clients did not acknowledge the event in the given delay
} else {
console.log(responses); // one response per client
}
});

在接收方

socket.on("some-event", (callback) => {
callback("got it"); // only one argument is expected
});

server.except(rooms)

在 v4.0.0 中添加

为后续事件发射设置一个修饰符,该事件将仅广播到未加入给定 rooms 的客户端。

// the "foo" event will be broadcast to all connected clients, except the ones that are in the "room-101" room
io.except("room-101").emit("foo", "bar");

// with an array of rooms
io.except(["room-101", "room-102"]).emit("foo", "bar");

// with multiple chained calls
io.except("room-101").except("room-102").emit("foo", "bar");

server.fetchSockets()

在 v4.0.0 中添加

io.of("/").fetchSocket() 的别名。

// return all Socket instances of the main namespace
const sockets = await io.fetchSockets();

// return all Socket instances in the "room1" room of the main namespace
const sockets = await io.in("room1").fetchSockets();

示例用法

io.on("connection", (socket) => {
const userId = computeUserId(socket);

socket.join(userId);

socket.on("disconnect", async () => {
const sockets = await io.in(userId).fetchSockets();
if (sockets.length === 0) {
// no more active connections for the given user
}
});
});
tip

此方法也适用于多个 Socket.IO 服务器的集群,并使用兼容的适配器,例如 Postgres 适配器

在这种情况下,如果您只想返回给定节点上的套接字实例,则需要使用 local 标志

// return all Socket instances that are currently connected on the given node
const sockets = await io.local.fetchSockets();

这里

server.in(room)

在 v1.0.0 中添加

server.to(room) 的同义词,但在某些情况下可能更清晰

// disconnect all clients in the "room-101" room
io.in("room-101").disconnectSockets();

server.listen(httpServer[, options])

server.attach(httpServer[, options]) 的同义词。

server.listen(port[, options])

server.attach(port[, options]) 的同义词。

server.of(nsp)

通过其路径名标识符 nsp 初始化并检索给定的 Namespace。如果命名空间已初始化,则立即返回它。

const adminNamespace = io.of("/admin");

还可以提供正则表达式或函数,以动态方式创建命名空间

const dynamicNsp = io.of(/^\/dynamic-\d+$/).on("connection", (socket) => {
const newNamespace = socket.nsp; // newNamespace.name === "/dynamic-101"

// broadcast to all clients in the given sub-namespace
newNamespace.emit("hello");
});

// client-side
const socket = io("/dynamic-101");

// broadcast to all clients in each sub-namespace
dynamicNsp.emit("hello");

// use a middleware for each sub-namespace
dynamicNsp.use((socket, next) => { /* ... */ });

使用函数

io.of((name, query, next) => {
// the checkToken method must return a boolean, indicating whether the client is able to connect or not.
next(null, checkToken(query.token));
}).on("connection", (socket) => { /* ... */ });

server.on(eventName, listener)

继承自 EventEmitter 类

listener 函数添加到名为 eventName 的事件的监听器数组的末尾。

可用事件

io.on("connection", (socket) => {
// ...
});

server.onconnection(socket)

仅供高级使用。从传入的 engine.io(或兼容 API)Socket 创建新的 socket.io 客户端。

import { Server } from "socket.io";
import { Server as Engine } from "engine.io";

const engine = new Engine();
const io = new Server();

engine.on("connection", (socket) => {
io.onconnection(socket);
});

engine.listen(3000);

server.path([value])

设置 engine.io 和静态文件将被提供的路径 value。默认为 /socket.io/。如果未提供任何参数,则此方法返回当前值。

import { Server } from "socket.io";

const io = new Server();

io.path("/myownpath/");
危险

path 值必须与客户端的值匹配

import { io } from "socket.io-client";

const socket = io({
path: "/myownpath/"
});

server.serveClient([value])

如果 valuetrue,则附加的服务器将提供客户端文件。默认为 true。此方法在调用 listen 后无效。如果未提供任何参数,则此方法返回当前值。

import { Server } from "socket.io";

const io = new Server();

io.serveClient(false);

io.listen(3000);

server.serverSideEmit(eventName[, ...args][, ack])

在 v4.1.0 中添加

别名:io.of("/").serverSideEmit(/* ... */);

集群 的其他 Socket.IO 服务器发送消息。

语法

io.serverSideEmit("hello", "world");

在接收方

io.on("hello", (arg1) => {
console.log(arg1); // prints "world"
});

也支持确认

// server A
io.serverSideEmit("ping", (err, responses) => {
console.log(responses[0]); // prints "pong"
});

// server B
io.on("ping", (cb) => {
cb("pong");
});

注意

  • connectionconnectnew_namespace 字符串是保留的,不能在您的应用程序中使用。

  • 您可以发送任意数量的参数,但目前不支持二进制结构(参数数组将被 JSON.stringify-ed)

示例

io.serverSideEmit("hello", "world", 1, "2", { 3: "4" });
  • 如果其他 Socket.IO 服务器在给定的延迟后没有响应,则确认回调可能会被调用并出现错误
io.serverSideEmit("ping", (err, responses) => {
if (err) {
// at least one Socket.IO server has not responded
// the 'responses' array contains all the responses already received though
} else {
// success! the 'responses' array contains one object per other Socket.IO server in the cluster
}
});

server.serverSideEmitWithAck(eventName[, ...args])

在 v4.6.0 中添加

别名:io.of("/").serverSideEmitWithAck(/* ... */);

广播的基于 Promise 的版本,并期望来自 集群 的其他 Socket.IO 服务器的确认。

try {
const responses = await io.serverSideEmitWithAck("some-event");
console.log(responses); // one response per server (except itself)
} catch (e) {
// some servers did not acknowledge the event in the given delay
}

上面的示例等效于

io.serverSideEmit("some-event", (err, responses) => {
if (err) {
// some servers did not acknowledge the event in the given delay
} else {
console.log(responses); // one response per server (except itself)
}
});

在接收方

io.on("some-event", (callback) => {
callback("got it"); // only one argument is expected
});

server.socketsJoin(rooms)

在 v4.0.0 中添加

别名 io.of("/").socketsJoin(rooms)

// make all Socket instances join the "room1" room
io.socketsJoin("room1");

// make all Socket instances in the "room1" room join the "room2" and "room3" rooms
io.in("room1").socketsJoin(["room2", "room3"]);

// this also works with a single socket ID
io.in(theSocketId).socketsJoin("room1");
tip

此方法也适用于多个 Socket.IO 服务器的集群,并使用兼容的适配器,例如 Postgres 适配器

在这种情况下,如果您只想影响给定节点上的套接字实例,则需要使用 local 标志

// make all Socket instances that are currently connected on the given node join the "room1" room
io.local.socketsJoin("room1");

这里

server.socketsLeave(rooms)

在 v4.0.0 中添加

别名 io.of("/").socketsLeave(rooms)

// make all Socket instances leave the "room1" room
io.socketsLeave("room1");

// make all Socket instances in the "room1" room leave the "room2" and "room3" rooms
io.in("room1").socketsLeave(["room2", "room3"]);

// this also works with a single socket ID
io.in(theSocketId).socketsLeave("room1");
tip

此方法也适用于多个 Socket.IO 服务器的集群,并使用兼容的适配器,例如 Postgres 适配器

在这种情况下,如果您只想影响给定节点上的套接字实例,则需要使用 local 标志

// make all Socket instances that are currently connected on the given node leave the "room1" room
io.local.socketsLeave("room1");

这里

server.timeout(value)

在 v4.5.0 中添加

设置后续事件发射的修饰符,当给定的毫秒数过去而没有收到所有目标客户端的确认时,回调将被调用并出现错误

io.timeout(10000).emit("some-event", (err, responses) => {
if (err) {
// some clients did not acknowledge the event in the given delay
} else {
console.log(responses); // one response per client
}
});

server.to(room)

历史
版本更改
v4.0.0允许传递房间数组。
v1.0.0初始实现。

设置后续事件发射的修饰符,该事件将仅广播给已加入给定 room 的客户端。

要向多个房间发射,您可以多次调用 to

// the “foo” event will be broadcast to all connected clients in the “room-101” room
io.to("room-101").emit("foo", "bar");

// with an array of rooms (a client will be notified at most once)
io.to(["room-101", "room-102"]).emit("foo", "bar");

// with multiple chained calls
io.to("room-101").to("room-102").emit("foo", "bar");

server.use(fn)

在 v1.0.0 中添加

别名 io.of("/").use(fn)

为主命名空间注册中间件,该中间件是一个函数,它会为每个传入的 Socket 执行,并接收套接字和一个函数作为参数,该函数可以选择将执行延迟到下一个注册的中间件。

传递给中间件回调的错误将作为特殊的 connect_error 数据包发送给客户端。

服务器

io.use((socket, next) => {
const err = new Error("not authorized");
err.data = { content: "Please retry later" }; // additional details
next(err);
});

客户端

socket.on("connect_error", err => {
console.log(err instanceof Error); // true
console.log(err.message); // not authorized
console.log(err.data); // { content: "Please retry later" }
});

更多信息可以在 这里 找到。

info

如果您正在寻找 Express 中间件,请查看 本节

命名空间

Namespace in the class diagram for the serverNamespace in the class diagram for the server

表示在给定范围内连接的套接字池,该范围由路径名标识(例如:/chat)。

更多信息可以在 这里 找到。

属性

namespace.adapter

命名空间使用的 "适配器"

注意: 主命名空间的适配器可以通过 io.of("/").adapter 访问。

更多关于它的信息 这里

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

namespace.name

命名空间标识符属性。

namespace.sockets

连接到此命名空间的 Socket 实例的映射。

// number of sockets in this namespace (on this node)
const socketCount = io.of("/admin").sockets.size;

事件

事件:'connect'

事件:"connection" 的同义词。

事件:'connection'

在客户端连接时触发。

// main namespace
io.on("connection", (socket) => {
// ...
});

// custom namespace
io.of("/admin").on("connection", (socket) => {
// ...
});

方法

namespace.allSockets()

  • 返回值 Promise<Set<SocketId>>
caution

此方法将在下一个主要版本中删除,请改用 serverSideEmit()fetchSockets()

获取连接到此命名空间的套接字 ID 列表(如果适用,跨所有节点)。

// all sockets in the main namespace
const ids = await io.allSockets();

// all sockets in the main namespace and in the "user:1234" room
const ids = await io.in("user:1234").allSockets();

// all sockets in the "chat" namespace
const ids = await io.of("/chat").allSockets();

// all sockets in the "chat" namespace and in the "general" room
const ids = await io.of("/chat").in("general").allSockets();

namespace.disconnectSockets([close])

在 v4.0.0 中添加

  • close <boolean> 是否关闭底层连接
  • 返回值 void

使匹配的 Socket 实例断开连接。

// make all Socket instances disconnect
io.disconnectSockets();

// make all Socket instances in the "room1" room disconnect (and discard the low-level connection)
io.in("room1").disconnectSockets(true);

// make all Socket instances in the "room1" room of the "admin" namespace disconnect
io.of("/admin").in("room1").disconnectSockets();

// this also works with a single socket ID
io.of("/admin").in(theSocketId).disconnectSockets();

namespace.emit(eventName[, ...args])

历史
版本更改
v4.5.0io.emit() 现在支持确认。
v1.0.0初始实现。

向给定命名空间中所有连接的客户端发射事件。

io.of("/chat").emit("hello");

可以包含任意数量的参数,并且支持所有可序列化数据结构

io.of("/chat").emit("hello", 1, "2", { "3": 4 }, Buffer.from([5]));

在接收方

socket.on("hello", (arg1, arg2, arg3, arg4) => {
console.log(arg1); // 1
console.log(arg2); // "2"
console.log(arg3); // { "3": 4 }
console.log(arg4); // ArrayBuffer or Buffer, depending on the platform
});
info

参数将自动序列化,因此不需要调用 JSON.stringify()

您可以使用 to()except() 将数据包发送到特定客户端

// the “hello” event will be broadcast to all connected clients that are either
// in the "room1" room or in the "room2" room, excluding those in the "room3" room
io.of("/chat").to("room1").to("room2").except("room3").emit("hello");

从版本 4.5.0 开始,现在可以在广播时使用确认

io.of("/chat").timeout(10000).emit("some-event", (err, responses) => {
if (err) {
// some clients did not acknowledge the event in the given delay
} else {
console.log(responses); // one response per client
}
});
caution

在这种情况下,调用 timeout() 是强制性的。

namespace.emitWithAck(eventName[, ...args])

在 v4.6.0 中添加

广播的基于 Promise 的版本,并期望来自给定命名空间中所有目标客户端的确认

try {
const responses = await io.of("/chat").timeout(10000).emitWithAck("some-event");
console.log(responses); // one response per client
} catch (e) {
// some clients did not acknowledge the event in the given delay
}

上面的示例等效于

io.of("/chat").timeout(10000).emit("some-event", (err, responses) => {
if (err) {
// some clients did not acknowledge the event in the given delay
} else {
console.log(responses); // one response per client
}
});

在接收方

socket.on("some-event", (callback) => {
callback("got it"); // only one argument is expected
});

namespace.except(rooms)

在 v4.0.0 中添加

为后续事件发射设置一个修饰符,该事件将仅广播到未加入给定 rooms 的客户端。

const myNamespace = io.of("/my-namespace");

// the "foo" event will be broadcast to all connected clients, except the ones that are in the "room-101" room
myNamespace.except("room-101").emit("foo", "bar");

// with an array of rooms
myNamespace.except(["room-101", "room-102"]).emit("foo", "bar");

// with multiple chained calls
myNamespace.except("room-101").except("room-102").emit("foo", "bar");

namespace.fetchSockets()

在 v4.0.0 中添加

返回匹配的 Socket 实例

// return all Socket instances in the main namespace
const sockets = await io.fetchSockets();

// return all Socket instances in the "room1" room of the main namespace
const sockets = await io.in("room1").fetchSockets();

// return all Socket instances in the "room1" room of the "admin" namespace
const sockets = await io.of("/admin").in("room1").fetchSockets();

// this also works with a single socket ID
const sockets = await io.in(theSocketId).fetchSockets();

上面示例中的 sockets 变量是一个对象数组,它公开了通常的 Socket 类的子集

for (const socket of sockets) {
console.log(socket.id);
console.log(socket.handshake);
console.log(socket.rooms);
console.log(socket.data);
socket.emit(/* ... */);
socket.join(/* ... */);
socket.leave(/* ... */);
socket.disconnect(/* ... */);
}

data 属性是一个任意对象,可用于在 Socket.IO 服务器之间共享信息

// server A
io.on("connection", (socket) => {
socket.data.username = "alice";
});

// server B
const sockets = await io.fetchSockets();
console.log(sockets[0].data.username); // "alice"

重要说明:此方法(以及 socketsJoinsocketsLeavedisconnectSockets)与 Redis 适配器(从 socket.io-redis@6.1.0 开始)兼容,这意味着它们将在 Socket.IO 服务器之间工作。

namespace.in(room)

在 v1.0.0 中添加

namespace.to(room) 的同义词,但在某些情况下可能感觉更清晰

const myNamespace = io.of("/my-namespace");

// disconnect all clients in the "room-101" room
myNamespace.in("room-101").disconnectSockets();

namespace.serverSideEmit(eventName[, ...args][, ack])

在 v4.1.0 中添加

集群 的其他 Socket.IO 服务器发送消息。

语法

io.of("/chat").serverSideEmit("hello", "world");

在接收方

io.of("/chat").on("hello", (arg1) => {
console.log(arg1); // prints "world"
});

也支持确认

// server A
io.of("/chat").serverSideEmit("ping", (err, responses) => {
console.log(responses[0]); // prints "pong"
});

// server B
io.of("/chat").on("ping", (cb) => {
cb("pong");
});

注意

  • connectionconnectnew_namespace 字符串是保留的,不能在您的应用程序中使用。

  • 您可以发送任意数量的参数,但目前不支持二进制结构(参数数组将被 JSON.stringify-ed)

示例

io.of("/chat").serverSideEmit("hello", "world", 1, "2", { 3: "4" });
  • 如果其他 Socket.IO 服务器在给定的延迟后没有响应,则确认回调可能会被调用并出现错误
io.of("/chat").serverSideEmit("ping", (err, responses) => {
if (err) {
// at least one Socket.IO server has not responded
// the 'responses' array contains all the responses already received though
} else {
// success! the 'responses' array contains one object per other Socket.IO server in the cluster
}
});

namespace.serverSideEmitWithAck(eventName[, ...args])

在 v4.6.0 中添加

广播的基于 Promise 的版本,并期望来自 集群 的其他 Socket.IO 服务器的确认。

try {
const responses = await io.of("/chat").serverSideEmitWithAck("some-event");
console.log(responses); // one response per server (except itself)
} catch (e) {
// some servers did not acknowledge the event in the given delay
}

上面的示例等效于

io.of("/chat").serverSideEmit("some-event", (err, responses) => {
if (err) {
// some servers did not acknowledge the event in the given delay
} else {
console.log(responses); // one response per server (except itself)
}
});

在接收方

io.of("/chat").on("some-event", (callback) => {
callback("got it"); // only one argument is expected
});

namespace.socketsJoin(rooms)

在 v4.0.0 中添加

使匹配的 Socket 实例加入指定的房间

// make all Socket instances join the "room1" room
io.socketsJoin("room1");

// make all Socket instances in the "room1" room join the "room2" and "room3" rooms
io.in("room1").socketsJoin(["room2", "room3"]);

// make all Socket instances in the "room1" room of the "admin" namespace join the "room2" room
io.of("/admin").in("room1").socketsJoin("room2");

// this also works with a single socket ID
io.in(theSocketId).socketsJoin("room1");

更多信息可以在 这里 找到。

namespace.socketsLeave(rooms)

在 v4.0.0 中添加

使匹配的 Socket 实例离开指定的房间

// make all Socket instances leave the "room1" room
io.socketsLeave("room1");

// make all Socket instances in the "room1" room leave the "room2" and "room3" rooms
io.in("room1").socketsLeave(["room2", "room3"]);

// make all Socket instances in the "room1" room of the "admin" namespace leave the "room2" room
io.of("/admin").in("room1").socketsLeave("room2");

// this also works with a single socket ID
io.in(theSocketId).socketsLeave("room1");

namespace.timeout(value)

在 v4.5.0 中添加

设置后续事件发射的修饰符,当给定的毫秒数过去而没有收到客户端的确认时,回调将被调用并出现错误

io.of("/chat").timeout(10000).emit("some-event", (err, responses) => {
if (err) {
// some clients did not acknowledge the event in the given delay
} else {
console.log(responses); // one response per client
}
});

namespace.to(room)

历史
版本更改
v4.0.0允许传递房间数组。
v1.0.0初始实现。

设置后续事件发射的修饰符,该事件将仅广播给已加入给定 room 的客户端。

要向多个房间发射,您可以多次调用 to

const myNamespace = io.of("/my-namespace");

// the “foo” event will be broadcast to all connected clients in the “room-101” room
myNamespace.to("room-101").emit("foo", "bar");

// with an array of rooms (a client will be notified at most once)
myNamespace.to(["room-101", "room-102"]).emit("foo", "bar");

// with multiple chained calls
myNamespace.to("room-101").to("room-102").emit("foo", "bar");

namespace.use(fn)

在 v1.0.0 中添加

为给定命名空间注册一个中间件,该中间件是一个函数,它会为每个传入的 Socket 执行,并接收套接字和一个可选的函数作为参数,以将执行延迟到下一个注册的中间件。

传递给中间件回调的错误将作为特殊的 connect_error 数据包发送给客户端。

服务器

io.of("/chat").use((socket, next) => {
const err = new Error("not authorized");
err.data = { content: "Please retry later" }; // additional details
next(err);
});

客户端

socket.on("connect_error", err => {
console.log(err instanceof Error); // true
console.log(err.message); // not authorized
console.log(err.data); // { content: "Please retry later" }
});

更多信息可以在 这里 找到。

info

如果您正在寻找 Express 中间件,请查看 本节

标志

标志:'local'

为后续的事件发射设置一个修饰符,该修饰符将使事件数据仅广播到当前节点(当扩展到多个节点时)。

io.local.emit("an event", { some: "data" });

标志:'volatile'

为后续的事件发射设置一个修饰符,该修饰符将使事件数据可能丢失,如果客户端尚未准备好接收消息(由于网络缓慢或其他问题,或者因为它们通过长轮询连接并且处于请求-响应周期的中间)。

io.volatile.emit("an event", { some: "data" }); // the clients may or may not receive it

套接字

Socket in the class diagram for the serverSocket in the class diagram for the server

Socket 是与浏览器客户端交互的基本类。Socket 属于某个特定的 Namespace(默认情况下为 /),并使用底层的 Client 进行通信。

需要注意的是,Socket 与实际的底层 TCP/IP socket 没有直接关系,它只是类的名称。

在每个 Namespace 中,您还可以定义任意通道(称为 room),Socket 可以加入和离开。这提供了一种方便的方式来广播到一组 Socket(见下面的 Socket#to)。

Socket 类继承自 EventEmitterSocket 类覆盖了 emit 方法,并且没有修改任何其他 EventEmitter 方法。此处记录的所有方法(除了 emit)也作为 EventEmitter 方法出现,这些方法都是由 EventEmitter 实现的,并且 EventEmitter 的文档适用。

更多信息可以在这里找到 here.

事件

事件:'disconnect'

  • reason <string> 断开连接的原因(客户端或服务器端)

断开连接时触发。

io.on("connection", (socket) => {
socket.on("disconnect", (reason) => {
// ...
});
});

可能的原因

原因描述
server namespace disconnect套接字被强制断开连接,使用 socket.disconnect().
client namespace disconnect客户端已使用 socket.disconnect() 手动断开套接字连接。
server shutting down服务器正在关闭。
ping timeout客户端在 pingTimeout 延迟内没有发送 PONG 数据包。
transport close连接已关闭(例如:用户已断开连接,或网络已从 WiFi 更改为 4G)。
transport error连接遇到错误。
parse error服务器从客户端接收到了无效的数据包。
forced close服务器从客户端接收到了无效的数据包。
forced server close客户端没有及时加入命名空间(参见 connectTimeout 选项),并被强制关闭。

事件:'disconnecting'

在 v1.5.0 中添加

  • reason <string> 断开连接的原因(客户端或服务器端)

当客户端即将断开连接时触发(但尚未离开其 rooms)。

io.on("connection", (socket) => {
socket.on("disconnecting", (reason) => {
console.log(socket.rooms); // Set { ... }
});
});

使用异步处理程序,您需要创建 rooms 属性的副本

io.on("connection", (socket) => {
socket.on("disconnecting", async (reason) => {
const rooms = new Set(socket.rooms);

await someLongRunningOperation();

// socket.rooms will be empty there
console.log(rooms);
});
});
caution

这些事件以及 connectconnect_errornewListenerremoveListener 是特殊事件,不应在您的应用程序中使用

// BAD, will throw an error
socket.emit("disconnect");

属性

socket.client

对底层 Client 对象的引用。

socket.conn

  • <engine.Socket>

对底层 Client 传输连接(engine.io Socket 对象)的引用。这允许访问 IO 传输层,该层仍然(大部分)抽象了实际的 TCP/IP 套接字。

io.on("connection", (socket) => {
console.log("initial transport", socket.conn.transport.name); // prints "polling"

socket.conn.once("upgrade", () => {
// called when the transport is upgraded (i.e. from HTTP long-polling to WebSocket)
console.log("upgraded transport", socket.conn.transport.name); // prints "websocket"
});

socket.conn.on("packet", ({ type, data }) => {
// called for each packet received
});

socket.conn.on("packetCreate", ({ type, data }) => {
// called for each packet sent
});

socket.conn.on("drain", () => {
// called when the write buffer is drained
});

socket.conn.on("close", (reason) => {
// called when the underlying connection is closed
});
});

socket.data

在 v4.0.0 中添加

一个任意对象,可以与 fetchSockets() 实用程序方法结合使用

io.on("connection", (socket) => {
socket.data.username = "alice";
});

const sockets = await io.fetchSockets();
console.log(sockets[0].data.username); // "alice"
tip

这也适用于 Socket.IO 集群,使用兼容的适配器,例如 Postgres 适配器.

socket.handshake

握手详细信息

字段类型描述
headersIncomingHttpHeaders作为握手的一部分发送的标头。
time<string>创建日期(作为字符串)。
address<string>客户端的 IP 地址。
xdomain<boolean>连接是否跨域。
secure<boolean>连接是否通过 SSL 建立。
issued<number>创建日期(作为 Unix 时间戳)。
url<string>请求 URL 字符串。
queryRecord<string, string or string[]>第一个请求的查询参数。
authRecord<string, any>身份验证有效负载。另见 here.

用法

io.use((socket, next) => {
let handshake = socket.handshake;
// ...
});

io.on("connection", (socket) => {
let handshake = socket.handshake;
// ...
});

示例

const handshake = {
headers: {
"user-agent": "node-XMLHttpRequest",
accept: "*/*",
host: "localhost:3000",
connection: "close"
},
time: "Wed Jan 01 2020 01:00:00 GMT+0100 (Central European Standard Time)",
address: "::ffff:127.0.0.1",
xdomain: false,
secure: false,
issued: 1577836800000,
url: "/socket.io/?EIO=4&transport=polling&t=OPAfXv5&b64=1",
query: {
EIO: "4",
transport: "polling",
t: "OPAfXv5",
b64: "1"
},
auth: {}
}

注意:headers 属性指的是会话的第一个 HTTP 请求的标头,不会被后续的 HTTP 请求更新。

io.on("connection", (socket) => {
console.log(socket.handshake.headers === socket.request.headers); // prints "true"
});

socket.id

会话的唯一标识符,来自底层的 Client

caution

id 属性是一个短暂的 ID,不应在您的应用程序中使用(或仅用于调试目的),因为

  • 此 ID 在每次重新连接后都会重新生成(例如,当 WebSocket 连接断开时,或当用户刷新页面时)
  • 两个不同的浏览器选项卡将具有两个不同的 ID
  • 服务器上没有为给定 ID 存储消息队列(即,如果客户端断开连接,则从服务器发送到此 ID 的消息将丢失)

请使用常规会话 ID 代替(要么在 cookie 中发送,要么存储在 localStorage 中并在 auth 有效负载中发送)。

另见

socket.recovered

在 v4.6.0 中添加

在上次重新连接期间连接状态是否成功恢复。

io.on("connection", (socket) => {
if (socket.recovered) {
// recovery was successful: socket.id, socket.rooms and socket.data were restored
} else {
// new or unrecoverable session
}
});

有关此功能的更多信息,请参见 here.

socket.request

一个 getter 代理,它返回对产生底层 engine.io Clientrequest 的引用。对于访问请求标头(如 CookieUser-Agent)很有用。

import { parse } from "cookie";

io.on("connection", (socket) => {
const cookies = parse(socket.request.headers.cookie || "");
});

注意:socket.request 指的是会话的第一个 HTTP 请求,不会被后续的 HTTP 请求更新。

io.on("connection", (socket) => {
console.log(socket.request.headers === socket.handshake.headers); // prints "true"
});

如果您不需要此引用,可以将其丢弃以减少内存占用

io.on("connection", (socket) => {
delete socket.conn.request;
});

socket.rooms

一个字符串集,标识此客户端所在的房间。

io.on("connection", (socket) => {

console.log(socket.rooms); // Set { <socket.id> }

socket.join("room1");

console.log(socket.rooms); // Set { <socket.id>, "room1" }

});

方法

socket.compress(value)

  • value <boolean> 是否压缩以下数据包
  • 返回 Socket 用于链接

为后续的事件发射设置一个修饰符,该修饰符将使事件数据仅在值为 true 时才压缩。如果您不调用该方法,则默认为 true

io.on("connection", (socket) => {
socket.compress(false).emit("uncompressed", "that's rough");
});

socket.disconnect([close])

断开此套接字连接。如果 close 的值为 true,则关闭底层连接。否则,它只断开命名空间的连接。

io.on("connection", (socket) => {
setTimeout(() => socket.disconnect(true), 5000);
});

socket.emit(eventName[, ...args][, ack])

(覆盖 EventEmitter.emit)

向由字符串名称标识的套接字发出事件。可以包含任何其他参数。所有可序列化数据结构都受支持,包括 Buffer

io.on("connection", () => {
socket.emit("hello", "world");
socket.emit("with-binary", 1, "2", { 3: "4", 5: Buffer.from([6]) });
});

ack 参数是可选的,它将使用客户端的答案进行调用。

服务器

io.on("connection", (socket) => {
socket.emit("hello", "world", (response) => {
console.log(response); // "got it"
});
});

客户端

socket.on("hello", (arg, callback) => {
console.log(arg); // "world"
callback("got it");
});

socket.emitWithAck(eventName[, ...args])

在 v4.6.0 中添加

基于 Promise 的发出并期望从给定客户端确认的版本

io.on("connection", async (socket) => {
// without timeout
const response = await socket.emitWithAck("hello", "world");

// with a specific timeout
try {
const response = await socket.timeout(10000).emitWithAck("hello", "world");
} catch (err) {
// the client did not acknowledge the event in the given delay
}
});

上面的示例等效于

io.on("connection", (socket) => {
// without timeout
socket.emit("hello", "world", (val) => {
// ...
});

// with a specific timeout
socket.timeout(10000).emit("hello", "world", (err, val) => {
// ...
});
});

在接收方

socket.on("hello", (arg1, callback) => {
callback("got it"); // only one argument is expected
});

socket.eventNames()

继承自 EventEmitter(以及此处未提及的其他方法)。有关 events 模块的 Node.js 文档,请参见。

socket.except(rooms)

在 v4.0.0 中添加

为后续的事件发射设置一个修饰符,该修饰符将使事件仅广播到尚未加入给定 rooms 的客户端(套接字本身被排除在外)。

// to all clients except the ones in "room1" and the sender
socket.broadcast.except("room1").emit(/* ... */);

// same as above
socket.except("room1").emit(/* ... */);

// to all clients in "room4" except the ones in "room5" and the sender
socket.to("room4").except("room5").emit(/* ... */);

socket.in(room)

在 v1.0.0 中添加

socket.to(room) 的同义词。

socket.join(room)

将套接字添加到给定的 room 或房间列表中。

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

console.log(socket.rooms); // Set { <socket.id>, "room 237" }

socket.join(["room 237", "room 238"]);

io.to("room 237").emit("a new user has joined the room"); // broadcast to everyone in the room
});

加入房间的机制由已配置的 Adapter 处理(参见上面的 Server#adapter),默认为 socket.io-adapter.

为了方便起见,每个套接字都会自动加入一个由其 ID 标识的房间(参见 Socket#id)。这使得向其他套接字广播消息变得容易

io.on("connection", (socket) => {
socket.on("say to someone", (id, msg) => {
// send a private message to the socket with the given id
socket.to(id).emit("my message", msg);
});
});

socket.leave(room)

从给定的 room 中删除套接字。

io.on("connection", (socket) => {
socket.leave("room 237");

io.to("room 237").emit(`user ${socket.id} has left the room`);
});
info

断开连接时会自动离开房间。

socket.listenersAny()

返回已注册的通配符监听器列表。

const listeners = socket.listenersAny();

socket.listenersAnyOutgoing()

在 v4.5.0 中添加

返回已注册的用于传出数据包的通配符监听器列表。

const listeners = socket.listenersAnyOutgoing();

socket.offAny([listener])

移除之前注册的监听器。如果未提供监听器,则会移除所有通配符监听器。

const myListener = () => { /* ... */ };

socket.onAny(myListener);

// then, later
socket.offAny(myListener);

socket.offAny();

socket.offAnyOutgoing([listener])

在 v4.5.0 中添加

移除之前注册的监听器。如果未提供监听器,则会移除所有通配符监听器。

const myListener = () => { /* ... */ };

socket.onAnyOutgoing(myListener);

// remove a single listener
socket.offAnyOutgoing(myListener);

// remove all listeners
socket.offAnyOutgoing();

socket.on(eventName, callback)

继承自 EventEmitter 类

为给定事件注册新的处理程序。

socket.on("news", (data) => {
console.log(data);
});
// with several arguments
socket.on("news", (arg1, arg2, arg3) => {
// ...
});
// or with acknowledgement
socket.on("news", (data, callback) => {
callback(0);
});

socket.onAny(callback)

注册新的通配符监听器。

socket.onAny((event, ...args) => {
console.log(`got ${event}`);
});
caution

确认 不会被通配符监听器捕获。

socket.emit("foo", (value) => {
// ...
});

socket.onAnyOutgoing(() => {
// triggered when the event is sent
});

socket.onAny(() => {
// not triggered when the acknowledgement is received
});

socket.onAnyOutgoing(callback)

在 v4.5.0 中添加

为出站数据包注册新的通配符监听器。

socket.onAnyOutgoing((event, ...args) => {
console.log(`got ${event}`);
});
caution

确认 不会被通配符监听器捕获。

socket.on("foo", (value, callback) => {
callback("OK");
});

socket.onAny(() => {
// triggered when the event is received
});

socket.onAnyOutgoing(() => {
// not triggered when the acknowledgement is sent
});

socket.once(eventName, listener)

继承自 EventEmitter(以及此处未提及的其他方法)。有关 events 模块的 Node.js 文档,请参见。

socket.prependAny(callback)

注册新的通配符监听器。监听器将被添加到监听器数组的开头。

socket.prependAny((event, ...args) => {
console.log(`got ${event}`);
});

socket.prependAnyOutgoing(callback)

在 v4.5.0 中添加

为出站数据包注册新的通配符监听器。监听器将被添加到监听器数组的开头。

socket.prependAnyOutgoing((event, ...args) => {
console.log(`got ${event}`);
});

socket.removeAllListeners([eventName])

继承自 EventEmitter(以及此处未提及的其他方法)。有关 events 模块的 Node.js 文档,请参见。

socket.removeListener(eventName, listener)

继承自 EventEmitter(以及此处未提及的其他方法)。有关 events 模块的 Node.js 文档,请参见。

socket.send([...args][, ack])

发送 message 事件。参见 socket.emit(eventName[, ...args][, ack]).

socket.timeout(value)

在 v4.4.0 中添加

设置后续事件发射的修饰符,当给定的毫秒数过去而没有收到客户端的确认时,回调将被调用并出现错误

socket.timeout(5000).emit("my-event", (err) => {
if (err) {
// the client did not acknowledge the event in the given delay
}
});

socket.to(room)

历史
版本更改
v4.0.0允许传递房间数组。
v1.0.0初始实现。

为后续事件发射设置一个修饰符,该事件将仅广播给已加入给定 room 的客户端(不包括套接字本身)。

要向多个房间发射,您可以多次调用 to

io.on("connection", (socket) => {

// to one room
socket.to("others").emit("an event", { some: "data" });

// to multiple rooms
socket.to("room1").to("room2").emit("hello");

// or with an array
socket.to(["room1", "room2"]).emit("hello");

// a private message to another socket
socket.to(/* another socket id */).emit("hey");

// WARNING: `socket.to(socket.id).emit()` will NOT work
// Please use `io.to(socket.id).emit()` instead.
});

注意:广播时不支持确认。

socket.use(fn)

历史
版本更改
v3.0.5恢复第一个实现。
v3.0.0删除,改为使用 socket.onAny()
v1.7.2error 事件直接发送到客户端。
v1.6.0第一个实现。

注册一个中间件,它是一个函数,对每个传入的 Packet 执行,并接收数据包和一个函数作为参数,该函数可以选择将执行延迟到下一个注册的中间件。

传递给中间件回调的错误将作为服务器端的 error 事件发出

io.on("connection", (socket) => {
socket.use(([event, ...args], next) => {
if (isUnauthorized(event)) {
return next(new Error("unauthorized event"));
}
// do not forget to call next
next();
});

socket.on("error", (err) => {
if (err && err.message === "unauthorized event") {
socket.disconnect();
}
});
});

标志

标志:'broadcast'

为后续事件发射设置一个修饰符,该事件数据将仅广播到除发送者之外的所有套接字。

io.on("connection", (socket) => {
socket.broadcast.emit("an event", { some: "data" }); // everyone gets it but the sender
});

标志:'volatile'

为后续事件发射设置一个修饰符,如果客户端未准备好接收消息(由于网络缓慢或其他问题,或者因为它们通过长轮询连接并且处于请求-响应周期的中间),则事件数据可能会丢失。

io.on("connection", (socket) => {
socket.volatile.emit("an event", { some: "data" }); // the client may or may not receive it
});

客户端

Client in the class diagram for the serverClient in the class diagram for the server

Client 类表示传入的传输(engine.io)连接。一个 Client 可以与属于不同 Namespace 的多个复用 Socket 关联。

属性

client.conn

  • <engine.Socket>

对底层 engine.io Socket 连接的引用。

client.request

一个 getter 代理,返回对生成 engine.io 连接的 request 的引用。用于访问请求标头,例如 CookieUser-Agent

引擎

Engine.IO 服务器,它管理 WebSocket / HTTP 长轮询连接。更多信息 这里.

其源代码可以在此处找到: https://github.com/socketio/engine.io

事件

事件:'connection_error'

在 v4.1.0 中添加

io.engine.on("connection_error", (err) => {
console.log(err.req); // the request object
console.log(err.code); // the error code, for example 1
console.log(err.message); // the error message, for example "Session ID unknown"
console.log(err.context); // some additional error context
});

当连接异常关闭时,将发出此事件。以下是可能的错误代码列表

代码消息
0"Transport unknown"
1"Session ID unknown"
2"Bad handshake method"
3"Bad request"
4"Forbidden"
5"Unsupported protocol version"

事件:'headers'

在 v4.1.0 中添加

此事件将在写入会话的每个 HTTP 请求(包括 WebSocket 升级)的响应标头之前发出,允许您自定义它们。

import { serialize, parse } from "cookie";

io.engine.on("headers", (headers, request) => {
if (!request.headers.cookie) return;
const cookies = parse(request.headers.cookie);
if (!cookies.randomId) {
headers["set-cookie"] = serialize("randomId", "abc", { maxAge: 86400 });
}
});

事件:'initial_headers'

在 v4.1.0 中添加

此事件将在写入会话的第一个 HTTP 请求(握手)的响应标头之前发出,允许您自定义它们。

import { serialize } from "cookie";

io.engine.on("initial_headers", (headers, request) => {
headers["set-cookie"] = serialize("uid", "1234", { sameSite: "strict" });
});

如果您需要执行一些异步操作,则需要使用 allowRequest 选项

import { serialize } from "cookie";

const io = new Server(httpServer, {
allowRequest: async (req, callback) => {
const session = await fetchSession(req);
req.session = session;
callback(null, true);
}
});

io.engine.on("initial_headers", (headers, req) => {
if (req.session) {
headers["set-cookie"] = serialize("sid", req.session.id, { sameSite: "strict" });
}
});

另见

属性

engine.clientsCount

在 v1.0.0 中添加

当前连接的客户端数量。

const count = io.engine.clientsCount;
// may or may not be similar to the count of Socket instances in the main namespace, depending on your usage
const count2 = io.of("/").sockets.size;

方法

engine.generateId

用于生成新会话 ID 的函数。默认为 base64id.

const uuid = require("uuid");

io.engine.generateId = () => {
return uuid.v4(); // must be unique across all Socket.IO servers
}

engine.handleUpgrade(request, socket, head)

在 v1.0.0 中添加

此方法可用于注入 HTTP 升级

同时使用 Socket.IO 服务器和普通 WebSocket 服务器的示例

import { createServer } from "http";
import { Server as WsServer } from "ws";
import { Server } from "socket.io";

const httpServer = createServer();
const wss = new WsServer({ noServer: true });
const io = new Server(httpServer);

httpServer.removeAllListeners("upgrade");

httpServer.on("upgrade", (req, socket, head) => {
if (req.url === "/") {
wss.handleUpgrade(req, socket, head, (ws) => {
wss.emit("connection", ws, req);
});
} else if (req.url.startsWith("/socket.io/")) {
io.engine.handleUpgrade(req, socket, head);
} else {
socket.destroy();
}
});

httpServer.listen(3000);

engine.use(middleware)

在 v4.6.0 中添加

添加新的 Express 中间件.

io.engine.use((req, res, next) => {
// do something

next();
});

中间件将针对每个传入的 HTTP 请求(包括升级请求)调用。

使用 express-session 的示例

import session from "express-session";

io.engine.use(session({
secret: "keyboard cat",
resave: false,
saveUninitialized: true,
cookie: { secure: true }
}));

使用 helmet 的示例

import helmet from "helmet";

io.engine.use(helmet());