Skip to main content
Version: 4.x

Socket 实例(服务器端)

Socket 是与客户端交互的基础类。它继承了 Node.js EventEmitter 的所有方法,如 emitononceremoveListener

¥A Socket is the fundamental class for interacting with the client. It inherits all the methods of the Node.js EventEmitter, like emit, on, once or removeListener.

Bidirectional communication between server and clientBidirectional communication between server and client

除了:

¥Besides:

Socket 实例有一些可能在你的应用中使用的属性:

¥The Socket instance has a few attributes that may be of use in your application:

Socket#id

每个新连接都会分配一个随机的 20 个字符标识符。

¥Each new connection is assigned a random 20-characters identifier.

该标识符与客户端的值同步。

¥This identifier is synced with the value on the client-side.

// server-side
io.on("connection", (socket) => {
console.log(socket.id); // ojIckSD2jqNzOqIrAGzL
});

// client-side
socket.on("connect", () => {
console.log(socket.id); // ojIckSD2jqNzOqIrAGzL
});
caution

请注意,除非启用 连接状态恢复,否则 id 属性是一个临时 ID,不适合在你的应用中使用(或仅用于调试目的),因为:

¥Please note that, unless connection state recovery is enabled, the id attribute is an ephemeral ID that is not meant to be used in your application (or only for debugging purposes) because:

  • 每次重新连接后都会重新生成此 ID(例如当 WebSocket 连接被切断时,或者当用户刷新页面时)

    ¥this ID is regenerated after each reconnection (for example when the WebSocket connection is severed, or when the user refreshes the page)

  • 两个不同的浏览器选项卡将有两个不同的 ID

    ¥two different browser tabs will have two different IDs

  • 服务器上没有为给定 ID 存储消息队列(即如果客户端断开连接,从服务器发送到该 ID 的消息就会丢失)

    ¥there is no message queue stored for a given ID on the server (i.e. if the client is disconnected, the messages sent from the server to this ID are lost)

请改用常规会话 ID(在 cookie 中发送,或存储在 localStorage 中并在 auth 有效负载中发送)。

¥Please use a regular session ID instead (either sent in a cookie, or stored in the localStorage and sent in the auth payload).

也可以看看:

¥See also:

注意:你无法覆盖此标识符,因为它在 Socket.IO 代码库的多个部分中使用。

¥Note: you can't overwrite this identifier, as it is used in several parts of the Socket.IO codebase.

Socket#handshake

该对象包含有关 Socket.IO 会话开始时发生的握手的一些详细信息。

¥This object contains some details about the handshake that happens at the beginning of the Socket.IO session.

{
headers: /* the headers of the initial request */
query: /* the query params of the initial request */
auth: /* the authentication payload */
time: /* the date of creation (as string) */
issued: /* the date of creation (unix timestamp) */
url: /* the request URL string */
address: /* the ip of the client */
xdomain: /* whether the connection is cross-domain */
secure: /* whether the connection is secure */
}

示例:

¥Example:

{
"headers": {
"user-agent": "xxxx",
"accept": "*/*",
"host": "example.com",
"connection": "close"
},
"query": {
"EIO": "4",
"transport": "polling",
"t": "NNjNltH"
},
"auth": {
"token": "123"
},
"time": "Sun Nov 22 2020 01:33:46 GMT+0100 (Central European Standard Time)",
"issued": 1606005226969,
"url": "/socket.io/?EIO=4&transport=polling&t=NNjNltH",
"address": "::ffff:1.2.3.4",
"xdomain": false,
"secure": true
}

Socket#rooms

这是对套接字当前所在的 房间 的引用。

¥This is a reference to the rooms the Socket is currently in.

io.on("connection", (socket) => {
console.log(socket.rooms); // Set { <socket.id> }
socket.join("room1");
console.log(socket.rooms); // Set { <socket.id>, "room1" }
});

Socket#data

可以与 fetchSockets() 实用程序方法结合使用的任意对象:

¥An arbitrary object that can be used in conjunction with the fetchSockets() utility method:

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

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

更多信息在 此处

¥More information here.

Socket#conn

对底层 Engine.IO 套接字的引用(参见 此处)。

¥A reference to the underlying Engine.IO socket (see here).

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
});
});

附加属性

¥Additional attributes

只要不覆盖任何现有属性,你就可以将任何属性附加到 Socket 实例并在以后使用它:

¥As long as you do not overwrite any existing attribute, you can attach any attribute to the Socket instance and use it later:

// in a middleware
io.use(async (socket, next) => {
try {
const user = await fetchUser(socket);
socket.user = user;
} catch (e) {
next(new Error("unknown user"));
}
});

io.on("connection", (socket) => {
console.log(socket.user);

// in a listener
socket.on("set username", (username) => {
socket.username = username;
});
});

套接字中间件

¥Socket middlewares

这些中间件看起来很像通常的 中间件,只不过它们是为每个传入数据包调用的:

¥Those middlewares look a lot like the usual middlewares, except that they are called for each incoming packet:

socket.use(([event, ...args], next) => {
// do something with the packet (logging, authorization, rate limiting...)
// do not forget to call next() at the end
next();
});

还可以使用错误对象来调用 next 方法。在这种情况下,事件将不会到达已注册的事件处理程序,而是会触发 error 事件:

¥The next method can also be called with an error object. In that case, the event will not reach the registered event handlers and an error event will be emitted instead:

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

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

注意:该功能仅存在于服务器端。对于客户端,你可能对 捕获所有监听器 感兴趣。

¥Note: this feature only exists on the server-side. For the client-side, you might be interested in catch-all listeners.

事件

¥Events

在服务器端,Socket 实例触发两个特殊事件:

¥On the server-side, the Socket instance emits two special events:

disconnect

该事件由 Socket 实例在断开连接时触发。

¥This event is fired by the Socket instance upon disconnection.

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

以下是可能原因的列表:

¥Here is the list of possible reasons:

原因描述
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

此事件与 disconnect 类似,但在 Socket#rooms 集还不为空时触发得更早一些

¥This event is similar to disconnect but is fired a bit earlier, when the Socket#rooms set is not empty yet

io.on("connection", (socket) => {
socket.on("disconnecting", (reason) => {
for (const room of socket.rooms) {
if (room !== socket.id) {
socket.to(room).emit("user has left", socket.id);
}
}
});
});

注意:这些事件以及 connectconnect_errornewListenerremoveListener 都是特殊事件,不应在你的应用中使用:

¥Note: those events, along with connect, connect_error, newListener and removeListener, are special events that shouldn't be used in your application:

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

完整的 API

¥Complete API

Socket 实例暴露的完整 API 可以参见 此处

¥The complete API exposed by the Socket instance can be found here.