跳至主要內容
版本: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) 與客戶端建立 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])

Server 附加至 httpServer,並使用提供的 options

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])

Server 附加至提供的 port,並使用提供的 options

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 參數為選用,且將在所有連線關閉時呼叫。

資訊

這也會關閉底層的 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);
注意

僅關閉底層的 HTTP 伺服器是不夠的,因為這只會阻止伺服器接受新的連線,但透過 WebSocket 連線的用戶端不會立即斷開連線。

參考:https://node.dev.org.tw/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);
提示

這個方法也可以在多個 Socket.IO 伺服器叢集中使用,搭配相容的轉接器,例如 Postgres 轉接器

在這種情況下,如果你只想影響給定節點上的 socket 實例,你需要使用 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
});
資訊

參數會自動序列化,因此不需要呼叫 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
}
});
注意

在這種情況下,呼叫 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
}
});
});
提示

這個方法也可以在多個 Socket.IO 伺服器叢集中使用,搭配相容的轉接器,例如 Postgres 轉接器

在這種情況下,如果你只想傳回指定節點上的 socket 實例,你需要使用 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 事件的 listeners 陣列尾端。

可用的事件

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])

設定 value 路徑,engine.io 和靜態檔案會在該路徑下提供服務。預設為 /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)。

範例

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(/* ... */);

廣播和預期來自 叢集 中的其他 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");
提示

這個方法也可以在多個 Socket.IO 伺服器叢集中使用,搭配相容的轉接器,例如 Postgres 轉接器

在這種情況下,如果你只想影響給定節點上的 socket 實例,你需要使用 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");
提示

這個方法也可以在多個 Socket.IO 伺服器叢集中使用,搭配相容的轉接器,例如 Postgres 轉接器

在這種情況下,如果你只想影響給定節點上的 socket 實例,你需要使用 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 執行的函式,並接收 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" }
});

更多資訊請見 此處

資訊

如果您正在尋找 Express 中介軟體,請查看 此區段

命名空間

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

表示在由路徑名稱(例如:/chat)識別的特定範圍下連接的 socket 池。

更多資訊請見 此處

屬性

namespace.adapter

命名空間使用的 "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>>
注意

這個方法將在下一版主要版本中移除,請改用 serverSideEmit()fetchSockets()

取得連線到這個命名空間的 Socket 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 <布林> 是否關閉底層連線
  • 傳回 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
});
資訊

參數會自動序列化,因此不需要呼叫 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
}
});
注意

在這種情況下,呼叫 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)。

範例

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 中新增

廣播和預期來自 叢集 中的其他 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 執行的函式,並接收 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" }
});

更多資訊請見 此處

資訊

如果您正在尋找 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

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

Socket 是與瀏覽器用戶端互動的基本類別。Socket 屬於特定 Namespace(預設為 /),並使用底層 Client 進行通訊。

請注意,Socket 與實際的底層 TCP/IP socket 無直接關係,它只是類別的名稱。

在每個 Namespace 內,您也可以定義 Socket 可以加入和離開的任意頻道(稱為 room)。這提供了一種方便的方式來廣播至一群 Socket(請參閱下方的 Socket#to)。

Socket 類別繼承自 EventEmitterSocket 類別會覆寫 emit 方法,且不會修改任何其他 EventEmitter 方法。所有在此記錄但同時也顯示為 EventEmitter 方法(除了 emit)的方法均由 EventEmitter 實作,且 EventEmitter 的文件適用。

更多資訊請參閱 此處

事件

事件:'disconnect'

  • reason <string> 斷線原因(用戶端或伺服器端)

斷線時觸發。

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

可能原因

原因說明
伺服器命名空間斷線使用 socket.disconnect() 強制斷開 Socket 連線。
用戶端命名空間斷線用戶端已使用 socket.disconnect() 手動斷開 Socket 連線。
伺服器關閉伺服器正在關閉。
ping 超時用戶端未在 pingTimeout 延遲時間內傳送 PONG 封包。
傳輸關閉連線已關閉(範例:使用者已失去連線,或網路已從 WiFi 變更為 4G)。
傳輸錯誤連線遇到錯誤。
解析錯誤伺服器收到來自用戶端無效的封包。
強制關閉伺服器收到來自用戶端無效的封包。
強制伺服器關閉用戶端沒有及時加入命名空間(請參閱 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);
});
});
注意

