Socket 实例(服务器端)
Socket
是与客户端交互的基础类。它继承了 Node.js EventEmitter 的所有方法,如 emit、on、once 或 removeListener。
¥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.
除了:
¥Besides:
¥emitting and listening to events
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
});
请注意,除非启用 连接状态恢复,否则 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);
}
}
});
});
注意:这些事件以及 connect
、connect_error
、newListener
和 removeListener
都是特殊事件,不应在你的应用中使用:
¥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.