Socket.io 1.x:只使用 WebSockets?

Posted

技术标签:

【中文标题】Socket.io 1.x:只使用 WebSockets?【英文标题】:Socket.io 1.x: use WebSockets only? 【发布时间】:2015-03-30 02:10:11 【问题描述】:

出于不同的原因,我们正在开发一个只能在现代浏览器 (IE10+) 上运行的网络应用程序。

我们实现的功能之一是 Socket.io 1.x。但是,默认情况下,Socket.io 客户端尝试支持旧版浏览器,因此它使用长轮询启动连接,然后将其更新到 WebSockets。这是在浪费时间和资源,因为我们确定浏览器支持 WS。

我四处搜索,我只能找到this wiki page,但是,它是关于 Socket.io 0.9 的。

最终,我找到了the documentation for engine.io-client(Socket.io-client 基于 1.x 分支)。这是我编写的代码,似乎正在运行。但是,我想知道它是正确的还是我做错了什么:

io.connect('https://...', 
    upgrade: false,
    transports: ['websocket']
)

奇怪的是,仅仅将transports 属性设置为一个数组,其中只有websockets 是不够的;我还必须禁用upgrade。这是正确的吗?

更新

我有了一些新发现。

仅将transports 设置为['websocket'],无论是否启用upgrade 都没有任何区别。这正常吗?

【问题讨论】:

Google 员工注意事项:如果您遇到相反的问题(仅需要使用 HTTP),您可以使用io.set('transports', ['polling']);,或者如果您使用的是 NestJS, @WebSocketGateway( transports: ['polling'] ). 【参考方案1】:

socket.io 有两种类型的“升级”。首先(在 socket.io 1.0+ 中),socket.io 用一个 http 轮询请求启动所有连接,它实际上可能只用一个 http 请求交换一些初始数据。然后,在那之后的某个时刻,它会尝试实际启动一个 webSocket 连接。 webSocket 连接是通过发送指定upgrade: websocket 标头的特定类型的http 请求来完成的,然后服务器可以适当地响应它是否支持websocket。如果服务器同意升级,那么特定的 http 连接将“升级”到 webSocket 协议。此时,客户端知道支持 webSocket 并停止使用轮询 http 请求,从而完成其 upgrade 到 webSocket。

您可以通过在客户端执行此操作来完全阻止初始 http 轮询:

var socket = io(transports: ['websocket'], upgrade: false);

这将阻止来自您自己的合作客户端的轮询连接。如果您想阻止任何客户端使用轮询,则可以将其添加到服务器:

io.set('transports', ['websocket']);

但是,如果您在服务器上设置此选项,最初使用 http 轮询连接的 socket.io 客户端将根本无法工作。因此,这应该只与客户端中的正确设置相匹配,这样客户端就不会开始轮询。

这将告诉两端您只想使用 webSockets,而 socket.io 将在开始时跳过额外的 http 轮询。公平警告,这样做需要 webSocket 支持,因此这排除了与尚不支持 webSocket 的旧版本的 IE 兼容。如果你想保持兼容性,那么就让 socket.io 开始处理几个 http 请求。


这里有更多关于从 http 到 webSocket 协议升级的信息。

webSockets 协议使用 HTTP 连接启动每个 webSocket。这就是所有 webSockets 的工作方式。该 HTTP 连接包含一些标头,表明浏览器“希望”升级到 webSockets 协议。如果服务器支持该协议,那么它会响应告诉客户端它将升级到 webSocket 协议,然后那个套接字就会从 HTTP 协议切换到 webSocket 协议。这就是 webSocket 连接的设计方式。因此,您看到您的 webSocket 连接以 HTTP 连接开始这一事实是 100% 正常的。

您可以将 socket.io 配置为从不使用长轮询,如果这会让您感觉更好,但这不会改变 webSocket 连接仍然以 HTTP 连接开始,然后升级到 webSocket 协议并且它将不能提高支持 webSockets 的现代浏览器的运行效率。但是,它将使您的连接无法在旧版浏览器中运行。

【讨论】:

感谢您的解释。那么,禁用upgrade 有什么作用?通过禁用它,我在 Chrome 检查器上看到向wss://.../socket.io/?EIO=3&transport=websocket 发出 GET 请求,响应状态代码为 101(切换协议)。该请求始终处于“待处理”状态,我可以看到客户端和服务器交换的帧。 其实更诡异。通过仅将传输设置为 websocket,无论升级是真还是假,检查器都没有任何区别。在这两种情况下,我只看到一个请求,即上面的一个(它始终是 HTTP 请求,是的,但在 wss:// 协议上发出) 如果我启用“轮询”,另一方面,如果升级为真,我可以首先看到一些请求,然后是 WSS 请求。升级为 false 时,websocket 永远不会启动。 @Qualcuno - 由于所有 webSocket 连接都以 HTTP 请求开始,然后“升级”到 webSocket 协议,因此如果关闭 upgrade 选项可以让它永远不会变成 webSocket 连接。要确认这一点,必须研究 engine.io 源代码,因为文档并没有真正解释。如果不进一步研究源代码,我并不确切知道。这是good reference,如果您想查看 webSocket 连接是如何启动的。 @Qualcuno 您的观察是正确的。 Socket.IO 中的“升级”并不是指 HTTP 到 WSS 协议的升级,这通常被误解,而是指 Socket.IO 连接从长轮询 AJAX 连接到 WebSocket 的升级。如果您已经从 WebSocket 开始(这不是默认设置),则 upgrade false 无效,因为您不需要升级。如果您从轮询开始并禁用升级,那么它会保持这种状态并且不会升级到 WebSocket。详情见my answer【参考方案2】:

我想我应该添加到上面接受的答案,好像有人想消除 XHR 轮询传输并立即启动 websockets。下面的代码只是为了给出实现的一个想法:

var url = serverUrl + "/ssClients"  //ssClients is the socket.io namespace

var connectionOptions =  
    "force new connection" : true,
    "reconnection": true,
    "reconnectionDelay": 2000,                  //starts with 2 secs delay, then 4, 6, 8, until 60 where it stays forever until it reconnects
    "reconnectionDelayMax" : 60000,             //1 minute maximum delay between connections
    "reconnectionAttempts": "Infinity",         //to prevent dead clients, having the user to having to manually reconnect after a server restart.
    "timeout" : 10000,                           //before connect_error and connect_timeout are emitted.
    "transports" : ["websocket"]                //forces the transport to be only websocket. Server needs to be setup as well/

var socket = require("socket.io-client")(url, connectionOptions); 

socket.on("connect", function (_socket) 
    logger.info("Client connected to server: " + clientName);
    logger.info("Transport being used: " + socket.io.engine.transport.name);

    socket.emit("join", clientName, function(_socketId)   //tell the server the client name
        logger.info("Client received acknowledgement from server: " + _socketId);
        logger.info("Transport being used after acknowledgement: " + socket.io.engine.transport.name);

    );
);

服务器设置好后,你会看到:

2015-10-23T19:04:30.076Z - info:    Client connected to server: someClientId 
2015-10-23T19:04:30.077Z - info:    Transport being used: websocket 
2015-10-23T19:04:30.081Z - info:    Client received acknowledgement from server: aMH0SmW8CbiL8w5RAAAA
2015-10-23T19:04:30.081Z - info:    Transport being used after acknowledgement: websocket

如果您不强制传输,您会看到“轮询”而不是 websocket。但是,这不仅仅发生在客户端,还必须设置服务器:

var io = require("socket.io")(server,  adapter: adapter, log: false ); //attach io to existing Express (http) server
..
io.set('transports', ['websocket']); //forces client to connect as websockets. If client tries xhr polling, it won't connect.

危险

如果客户端确实支持 websocket 协议,连接将不会发生,客户端将报告xhr poll error

这对我来说非常有效,因为我可以控制我拥有的客户端,所以我可以立即强制使用 websocket,我相信这就是最初的问题。我希望这可以帮助那里的人...

【讨论】:

如果客户端已经禁用 XHR 轮询,那么在服务器上禁用 XHR 轮询有什么意义/用途? 出色的工作!谢谢。就我而言,我指的是https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js,因此为了能够连接到我的websocket,我只使用了var url = 'http://localhost:8080'; var socket = io.connect(url,connectionOptions);并获得了我的状态101 =) 嗨,如果我在服务器上使用 Node.js Express Clustering 模块,那么这可以工作吗...?【参考方案3】:

要告诉 Socket.IO 只使用 WebSocket 而不是首先使用几个 XHR 请求,只需将其添加到 Node 服务器:

io.set('transports', ['websocket']);

然后在客户端添加这个:

var socket = io(transports: ['websocket']);

这告诉 Socket.IO 只使用 WebSocket 协议而不使用其他协议;它更干净、更快,并且在客户端和服务器端使用的资源更少。

现在您只会在网络请求列表中看到一个 WebSocket 连接,请记住 IE9 及更早版本无法使用 WebSocket。

【讨论】:

你建议使用这个吗? 我个人使用这个,是的,我建议任何不关心 IE9 或更旧浏览器的人也使用它,即如果你生活在现代,在 2017 年,是的,你应该避免 XHR 回退并且只使用 websocket 传输。 IE9 = 是的,我已经在我的网站上实现了这个。效果很棒。我在控制台上想知道为什么有很多对 socketio 的请求,你的解决方案修复了它。是的,我不在乎 0.01% :) 因为我不知道还有谁还在使用 IE,除了我的爷爷。【参考方案4】:

我发布该答案是因为接受的答案不正确 - 它混淆了从长轮询 AJAX 到 WebSocket 的 Socket.IO 升级与 WSS 协议“连接:升级”请求。问题不在于 WebSocket 连接从 HTTP 开始并升级到 WebSocket - 它怎么可能不呢? - 但是即使在支持 WebSocket 的浏览器上,Socket.IO 也以长轮询 AJAX 连接开始,并且仅在交换一些流量后才升级它。在 Firefox 或 Chrome 的开发者工具中很容易看到。

问题的作者在他的观察中是正确的。 Socket.IO 中的“升级”并不是指 HTTP 到 WSS 协议的升级,这通常被误解,而是指 Socket.IO 连接从长轮询 AJAX 连接到 WebSocket 的升级。如果您已经从 WebSocket 开始(这不是默认设置),则 upgrade false 无效,因为您不需要升级。如果您从轮询开始并禁用升级,那么它会保持这种状态并且不会升级到 WebSocket。

如果您想避免从长轮询开始,请参阅 arnoldNick Steele 的回答。我会更详细地解释发生了什么。

这是我在 my experiments 中使用简单的 WebSocket 和 Socket.IO 应用程序观察到的:

WebSocket

2 个请求,1.50 KB,0.05 秒

来自这两个请求:

    html 页面本身 连接升级到 WebSocket

(连接升级请求在开发者工具上可见,并带有 101 Switching Protocols 响应。)

Socket.IO

6 个请求,181.56 KB,0.25 秒

来自这 6 个请求:

    HTML 页面本身 Socket.IO 的 javascript(180 KB) 第一个长轮询 AJAX 请求 第二个长轮询 AJAX 请求 第三次长轮询 AJAX 请求 连接升级到 WebSocket

详情

我在 localhost 上得到的 WebSocket 结果:

我在 localhost 上得到的 Socket.IO 结果:

测试自己

我发布了代码on npm和on GitHub,大家可以自己运行:

# Install:
npm i -g websocket-vs-socket.io
# Run the server:
websocket-vs-socket.io

并遵守规定。卸载:

# Uninstall:
npm rm -g websocket-vs-socket.io

请参阅this answer 了解更多信息。

【讨论】:

在一个地方收集所有数据的工作很棒!谢谢!如果所有的工作都像这辈子一样彻底,就会有一本手册:)

以上是关于Socket.io 1.x:只使用 WebSockets?的主要内容,如果未能解决你的问题,请参考以下文章

websocket 与Socket.IO介绍

基于heroku socket.io服务器的heroku空闲连接超时

使用websockets手动连接socket.io 1.x,容量测试

Socket IO AngularJS(1.x) 工厂注入多次发射

如何使socket.io只连接一次

WebSocket的几个模块(node.js)(未完)