疑難排解連線問題
透過 管理員介面 可以進一步了解 Socket.IO 部署的狀態。
常見/已知問題
其他常見問題
問題:Socket 無法連線
疑難排解步驟
在用戶端,connect_error
事件提供其他資訊
socket.on("connect_error", (err) => {
// the reason of the error, for example "xhr poll error"
console.log(err.message);
// some additional description, for example the status code of the initial HTTP response
console.log(err.description);
// some additional context, for example the XMLHttpRequest object
console.log(err.context);
});
在伺服器端,connection_error
事件也可能提供一些其他見解
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 | "錯誤的請求" | 通常,這表示伺服器前面的代理程式未正確轉送 WebSocket 標頭(請參閱 此處)。 |
4 | "禁止" | 連線被 allowRequest() 方法拒絕。 |
5 | "不支援的協定版本" | 用戶端版本與伺服器不相容(請參閱 此處)。 |
可能的解釋
您嘗試連線到純 WebSocket 伺服器
如 "Socket.IO 是什麼" 部分所述,Socket.IO 用戶端並非 WebSocket 實作,因此無法與 WebSocket 伺服器建立連線,即使使用 transports: ["websocket"]
也是如此
const socket = io("ws://echo.websocket.org", {
transports: ["websocket"]
});
伺服器無法連線
請確認 Socket.IO 伺服器實際上可以在指定的 URL 連線。你可以使用以下指令測試
curl "<the server URL>/socket.io/?EIO=4&transport=polling"
它應該會傳回類似這樣的內容
0{"sid":"Lbo5JLzTotvW3g2LAAAA","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":20000}
如果不是這樣,請檢查 Socket.IO 伺服器是否正在執行,以及是否有任何阻礙連線的因素。
實作協定 v3 的 v1/v2 伺服器(因此有 EIO=3
)會傳回類似這樣的內容
96:0{"sid":"ptzi_578ycUci8WLB9G1","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":5000}2:40
用戶端與伺服器版本不相容
維持向後相容性是我們的首要任務,但在某些特定情況下,我們必須在協定層級實作一些重大變更
- 從 v1.x 到 v2.0.0(於 2017 年 5 月發布),以改善與非 JavaScript 用戶端的相容性(請參閱 此處)
- 從 v2.x 到 v3.0.0(於 2020 年 11 月發布),以一次修正協定中一些長久存在的問題(請參閱 此處)
v4.0.0
包含 JavaScript 伺服器 API 中的一些重大變更。Socket.IO 協定本身並未更新,因此 v3 用戶端將能夠連線到 v4 伺服器,反之亦然(請參閱 此處)。
例如,使用 v1/v2 用戶端連線到 v3/v4 伺服器將產生以下回應
< HTTP/1.1 400 Bad Request
< Content-Type: application/json
{"code":5,"message":"Unsupported protocol version"}
以下是 JS 用戶端 的相容性表格
JS 用戶端版本 | Socket.IO 伺服器版本 | |||
---|---|---|---|---|
1.x | 2.x | 3.x | 4.x | |
1.x | 是 | 否 | 否 | 否 |
2.x | 否 | 是 | 是1 | 是1 |
3.x | 否 | 否 | 是 | 是 |
4.x | 否 | 否 | 是 | 是 |
[1] 是,使用 allowEIO3: true
以下是 Java 客户端 的相容性表格
Java 客户端版本 | Socket.IO 伺服器版本 | ||
---|---|---|---|
2.x | 3.x | 4.x | |
1.x | 是 | 是1 | 是1 |
2.x | 否 | 是 | 是 |
[1] 是,使用 allowEIO3: true
以下是 Swift 客户端 的相容性表格
Swift 客户端版本 | Socket.IO 伺服器版本 | ||
---|---|---|---|
2.x | 3.x | 4.x | |
v15.x | 是 | 是1 | 是2 |
v16.x | 是3 | 是 | 是 |
[1] 是,使用 allowEIO3: true(伺服器)和 .connectParams(["EIO": "3"])
(客户端)
SocketManager(socketURL: URL(string:"http://localhost:8087/")!, config: [.connectParams(["EIO": "3"])])
[2] 是,allowEIO3: true(伺服器)
[3] 是,使用 .version(.two)
(客户端)
SocketManager(socketURL: URL(string:"http://localhost:8087/")!, config: [.version(.two)])
伺服器未傳送必要的 CORS 標頭
如果您在主控台看到以下錯誤訊息
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at ...
這可能表示
請參閱此處的說明文件 here。
您未啟用黏著式連線(在多伺服器設定中)
當擴充到多個 Socket.IO 伺服器時,您需要確保特定 Socket.IO 連線的所有要求都到達同一個 Socket.IO 伺服器。說明可在此處找到 here。
未執行此操作將導致 HTTP 400 回應,其中包含代碼:{"code":1,"message":"Session ID unknown"}
請參閱此處的說明文件 here。
要求路徑在兩側不匹配
預設情況下,客戶端會傳送(而伺服器會接收)HTTP 要求,其中包含 "/socket.io/" 要求路徑。
這可以使用 path
選項來控制
伺服器
import { Server } from "socket.io";
const io = new Server({
path: "/my-custom-path/"
});
io.listen(3000);
用戶端
import { io } from "socket.io-client";
const socket = io(SERVER_URL, {
path: "/my-custom-path/"
});
在這種情況下,HTTP 要求看起來會像 <SERVER_URL>/my-custom-path/?EIO=4&transport=polling[&...]
。
import { io } from "socket.io-client";
const socket = io("/my-custom-path/");
表示客戶端將嘗試連接到命名為 "/my-custom-path/" 的命名空間,但要求路徑仍將是 "/socket.io/"。
問題:Socket 斷線
疑難排解步驟
首先,請注意,即使在穩定的網路連線中,斷線是很常見且預期的
- 使用者與 Socket.IO 伺服器之間的任何事物都可能遇到暫時故障或重新啟動
- 伺服器本身可能會在自動擴充政策中被終止
- 在行動瀏覽器的案例中,使用者可能會失去連線或從 WiFi 切換到 4G
- 瀏覽器本身可能會凍結不活動的分頁
話雖如此,除非特別告知否則,Socket.IO 伺服器將始終嘗試重新連線。
disconnect
事件提供額外資訊
socket.on("disconnect", (reason, details) => {
// the reason of the disconnection, for example "transport error"
console.log(reason);
// the low-level reason of the disconnection, for example "xhr post error"
console.log(details.message);
// some additional description, for example the status code of the HTTP response
console.log(details.description);
// some additional context, for example the XMLHttpRequest object
console.log(details.context);
});
可能的理由列於此處。
可能的解釋
伺服器和客戶端之間的某個事物關閉連線
如果斷線發生在規律的間隔中,這可能表示伺服器和客戶端之間的某個事物未正確設定,並關閉連線
- nginx
nginx 的 proxy_read_timeout
值(預設為 60 秒)必須大於 Socket.IO 的 pingInterval + pingTimeout
(預設為 45 秒),否則如果在給定的延遲後未傳送資料,它將強制關閉連線,而客戶端將收到「傳輸關閉」錯誤。
- Apache HTTP 伺服器
httpd 的 ProxyTimeout
值(預設為 60 秒)必須大於 Socket.IO 的 pingInterval + pingTimeout
(預設為 45 秒),否則如果在給定的延遲後未傳送資料,它將強制關閉連線,而客戶端將收到「傳輸關閉」錯誤。
瀏覽器標籤已最小化,心跳已失敗
當瀏覽器標籤不在焦點時,某些瀏覽器(例如 Chrome)會限制 JavaScript 計時器,這可能會導致 ping 超時而中斷連線在 Socket.IO v2 中,因為心跳機制依賴於客戶端上的 setTimeout
函式。
作為解決方法,您可以在伺服器端增加 pingTimeout
值
const io = new Server({
pingTimeout: 60000
});
請注意,升級到 Socket.IO v4(至少 socket.io-client@4.1.3
,由於 此)應可防止此類問題,因為心跳機制已反轉(伺服器現在會傳送 PING 封包)。
客戶端與伺服器版本不相容
由於在 v2 和 v3/v4 中透過 WebSocket 傳輸傳送的封包格式相似,您可能可以使用不相容的客戶端進行連線(請參閱上方),但連線最終會在經過一段延遲後關閉。
因此,如果您在 30 秒後遇到定期中斷(這是 Socket.IO v2 中 pingTimeout 和 pingInterval 值的總和),這肯定是由於版本不相容。
您正在嘗試傳送龐大酬載
如果您在傳送龐大酬載時中斷連線,這可能表示您已達到 maxHttpBufferSize
值,其預設值為 1 MB。請根據您的需求調整它
const io = require("socket.io")(httpServer, {
maxHttpBufferSize: 1e8
});
比 pingTimeout
選項值花費更多時間上傳的龐大酬載也會觸發中斷(因為 心跳機制 在上傳期間會失敗)。請根據您的需求調整它
const io = require("socket.io")(httpServer, {
pingTimeout: 60000
});
問題:Socket 停留在 HTTP 長輪詢
疑難排解步驟
在大多數情況下,您應該看到類似的內容
- Engine.IO 握手(包含會在後續要求中使用的會話 ID,在此為
zBjrh...AAAK
) - Socket.IO 握手要求(包含
auth
選項的值) - Socket.IO 握手回應(包含 Socket#id)
- WebSocket 連線
- 第一個 HTTP 長輪詢要求,在建立 WebSocket 連線後會關閉
如果您沒有看到第 4 個要求的 HTTP 101 切換協定 回應,表示伺服器和您的瀏覽器之間有某項內容阻擋了 WebSocket 連線。
請注意,這不一定是阻擋,因為連線仍然透過 HTTP 長輪詢建立,但效率較差。
您可以透過以下方式取得目前傳輸的名稱
客戶端
socket.on("connect", () => {
const transport = socket.io.engine.transport.name; // in most cases, "polling"
socket.io.engine.on("upgrade", () => {
const upgradedTransport = socket.io.engine.transport.name; // in most cases, "websocket"
});
});
伺服器端
io.on("connection", (socket) => {
const transport = socket.conn.transport.name; // in most cases, "polling"
socket.conn.on("upgrade", () => {
const upgradedTransport = socket.conn.transport.name; // in most cases, "websocket"
});
});
可能的說明
伺服器前的代理程式不接受 WebSocket 連線
如果 nginx 或 Apache HTTPD 等代理程式未正確設定為接受 WebSocket 連線,您可能會收到 TRANSPORT_MISMATCH
錯誤
io.engine.on("connection_error", (err) => {
console.log(err.code); // 3
console.log(err.message); // "Bad request"
console.log(err.context); // { name: 'TRANSPORT_MISMATCH', transport: 'websocket', previousTransport: 'polling' }
});
表示 Socket.IO 伺服器未收到必要的 Connection: upgrade
標頭(您可以檢查 err.req.headers
物件)。
請參閱此處的說明文件。
express-status-monitor
執行自己的 socket.io 執行個體
請參閱此處的解決方案。
其他常見問題
重複事件註冊
在客戶端,每次 socket 重新連線時都會發出 connect
事件,因此必須在 connect
事件偵聽器之外註冊事件偵聽器
不佳 ⚠️
socket.on("connect", () => {
socket.on("foo", () => {
// ...
});
});
良好 👍
socket.on("connect", () => {
// ...
});
socket.on("foo", () => {
// ...
});
如果不是這樣,您的事件監聽器可能會被呼叫多次。
延遲事件處理器註冊
不佳 ⚠️
io.on("connection", async (socket) => {
await longRunningOperation();
// WARNING! Some packets might be received by the server but without handler
socket.on("hello", () => {
// ...
});
});
良好 👍
io.on("connection", async (socket) => {
socket.on("hello", () => {
// ...
});
await longRunningOperation();
});
socket.id
屬性的用法
請注意,除非已啟用連線狀態復原,否則 id
屬性為臨時性 ID,不應在您的應用程式中使用(或僅用於除錯目的),因為
- 此 ID 會在每次重新連線後重新產生(例如當 WebSocket 連線中斷,或當使用者重新整理頁面時)
- 兩個不同的瀏覽器分頁將會有兩個不同的 ID
- 伺服器上沒有為特定 ID 儲存訊息佇列(亦即如果用戶端斷線,伺服器傳送至該 ID 的訊息將會遺失)
請改用一般會話 ID(透過 Cookie 傳送,或儲存在 localStorage 中並在auth
payload 中傳送)。
另請參閱
在無伺服器平台上部署
由於大多數無伺服器平台(例如 Vercel)會根據請求處理器的持續時間計費,因此不建議與 Socket.IO(或純粹的 WebSocket)維持長時間連線。
參考資料