跳至主要内容
版本:4.x

连接问题排查

提示

管理 UI 可以为您提供有关 Socket.IO 部署状态的更多见解。

常见/已知问题

其他常见问题

问题:套接字无法连接

故障排除步骤

在客户端,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"会话 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 服务器是否正在运行,以及是否有任何阻止连接的东西。

注意

v1/v2 服务器(实现协议的 v3,因此为 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.x2.x3.x4.x
1.x
2.x11
3.x
4.x

[1]是,使用 allowEIO3: true

以下是 Java 客户端 的兼容性表

Java 客户端版本Socket.IO 服务器版本
2.x3.x4.x
1.x11
2.x

[1]是,使用 allowEIO3: true

以下是 Swift 客户端 的兼容性表

Swift 客户端版本Socket.IO 服务器版本
2.x3.x4.x
v15.x12
v16.x3

[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 ...

这可能意味着

  • 您实际上没有访问 Socket.IO 服务器(见 上方
  • 或者您没有在服务器端启用 跨域资源共享 (CORS)。

请参阅 此处 的文档。

您没有启用粘性会话(在多服务器设置中)

在扩展到多个 Socket.IO 服务器时,您需要确保给定 Socket.IO 会话的所有请求都到达同一个 Socket.IO 服务器。解释可以在 此处 找到。

如果未能做到这一点,将导致 HTTP 400 响应,代码为:{"code":1,"message":"Session ID unknown"}

请参阅 此处 的文档。

请求路径在两端不匹配

默认情况下,客户端发送(服务器也期望)带有 "/socket.io/" 请求路径的 HTTP 请求。

这可以通过 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.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 Server

httpd 的 ProxyTimeout 值(默认 60 秒)必须大于 Socket.IO 的 pingInterval + pingTimeout(默认 45 秒),否则如果在给定延迟后没有发送数据,它将强制关闭连接,客户端将收到“传输关闭”错误。

浏览器标签被最小化,心跳失败

当浏览器标签不在焦点时,一些浏览器(如 Chrome)会限制 JavaScript 定时器,这会导致 **Socket.IO v2 中** 因 ping 超时而断开连接,因为心跳机制依赖于客户端的 setTimeout 函数。

作为解决方法,您可以在服务器端增加 pingTimeout

const io = new Server({
pingTimeout: 60000
});

请注意,升级到 Socket.IO v4(至少 socket.io-client@4.1.3,由于 this)应该可以防止此类问题,因为心跳机制已反转(服务器现在发送 PING 包)。

客户端与服务器版本不兼容

由于通过 WebSocket 传输发送的数据包格式在 v2 和 v3/v4 中类似,您可能能够使用不兼容的客户端连接(参见 above),但连接最终将在给定延迟后关闭。

因此,如果您在 30 秒后遇到定期断开连接(这是 Socket.IO v2 中 pingTimeoutpingInterval 值之和),这肯定是由版本不兼容引起的。

您正在尝试发送一个巨大的有效负载

如果您在发送大型有效负载时断开连接,这可能意味着您已达到 maxHttpBufferSize 值,该值默认为 1 MB。请根据您的需要调整它

const io = require("socket.io")(httpServer, {
maxHttpBufferSize: 1e8
});

一个巨大的有效负载,上传时间超过 pingTimeout 选项的值,也会触发断开连接(因为 心跳机制 在上传过程中失败)。请根据您的需要调整它

const io = require("socket.io")(httpServer, {
pingTimeout: 60000
});

问题:套接字卡在 HTTP 长轮询中

故障排除步骤

在大多数情况下,您应该看到类似于以下内容

Network monitor upon success

  1. Engine.IO 握手(包含会话 ID - 在这里,zBjrh...AAAK - 用于后续请求)
  2. Socket.IO 握手请求(包含 auth 选项的值)
  3. Socket.IO 握手响应(包含 Socket#id
  4. WebSocket 连接
  5. 第一个 HTTP 长轮询请求,在建立 WebSocket 连接后关闭

如果您没有看到 HTTP 101 Switching Protocols 对第 4 个请求的响应,这意味着服务器和浏览器之间存在阻止 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 对象)。

请参见 here 的文档。

express-status-monitor 运行它自己的 socket.io 实例

请参见 here 的解决方案。

其他常见问题

重复事件注册

在客户端,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 有效负载中发送)。

另请参阅

在无服务器平台上部署

由于大多数无服务器平台(如 Vercel)按请求处理程序的持续时间计费,因此不建议使用 Socket.IO(甚至普通的 WebSocket)维护长时间运行的连接。

参考资料