连接问题排查
管理 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.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 ...
这可能意味着
请参阅 此处 的文档。
您没有启用粘性会话(在多服务器设置中)
在扩展到多个 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 中 pingTimeout 和 pingInterval 值之和),这肯定是由版本不兼容引起的。
您正在尝试发送一个巨大的有效负载
如果您在发送大型有效负载时断开连接,这可能意味着您已达到 maxHttpBufferSize
值,该值默认为 1 MB。请根据您的需要调整它
const io = require("socket.io")(httpServer, {
maxHttpBufferSize: 1e8
});
一个巨大的有效负载,上传时间超过 pingTimeout
选项的值,也会触发断开连接(因为 心跳机制 在上传过程中失败)。请根据您的需要调整它
const io = require("socket.io")(httpServer, {
pingTimeout: 60000
});
问题:套接字卡在 HTTP 长轮询中
故障排除步骤
在大多数情况下,您应该看到类似于以下内容
- Engine.IO 握手(包含会话 ID - 在这里,
zBjrh...AAAK
- 用于后续请求) - Socket.IO 握手请求(包含
auth
选项的值) - Socket.IO 握手响应(包含 Socket#id)
- WebSocket 连接
- 第一个 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)维护长时间运行的连接。
参考资料