這些事件,連同 connectconnect_errornewListenerremoveListener,是特殊事件,不應在您的應用程式中使用

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

屬性

socket.client

對應的 Client 物件參考。

socket.conn

  • <engine.Socket>

對應的 Client 傳輸連線(engine.io Socket 物件)。這允許存取 IO 傳輸層,它仍然(大多數)抽象化實際的 TCP/IP socket。

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"
提示

這也適用於 Socket.IO 叢集,並搭配相容的轉接器,例如 Postgres 轉接器

socket.handshake

握手詳細資料

欄位類型說明
headersIncomingHttpHeaders作為握手一部分傳送的標頭。
time<字串>建立日期(字串格式)。
address<字串>用戶端的 IP 位址。
xdomain<boolean>連線是否跨網域。
secure<boolean>連線是否透過 SSL 建立。
issued<number>建立日期(Unix 時間戳記格式)。
url<字串>請求 URL 字串。
queryRecord<string, string or string[]>第一個請求的查詢參數。
authRecord<string, any>驗證負載。另請參閱這裡

用法

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

注意

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

關於此功能的更多資訊,請按這裡

socket.request

取得參考的代理,傳回產生底層 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])

中斷此 socket。若 close 的值為 true,則關閉底層連線。否則,僅中斷命名空間。

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

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

(覆寫 EventEmitter.emit)

發射事件至由字串名稱識別的 socket。可包含任何其他參數。支援所有可序列化資料結構,包括 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(以及其他未在此處提及的方法)。請參閱 Node.js 文件中的 事件 模組。

socket.except(rooms)

於 v4.0.0 新增

設定後續事件發射的修改器,表示僅會廣播事件至尚未加入指定 rooms 的客戶端(不包含 socket 本身)。

// 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)

將 socket 加入指定的 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

為方便起見,每個 socket 會自動加入一個由其 id 識別的房間(請參閱 Socket#id)。這讓廣播訊息給其他 socket 變得很容易

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 中移除 socket。

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

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

中斷連線後,會自動離開房間。

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}`);
});
注意

確認不會被萬用監聽器捕獲。

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}`);
});
注意

確認不會被萬用監聽器捕獲。

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(以及其他未在此處提及的方法)。請參閱 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(以及其他未在此處提及的方法)。請參閱 Node.js 文件中的 事件 模組。

socket.removeListener(eventName, listener)

繼承自 EventEmitter(以及其他未在此處提及的方法)。請參閱 Node.js 文件中的 事件 模組。

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

傳送 訊息 事件。請參閱 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初始實作。

設定後續事件發射的修改器,該事件將僅廣播給已加入指定 房間 的用戶端(不包括 socket 本身)。

若要發射到多個房間,您可以多次呼叫 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.2錯誤 事件會直接傳送給用戶端。
v1.6.0第一個實作。

註冊中間件,此為一個函式,會對每個傳入的 封包 執行,並接收封包和一個函式(可選擇將執行延後到下一個已註冊的中間件)。

傳遞給中間件回呼的錯誤會在伺服器端發射為 錯誤 事件

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'

設定後續事件發射的修改器,該事件資料將僅廣播給每個 socket,但不包括傳送者。

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

用戶端類別表示傳入傳輸(engine.io)連線。用戶端可以與屬於不同 命名空間 的多工 Socket 產生關聯。

屬性

client.conn

  • <engine.Socket>

對應的 engine.io Socket 連線參考。

client.request

取得產生 engine.io 連線的 請求 參考的 getter 代理。可用於存取請求標頭,例如 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"傳輸不明"
1"Session ID 不明"
2"錯誤的握手方法"
3"錯誤的請求"
4"禁止"
5"不支援的協定版本"

事件:'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

用於產生新的 Session 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());