客户端 API
IO
io
方法绑定到独立构建中的全局作用域:
¥The io
method is bound to the global scope in the standalone build:
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
</script>
自版本 4.3.0 起,还提供 ESM 打包包:
¥An ESM bundle is also available since version 4.3.0:
<script type="module">
import { io } from "https://cdn.socket.io/4.7.5/socket.io.esm.min.js";
const socket = io();
</script>
使用 导入映射:
¥With an import map:
<script type="importmap">
{
"imports": {
"socket.io-client": "https://cdn.socket.io/4.7.5/socket.io.esm.min.js"
}
}
</script>
<script type="module">
import { io } from "socket.io-client";
const socket = io();
</script>
否则,在所有其他情况下(使用 Node.js 或 React Native 中的某些构建工具),可以从 socket.io-client
包导入:
¥Else, in all other cases (with some build tools, in Node.js or React Native), it can be imported from the socket.io-client
package:
// ES modules
import { io } from "socket.io-client";
// CommonJS
const { io } = require("socket.io-client");
io.protocol
协议修订号(当前:5)。
¥The protocol revision number (currently: 5).
该协议定义了客户端和服务器之间交换的数据包的格式。客户端和服务器必须使用相同的版本才能相互理解。
¥The protocol defines the format of the packets exchanged between the client and the server. Both the client and the server must use the same revision in order to understand each other.
你可以找到更多信息 此处。
¥You can find more information here.
io([url][, options])
url
<string>
(默认为window.location.host
)¥
url
<string>
(defaults towindow.location.host
)options
<Object>
返回
<Socket>
¥Returns
<Socket>
为给定 URL 创建新的 Manager
,并尝试在后续调用中重用现有的 Manager
,除非 multiplex
选项与 false
一起传递。传递此选项相当于传递 "force new connection": true
或 forceNew: true
。
¥Creates a new Manager
for the given URL, and attempts to reuse an existing Manager
for subsequent calls, unless the multiplex
option is passed with false
. Passing this option is the equivalent of passing "force new connection": true
or forceNew: true
.
为 URL 中的路径名指定的命名空间返回一个新的 Socket
实例,默认为 /
。例如,如果 url
是 http://localhost/users
,则将建立到 http://localhost
的传输连接,以及将建立到 /users
的 Socket.IO 连接。
¥A new Socket
instance is returned for the namespace specified by the pathname in the URL, defaulting to /
. For example, if the url
is http://localhost/users
, a transport connection will be established to http://localhost
and a Socket.IO connection will be established to /users
.
还可以使用 query
选项或直接在 url 中提供查询参数(例如:http://localhost/users?token=abc
)。
¥Query parameters can also be provided, either with the query
option or directly in the url (example: http://localhost/users?token=abc
).
要了解幕后发生的情况,请看以下示例:
¥To understand what happens under the hood, the following example:
import { io } from "socket.io-client";
const socket = io("ws://example.com/my-namespace", {
reconnectionDelayMax: 10000,
auth: {
token: "123"
},
query: {
"my-key": "my-value"
}
});
是以下内容的简短版本:
¥is the short version of:
import { Manager } from "socket.io-client";
const manager = new Manager("ws://example.com", {
reconnectionDelayMax: 10000,
query: {
"my-key": "my-value"
}
});
const socket = manager.socket("/my-namespace", {
auth: {
token: "123"
}
});
可用选项的完整列表可在 此处 中找到。
¥The complete list of available options can be found here.
管理者
¥Manager
Manager
管理 Engine.IO client 实例,它是建立与服务器的连接的底层引擎(通过使用 WebSocket 或 HTTP 长轮询等传输)。
¥The Manager
manages the Engine.IO client instance, which is the low-level engine that establishes the connection to the server (by using transports like WebSocket or HTTP long-polling).
Manager
处理重新连接逻辑。
¥The Manager
handles the reconnection logic.
一个 Manager
可以被多个 插座 使用。你可以找到有关此多路复用功能 此处 的更多信息。
¥A single Manager
can be used by several Sockets. You can find more information about this multiplexing feature here.
请注意,在大多数情况下,你不会直接使用 Manager,而是使用 套接字 实例。
¥Please note that, in most cases, you won't use the Manager directly but use the Socket instance instead.
构造函数
¥Constructor
new Manager(url[, options])
可用选项的完整列表可在 此处 中找到。
¥The complete list of available options can be found here.
import { Manager } from "socket.io-client";
const manager = new Manager("https://example.com");
const socket = manager.socket("/"); // main namespace
const adminSocket = manager.socket("/admin"); // admin namespace
事件
¥Events
事件:'错误'
¥Event: 'error'
因连接错误而触发。
¥Fired upon a connection error.
socket.io.on("error", (error) => {
// ...
});
事件:'乒'
¥Event: 'ping'
当从服务器收到 ping 数据包时触发。
¥Fired when a ping packet is received from the server.
socket.io.on("ping", () => {
// ...
});
事件:'重新连接'
¥Event: 'reconnect'
成功重新连接后触发。
¥Fired upon a successful reconnection.
socket.io.on("reconnect", (attempt) => {
// ...
});
事件:'重新连接尝试'
¥Event: 'reconnect_attempt'
尝试重新连接时触发。
¥Fired upon an attempt to reconnect.
socket.io.on("reconnect_attempt", (attempt) => {
// ...
});
事件:'重新连接错误'
¥Event: 'reconnect_error'
重新连接尝试错误时触发。
¥Fired upon a reconnection attempt error.
socket.io.on("reconnect_error", (error) => {
// ...
});
事件:'重新连接失败'
¥Event: 'reconnect_failed'
当无法在 reconnectionAttempts
内重新连接时触发。
¥Fired when couldn't reconnect within reconnectionAttempts
.
socket.io.on("reconnect_failed", () => {
// ...
});
方法
¥Methods
manager.connect([callback])
manager.open([callback]) 的同义词。
¥Synonym of manager.open([callback]).
manager.open([callback])
callback
<Function>
返回
<Manager>
¥Returns
<Manager>
如果管理器是使用 autoConnect
到 false
启动的,则启动新的连接尝试。
¥If the manager was initiated with autoConnect
to false
, launch a new connection attempt.
callback
参数是可选的,一旦尝试失败/成功就会被调用。
¥The callback
argument is optional and will be called once the attempt fails/succeeds.
import { Manager } from "socket.io-client";
const manager = new Manager("https://example.com", {
autoConnect: false
});
const socket = manager.socket("/");
manager.open((err) => {
if (err) {
// an error has occurred
} else {
// the connection was successfully established
}
});
manager.reconnection([value])
设置 reconnection
选项,如果没有传递参数则返回它。
¥Sets the reconnection
option, or returns it if no parameters are passed.
manager.reconnectionAttempts([value])
设置 reconnectionAttempts
选项,如果没有传递参数则返回它。
¥Sets the reconnectionAttempts
option, or returns it if no parameters are passed.
manager.reconnectionDelay([value])
设置 reconnectionDelay
选项,如果没有传递参数则返回它。
¥Sets the reconnectionDelay
option, or returns it if no parameters are passed.
manager.reconnectionDelayMax([value])
设置 reconnectionDelayMax
选项,如果没有传递参数则返回它。
¥Sets the reconnectionDelayMax
option, or returns it if no parameters are passed.
manager.socket(nsp, options)
为给定命名空间创建一个新的 Socket
。从 options
对象中仅读取 auth
({ auth: {key: "value"} }
)。其他键将被忽略,并应在实例化 new Manager(nsp, options)
时传递。
¥Creates a new Socket
for the given namespace. Only auth
({ auth: {key: "value"} }
) is read from the options
object. Other keys will be ignored and should be passed when instancing a new Manager(nsp, options)
.
manager.timeout([value])
设置 timeout
选项,如果没有传递参数则返回它。
¥Sets the timeout
option, or returns it if no parameters are passed.
套接字
¥Socket
Socket
是与服务器交互的基本类。Socket
属于某个 命名空间(默认为 /
),并使用底层 管理者 进行通信。
¥A Socket
is the fundamental class for interacting with the server. A Socket
belongs to a certain Namespace (by default /
) and uses an underlying Manager to communicate.
Socket
基本上是 EventEmitter,它通过网络向服务器发送事件并从服务器接收事件。
¥A Socket
is basically an EventEmitter which sends events to — and receive events from — the server over the network.
socket.emit("hello", { a: "b", c: [] });
socket.on("hey", (...args) => {
// ...
});
更多信息可参见 此处。
¥More information can be found here.
事件
¥Events
事件:'connect'
¥Event: 'connect'
该事件由 Socket 实例在连接和重新连接时触发。
¥This event is fired by the Socket instance upon connection and reconnection.
socket.on("connect", () => {
// ...
});
事件处理程序不应在 connect
处理程序本身中注册,因为每次套接字实例重新连接时都会注册一个新的处理程序:
¥Event handlers shouldn't be registered in the connect
handler itself, as a new handler will be registered every time the socket instance reconnects:
不好:警告:
¥BAD ⚠️
socket.on("connect", () => {
socket.on("data", () => { /* ... */ });
});
GOOD 👍
socket.on("connect", () => {
// ...
});
socket.on("data", () => { /* ... */ });
事件:'连接错误'
¥Event: 'connect_error'
error
<Error>
连接失败时会触发此事件。
¥This event is fired upon connection failure.
原因 | 自动重连? |
---|---|
无法建立底层连接(暂时失败) | :白色复选标记:是的 |
连接在 中间件函数 中被服务器拒绝 | :X:不 |
socket.active
属性表示 socket 是否会在小 随机延迟 后自动尝试重新连接:
¥The socket.active
attribute indicates whether the socket will automatically try to reconnect after a small randomized delay:
socket.on("connect_error", (error) => {
if (socket.active) {
// temporary failure, the socket will automatically try to reconnect
} else {
// the connection was denied by the server
// in that case, `socket.connect()` must be manually called in order to reconnect
console.log(error.message);
}
});
事件:'disconnect'
¥Event: 'disconnect'
reason
<string>
details
<DisconnectDetails>
断开连接时会触发此事件。
¥This event is fired upon disconnection.
socket.on("disconnect", (reason, details) => {
// ...
});
以下是可能原因的列表:
¥Here is the list of possible reasons:
原因 | 描述 | 自动重连? |
---|---|---|
io server disconnect | 服务器已强制断开与 socket.disconnect() 的套接字 | :X:不 |
io client disconnect | 使用 socket.disconnect() 手动断开套接字 | :X:不 |
ping timeout | 服务器未发送 pingInterval + pingTimeout 范围内的 PING | :白色复选标记:是的 |
transport close | 连接已关闭(例如:用户失去连接,或网络从 WiFi 更改为 4G) | :白色复选标记:是的 |
transport error | 连接遇到错误(例如:服务器在 HTTP 长轮询周期期间被终止) | :白色复选标记:是的 |
socket.active
属性表示 socket 是否会在小 随机延迟 后自动尝试重新连接:
¥The socket.active
attribute indicates whether the socket will automatically try to reconnect after a small randomized delay:
socket.on("disconnect", (reason) => {
if (socket.active) {
// temporary disconnection, the socket will automatically try to reconnect
} else {
// the connection was forcefully closed by the server or the client itself
// in that case, `socket.connect()` must be manually called in order to reconnect
console.log(reason);
}
});
属性
¥Attributes
socket.active
套接字是否会自动尝试重新连接。
¥Whether the socket will automatically try to reconnect.
连接失败后可以使用该属性:
¥This attribute can be used after a connection failure:
socket.on("connect_error", (error) => {
if (socket.active) {
// temporary failure, the socket will automatically try to reconnect
} else {
// the connection was denied by the server
// in that case, `socket.connect()` must be manually called in order to reconnect
console.log(error.message);
}
});
或者断开连接后:
¥Or after a disconnection:
socket.on("disconnect", (reason) => {
if (socket.active) {
// temporary disconnection, the socket will automatically try to reconnect
} else {
// the connection was forcefully closed by the server or the client itself
// in that case, `socket.connect()` must be manually called in order to reconnect
console.log(reason);
}
});
socket.connected
套接字当前是否连接到服务器。
¥Whether the socket is currently connected to the server.
const socket = io();
console.log(socket.connected); // false
socket.on("connect", () => {
console.log(socket.connected); // true
});
socket.disconnected
套接字当前是否与服务器断开连接。
¥Whether the socket is currently disconnected from the server.
const socket = io();
console.log(socket.disconnected); // true
socket.on("connect", () => {
console.log(socket.disconnected); // false
});
socket.id
套接字会话的唯一标识符。在 connect
事件触发后设置,在 reconnect
事件后更新。
¥A unique identifier for the socket session. Set after the connect
event is triggered, and updated after the reconnect
event.
const socket = io();
console.log(socket.id); // undefined
socket.on("connect", () => {
console.log(socket.id); // "G5p5..."
});
id
属性是一个临时 ID,不应在你的应用中使用(或仅用于调试目的),因为:
¥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
对底层 管理者 的引用。
¥A reference to the underlying Manager.
socket.on("connect", () => {
const engine = socket.io.engine;
console.log(engine.transport.name); // in most cases, prints "polling"
engine.once("upgrade", () => {
// called when the transport is upgraded (i.e. from HTTP long-polling to WebSocket)
console.log(engine.transport.name); // in most cases, prints "websocket"
});
engine.on("packet", ({ type, data }) => {
// called for each packet received
});
engine.on("packetCreate", ({ type, data }) => {
// called for each packet sent
});
engine.on("drain", () => {
// called when the write buffer is drained
});
engine.on("close", (reason) => {
// called when the underlying connection is closed
});
});
socket.recovered
v4.6.0 中添加
¥Added in v4.6.0
上次重新连接时连接状态是否成功恢复。
¥Whether the connection state was successfully recovered during the last reconnection.
socket.on("connect", () => {
if (socket.recovered) {
// any event missed during the disconnection period will be received now
} else {
// new or unrecoverable session
}
});
有关此功能的更多信息 此处。
¥More information about this feature here.
方法
¥Methods
socket.close()
v1.0.0 中添加
¥Added in v1.0.0
socket.disconnect() 的同义词。
¥Synonym of socket.disconnect().
socket.compress(value)
设置后续事件触发的修饰符,仅当值为 true
时才会压缩事件数据。当你不调用该方法时默认为 true
。
¥Sets a modifier for a subsequent event emission that the event data will only be compressed if the value is true
. Defaults to true
when you don't call the method.
socket.compress(false).emit("an event", { some: "data" });
socket.connect()
v1.0.0 中添加
¥Added in v1.0.0
返回
Socket
¥Returns
Socket
手动连接套接字。
¥Manually connects the socket.
const socket = io({
autoConnect: false
});
// ...
socket.connect();
它还可用于手动重新连接:
¥It can also be used to manually reconnect:
socket.on("disconnect", () => {
socket.connect();
});
socket.disconnect()
v1.0.0 中添加
¥Added in v1.0.0
手动断开套接字。在这种情况下,套接字将不会尝试重新连接。
¥Manually disconnects the socket. In that case, the socket will not try to reconnect.
相关断线原因:
¥Associated disconnection reason:
客户端:
"io client disconnect"
¥client-side:
"io client disconnect"
服务器端:
"client namespace disconnect"
¥server-side:
"client namespace disconnect"
如果这是 Manager 的最后一个活动 Socket 实例,则底层连接将被关闭。
¥If this is the last active Socket instance of the Manager, the low-level connection will be closed.
socket.emit(eventName[, ...args][, ack])
args
<any[]>
ack
<Function>
返回
true
¥Returns
true
向由字符串名称标识的套接字触发事件。可以包括任何其他参数。支持所有可序列化的数据结构,包括 Buffer
。
¥Emits an event to the socket identified by the string name. Any other parameters can be included. All serializable data structures are supported, including Buffer
.
socket.emit("hello", "world");
socket.emit("with-binary", 1, "2", { 3: "4", 5: Buffer.from([6, 7, 8]) });
ack
参数是可选的,将与服务器应答一起调用。
¥The ack
argument is optional and will be called with the server answer.
客户端
¥Client
socket.emit("hello", "world", (response) => {
console.log(response); // "got it"
});
服务器
¥Server
io.on("connection", (socket) => {
socket.on("hello", (arg, callback) => {
console.log(arg); // "world"
callback("got it");
});
});
socket.emitWithAck(eventName[, ...args])
v4.6.0 中添加
¥Added in v4.6.0
args
any[]
返回
Promise<any>
¥Returns
Promise<any>
基于 promise 的版本,触发并期待服务器的确认:
¥Promised-based version of emitting and expecting an acknowledgement from the server:
// 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 server did not acknowledge the event in the given delay
}
上面的例子相当于:
¥The example above is equivalent to:
// without timeout
socket.emit("hello", "world", (val) => {
// ...
});
// with a specific timeout
socket.timeout(10000).emit("hello", "world", (err, val) => {
// ...
});
在接收端:
¥And on the receiving side:
io.on("connection", (socket) => {
socket.on("hello", (arg1, callback) => {
callback("got it"); // only one argument is expected
});
});
不支持 Promise 需要添加 polyfill 才能使用此功能的环境。
¥Environments that do not support Promises will need to add a polyfill in order to use this feature.
socket.listeners(eventName)
继承自 EventEmitter 类。
¥Inherited from the EventEmitter class.
返回
<Function[]>
¥Returns
<Function[]>
返回名为 eventName
的事件的监听器数组。
¥Returns the array of listeners for the event named eventName
.
socket.on("my-event", () => {
// ...
});
console.log(socket.listeners("my-event")); // prints [ [Function] ]
socket.listenersAny()
v3.0.0 中添加
¥Added in v3.0.0
返回
<Function[]>
¥Returns
<Function[]>
返回已注册的捕获所有监听器的列表。
¥Returns the list of registered catch-all listeners.
const listeners = socket.listenersAny();
socket.listenersAnyOutgoing()
v4.5.0 中添加
¥Added in v4.5.0
返回
<Function[]>
¥Returns
<Function[]>
返回已注册的传出数据包捕获所有监听器的列表。
¥Returns the list of registered catch-all listeners for outgoing packets.
const listeners = socket.listenersAnyOutgoing();
socket.off([eventName][, listener])
继承自 EventEmitter 类。
¥Inherited from the EventEmitter class.
listener
<Function>
返回
<Socket>
¥Returns
<Socket>
从名为 eventName
的事件的监听器数组中删除指定的 listener
。
¥Removes the specified listener
from the listener array for the event named eventName
.
const myListener = () => {
// ...
}
socket.on("my-event", myListener);
// then later
socket.off("my-event", myListener);
listener
参数也可以省略:
¥The listener
argument can also be omitted:
// remove all listeners for that event
socket.off("my-event");
// remove all listeners for all events
socket.off();
socket.offAny([listener])
v3.0.0 中添加
¥Added in v3.0.0
listener
<Function>
删除先前注册的监听器。如果未提供监听器,则删除所有捕获所有监听器。
¥Removes the previously registered listener. If no listener is provided, all catch-all listeners are removed.
const myListener = () => { /* ... */ };
socket.onAny(myListener);
// then, later
socket.offAny(myListener);
socket.offAny();
socket.offAnyOutgoing([listener])
v4.5.0 中添加
¥Added in v4.5.0
listener
<Function>
删除先前注册的监听器。如果未提供监听器,则删除所有捕获所有监听器。
¥Removes the previously registered listener. If no listener is provided, all catch-all listeners are removed.
const myListener = () => { /* ... */ };
socket.onAnyOutgoing(myListener);
// remove a single listener
socket.offAnyOutgoing(myListener);
// remove all listeners
socket.offAnyOutgoing();
socket.on(eventName, callback)
继承自 EventEmitter 类。
¥Inherited from the EventEmitter class.
listener
<Function>
返回
<Socket>
¥Returns
<Socket>
为给定事件注册一个新的处理程序。
¥Register a new handler for the given event.
socket.on("news", (data) => {
console.log(data);
});
// with multiple arguments
socket.on("news", (arg1, arg2, arg3, arg4) => {
// ...
});
// with callback
socket.on("news", (cb) => {
cb(0);
});
socket.onAny(callback)
v3.0.0 中添加
¥Added in v3.0.0
callback
<Function>
注册一个新的包罗万象的监听器。
¥Register a new catch-all listener.
socket.onAny((event, ...args) => {
console.log(`got ${event}`);
});
回执 没有被捕获在包罗万象的监听器中。
¥Acknowledgements are not caught in the catch-all listener.
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 中添加
¥Added in v4.5.0
callback
<Function>
为传出数据包注册一个新的捕获所有监听器。
¥Register a new catch-all listener for outgoing packets.
socket.onAnyOutgoing((event, ...args) => {
console.log(`got ${event}`);
});
回执 没有被捕获在包罗万象的监听器中。
¥Acknowledgements are not caught in the catch-all listener.
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, callback)
继承自 EventEmitter 类。
¥Inherited from the EventEmitter class.
listener
<Function>
返回
<Socket>
¥Returns
<Socket>
为名为 eventName
的事件添加一次性 listener
函数。下次触发 eventName
时,该监听器将被删除,然后被调用。
¥Adds a one-time listener
function for the event named eventName
. The next time eventName
is triggered, this listener is removed and then invoked.
socket.once("my-event", () => {
// ...
});
socket.open()
v1.0.0 中添加
¥Added in v1.0.0
socket.connect() 的同义词。
¥Synonym of socket.connect().
socket.prependAny(callback)
v3.0.0 中添加
¥Added in v3.0.0
callback
<Function>
注册一个新的包罗万象的监听器。监听器被添加到监听器数组的开头。
¥Register a new catch-all listener. The listener is added to the beginning of the listeners array.
socket.prependAny((event, ...args) => {
console.log(`got ${event}`);
});
socket.prependAnyOutgoing(callback)
v4.5.0 中添加
¥Added in v4.5.0
callback
<Function>
为传出数据包注册一个新的捕获所有监听器。监听器被添加到监听器数组的开头。
¥Register a new catch-all listener for outgoing packets. The listener is added to the beginning of the listeners array.
socket.prependAnyOutgoing((event, ...args) => {
console.log(`got ${event}`);
});
socket.send([...args][, ack])
args
<any[]>
ack
<Function>
返回
<Socket>
¥Returns
<Socket>
发送 message
事件。参见 socket.emit(eventName[, ...args][, ack])。
¥Sends a message
event. See socket.emit(eventName[, ...args][, ack]).
socket.timeout(value)
v4.4.0 中添加
¥Added in v4.4.0
为后续事件触发设置一个修饰符,当给定的毫秒数过去而没有服务器确认时,将调用回调并出现错误:
¥Sets a modifier for a subsequent event emission that the callback will be called with an error when the given number of milliseconds have elapsed without an acknowledgement from the server:
socket.timeout(5000).emit("my-event", (err) => {
if (err) {
// the server did not acknowledge the event in the given delay
}
});
标志
¥Flags
标志:'volatile'
¥Flag: 'volatile'
v3.0.0 中添加
¥Added in v3.0.0
为后续事件触发设置修饰符,指示在以下情况下可能会丢弃数据包:
¥Sets a modifier for the subsequent event emission indicating that the packet may be dropped if:
插座未连接
¥the socket is not connected
底层传输不可写(例如,当
POST
请求已在 HTTP 长轮询模式下运行时)¥the low-level transport is not writable (for example, when a
POST
request is already running in HTTP long-polling mode)
socket.volatile.emit(/* ... */); // the server may or may not receive it