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

     2023-03-07     275

关键词:

【中文标题】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 集群?

】没有粘性会话的Socket.io1.x集群?【英文标题】:Socket.io1.xclusteringwithoutstickysession?【发布时间】:2014-12-1901:14:09【问题描述】:有没有办法在没有这些粘性会话的情况下使用Redis集群Socket.io?【问题讨论】:【参考方案1】:所... 查看详情

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

】SocketIOAngularJS(1.x)工厂注入多次发射【英文标题】:SocketIOAngularJS(1.x)FactoryInjectionMultipleEmits【发布时间】:2018-07-1119:17:31【问题描述】:在我的服务器端,我使用以下工厂在我的控制器之间刷新/发送数据:angularApp.factory(\'socket\... 查看详情

回退如何与 socket.io 一起工作?

...et.io?【发布时间】:2015-12-2917:39:02【问题描述】:我想将WebSocket与Java一起使用。问题是,我的服务器通过无法配置的代理与客户端分开。我一直在寻找WebSocket的实现以及long-polling等后备选项。我找到了socket.io,但不知道后备是... 查看详情

如何使socket.io只连接一次

】如何使socket.io只连接一次【英文标题】:Howtomakesocket.ioonlyconnectonce【发布时间】:2020-10-1017:44:45【问题描述】:当我转到另一个屏幕或更新页面时,socket.io上会发出一个新连接。我希望当用户进入第一页时只连接一次并在其他... 查看详情

node.js 上的 Socket.io-client 只工作一次

】node.js上的Socket.io-client只工作一次【英文标题】:Socket.io-clientonnode.jsworksonlyonce【发布时间】:2012-10-1412:00:37【问题描述】:每当服务器收到请求时,我想在服务器端(使用node.js)创建新的sockei.io-client。但是下面的代码只工作... 查看详情

是否可以使用单向 socket.io 连接?

】是否可以使用单向socket.io连接?【英文标题】:Isitpossibletohaveaonewaysocket.ioconnection?【发布时间】:2015-06-0908:37:49【问题描述】:我的应用程序只需要socket.io将数据从服务器发送到客户端。为了防止拒绝服务攻击,如果我尝试发... 查看详情

Socket.io 只发送给当前用户

】Socket.io只发送给当前用户【英文标题】:Socket.ioonlyemittingtocurrentuser【发布时间】:2015-07-2023:48:39【问题描述】:我正在一个基本应用程序中测试Socket.io,但io.sockets.emit似乎没有按预期工作。下面是我相关的服务器端代码、Angul... 查看详情

我正在运行哪个版本的 Socket.IO?

】我正在运行哪个版本的Socket.IO?【英文标题】:WhichversionofSocket.IOamIrunning?【发布时间】:2019-11-2617:59:26【问题描述】:最近,我用npm安装了新版本的Socket.IO。我尝试使用npmupdatesocket.io,但这似乎对我不起作用,因为当我使用np... 查看详情

在控制器中使用 socket.io

】在控制器中使用socket.io【英文标题】:Usesocket.ioincontrollers【发布时间】:2013-11-0218:13:16【问题描述】:如果将新对象插入数据库,我只需要socket.io到emit到clients的消息。所以我的想法是直接从控制器的insert-method发出消息。在... 查看详情

Socket.io 事件多次发出

】Socket.io事件多次发出【英文标题】:Socket.ioeventemittingmultipletimes【发布时间】:2019-10-3015:52:05【问题描述】:我正在创建一个带有实时通知的聊天应用程序,通知会被发送,但它们会随机发送多次,而应该只发送一次。我在前... 查看详情

是否可以将 UDP 与 socket.io 一起使用?

】是否可以将UDP与socket.io一起使用?【英文标题】:IsitpossibletouseUDPwithsocket.io?【发布时间】:2016-04-0120:43:21【问题描述】:我正在开发一款游戏,我听说UDP更适合实时游戏。我知道socket.io使用TCP,并且想知道是否有某种方法可... 查看详情

socket.io简介

websocket是一种比较简单的协议,各种语言中都有很多实现版本,实际上它们差别不大,都是在websocket的基础上做些封装,随便选一个即可。socket.io就是众多websocket库中的一种,它并不像其它库那样简单地实现了一下websocket,而是... 查看详情

socket.io 怎么能像 axios 一样使用?

】socket.io怎么能像axios一样使用?【英文标题】:Howsocket.iocanbeusedlikeaxios?【发布时间】:2021-12-2304:51:30【问题描述】:我有一个应用程序,它内置在axios中,只考虑PUT、POST、DELETE、GET。看起来像这样getAPI=axios.create(.....)....getAPI.po... 查看详情

格式化消息以从 python 客户端发送到 socket.io node.js 服务器

...使用Socket.io0.7的Node.js服务器通信。基于我在GitHub和以下WebSock 查看详情

NodeJS - Socket.io 只允许经过 JWT 验证的连接

】NodeJS-Socket.io只允许经过JWT验证的连接【英文标题】:NodeJS-Socket.ioallowingonlyJWTverifiedconnections【发布时间】:2015-07-2005:23:04【问题描述】:我的代码就是这么简单:/*globalrequiremoduleprocessconsole*//*eslint-disable*/(function(require,process)\'u... 查看详情

使用 Socket.IO 实时显示点击计数器的应用

】使用Socket.IO实时显示点击计数器的应用【英文标题】:AppwiththecounterofclicksdisplayedinrealtimeusingSocket.IO【发布时间】:2018-03-1202:54:57【问题描述】:想象一下,我有一个只显示按钮的应用程序,并且用户可以点击这个按钮。在按... 查看详情

使用socket.io将消息发送到多个房间?(代码片段)

是否可以使用socket.io将消息发送到多个房间?发送到1个房间:io.sockets.in(room).emit("id",)发送到N个房间:io.sockets.in(room1,room2,roomN).emit("id",)答案sockets.in方法只接受一个房间作为参数,因此要广播到多个房间,你必须在两个房间之间... 查看详情

通过 Socket.io 更新 React 状态

】通过Socket.io更新React状态【英文标题】:UpdateReactstateviaSocket.io【发布时间】:2016-02-2916:17:00【问题描述】:我的React组件使用来自socket.io的数据作为它的状态。我不确定如何在更新数据时只更新状态而不重新渲染整个组件。示... 查看详情