在 Socket.IO 项目中,如果服务端只允许 WebSocket 传输,而客户端仍使用默认传输配置,可能会出现连接失败、反复重连等问题。本文介绍其原因、正确配置方式,以及使用 WebSocket-only 模式时需要注意的事项。

一、服务端配置

例如,服务端创建 Socket.IO 实例时指定:

        
const io = new Server(server, {
  adapter: createAdapter(redis, subClient),
  cors: {
    origin: [origin],
    credentials: true
  },
  transports: ["websocket"]
});
        
      

这里的 transports: ["websocket"] 表示:服务端只接受 WebSocket 传输,不接受 HTTP long-polling 传输。

二、客户端为什么也要配置?

Socket.IO 客户端默认通常会先使用 HTTP long-polling 建立连接,随后再尝试升级为 WebSocket。

如果服务端只允许 WebSocket,而客户端仍按默认流程先发起 polling 请求,那么初始请求就可能被服务端拒绝,导致连接无法建立。

因此,服务端限定为 WebSocket 时,客户端也应直接使用 WebSocket:

        
import { io } from "socket.io-client";

const socket = io("https://example.com", {
  transports: ["websocket"],
  withCredentials: true
});
        
      

这样,客户端会直接通过 WebSocket 建立连接,不会先尝试 polling。

三、服务端与客户端配置示例

服务端

        
import { Server } from "socket.io";

const io = new Server(server, {
  adapter: createAdapter(redis, subClient),
  cors: {
    origin: [origin],
    credentials: true
  },
  transports: ["websocket"]
});
        
      

客户端

        
import { io } from "socket.io-client";

const socket = io("https://example.com", {
  transports: ["websocket"],
  withCredentials: true
});

socket.on("connect", () => {
  console.log("Socket.IO 已连接", socket.id);
});

socket.on("connect_error", (error) => {
  console.error("Socket.IO 连接失败", error.message);
});
        
      

四、一定要两端都写 ["websocket"] 吗?

严格来说,关键不是“两端的配置必须完全相同”,而是客户端使用的传输方式必须被服务端支持。

例如:

  • 服务端只支持 WebSocket,客户端却先使用 polling:可能连接失败。

  • 服务端支持 polling 和 WebSocket,客户端只使用 WebSocket:通常可以正常连接。

  • 服务端和客户端都配置为只使用 WebSocket:两端行为明确,适用于只需要 WebSocket 的场景。

所以,在服务端明确设置:

        
transports: ["websocket"]
        
      

时,建议客户端也设置:

        
transports: ["websocket"]
        
      

这可以避免客户端默认使用 polling 与服务端配置不兼容。

五、使用 WebSocket-only 的影响

只使用 WebSocket 的优点是连接方式明确,也避免了先通过 polling 建立连接、再升级到 WebSocket 的过程。但它也意味着客户端失去了 polling 回退能力。

如果用户的网络环境、代理服务器、防火墙或负载均衡配置不支持 WebSocket,客户端就可能无法连接。此时可以考虑让服务端保留默认传输方式:

        
const io = new Server(server, {
  adapter: createAdapter(redis, subClient),
  cors: {
    origin: [origin],
    credentials: true
  }
});
        
      

客户端也使用默认配置:

        
const socket = io("https://example.com", {
  withCredentials: true
});
        
      

Socket.IO 会根据网络情况使用 polling,并尝试升级到 WebSocket。

六、常见排查方向

遇到连接异常时,可以检查以下内容:

  1. 传输方式是否兼容
    服务端只允许 WebSocket 时,确认客户端配置了 transports: ["websocket"]。

  2. 代理或负载均衡是否支持 WebSocket
    Nginx、Ingress、CDN 等中间层需要正确转发 WebSocket 的 Upgrade 请求。

  3. 前后端 Socket.IO 版本是否兼容
    客户端与服务端版本不兼容时,也可能导致连接失败。

  4. 检查 connect_error 信息和浏览器网络面板
    查看握手请求是否成功,以及连接是否被代理或服务端拒绝。

  5. 区分 Redis Adapter 与传输方式
    createAdapter(redis, subClient) 主要用于多实例间转发 Socket.IO 事件;它不负责决定客户端使用 polling 还是 WebSocket。

总结

当 Socket.IO 服务端配置了:

        
transports: ["websocket"]
        
      

就表示服务端不接受 polling。此时建议客户端也配置:

        
transports: ["websocket"]
        
      

确保客户端直接使用服务端支持的传输方式。需要注意的是,WebSocket-only 模式会失去 polling 回退能力,因此部署环境必须支持 WebSocket;如果需要更好的网络兼容性,可以考虑使用 Socket.IO 默认的传输与升级机制。