连接问题排查提示管理 UI 可以为您提供有关 Socket.IO 部署状态的更多见解。
常见/已知问题
套接字无法连接套接字断开连接套接字卡在 HTTP 长轮询中其他常见问题
重复事件注册延迟事件处理程序注册使用 socket.id 属性在无服务器平台上部署问题:套接字无法连接故障排除步骤在客户端,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 "
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.x1.x是否否否2.x否是是1是13.x否否是是4.x否否是是[1]是,使用 allowEIO3: true
以下是 Java 客户端 的兼容性表
Java 客户端版本Socket.IO 服务器版本2.x3.x4.x1.x是是1是12.x否是是[1]是,使用 allowEIO3: true
以下是 Swift 客户端 的兼容性表
Swift 客户端版本Socket.IO 服务器版本2.x3.x4.xv15.x是是1是2v16.x是3是是[1]是,使用 allowEIO3: true(服务器)和 .connectParams(["EIO": "3"])(客户端)
SocketManager(socketURL: URL(string:"https://:8087/")!, config: [.connectParams(["EIO": "3"])])[2]是,allowEIO3: true(服务器)
[3]是,使用 .version(.two)(客户端)
SocketManager(socketURL: URL(string:"https://: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 请求将类似于
警告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);});可能的理由列在 此处。
可能的解释服务器和客户端之间的某些东西关闭了连接如果断开连接以规律的时间间隔发生,这可能表明服务器和客户端之间的某些东西配置不当,并关闭了连接
nginxnginx 的 proxy_read_timeout 值(默认值为 60 秒)必须大于 Socket.IO 的 pingInterval + pingTimeout(默认值为 45 秒),否则如果在给定延迟后没有发送数据,它将强制关闭连接,客户端将收到 "传输关闭" 错误。
Apache HTTP Serverhttpd 的 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 有效负载中发送)。
另请参阅
我们私人消息指南的第二部分如何处理 cookie在无服务器平台上部署由于大多数无服务器平台(如 Vercel)按请求处理程序的持续时间计费,因此不建议使用 Socket.IO(甚至普通的 WebSocket)维护长时间运行的连接。
参考资料
https://vercel.com/guides/do-vercel-serverless-functions-support-websocket-connectionshttps://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api.html