Websocket 研究 / Nodejs 模块选型对比

导语 对Websocket的基础原理研究,并在nodejs的WebSocket库中进行选型对比,选出最适合我们的库。本文分为两章,第一张对WebSocket基础原理进行研究,第二章将从Nodejs库中选出最适合的WebSocket库。

第一章:Websocket研究

WebSocket连接本质上是TCP连接,在网页打开后通过http协议握手之后建立长连接。真正实现了Web的实时通信,使B/S模式具备了C/S模式的实时通信能力

WebSocket的生命周期

分为三个阶段:

第一阶段:由客户端发起的握手阶段,握手后建立连接 第二阶段:数据交换,客户端与服务端可以互相主动发送消息 第三阶段:关闭连接,可以由任意一端发起关闭的命令

WebSocket的握手协议

握手请求

GET http://localhost:8181/ HTTP/1.1
Host: localhost:8181
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: http://tomcltang.kf0309.3g.qq.com
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
Sec-WebSocket-Key: VCPIDS4ggndDGQmpLfzMLA==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
  • HTTP request method 必须是GET,协议应不小于1.1
  • Upgrade,并且其值为 websocket;
  • Connection,并且其值为Upgrade;
  • Sec-WebSocket-Key,其值采用base64编码的随机16字节长的字符序列;
  • Origin,服务器可以从Origin决定是否接受该WebSocket连接;
  • Sec-webSocket-Version,当前值必须是13;握手响应
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: UFCeZ5AiDPquwYZOEHuNHBnbZ94=
Sec-WebSocket-Extensions: permessage-deflate
  • 首行返回的是HTTP/1.1协议版本和状态码101,表示变换协议(Switching Protocol)
  • Upgrade,其值为 websocket;
  • Connection,其值为Upgrade;
  • Sec-WebSocket-Accept,加密处理后的握手Key消息体组成

WebSocket的消息并非没有额外信息,除了业务数据以外,消息体也包含一些额外信息。只不过相对http的头会小很多,一般只有6个bytes

FIN:1 bit 指示这个是消息的最后片段。第一个片段可能也是最后的片段。

RSV1, RSV2, RSV3: 每个1 bit 必须是0,除非一个扩展协商为非零值定义含义。如果收到一个非零值且没有协商的扩展定义这个非零值的含义,接收端点必须失败WebSokcket连接。

Opcode: 4 bits 定义了“负载数据”的解释。如果收到一个未知的操作码,接收端点必须失败WebSocket连接。定义了以下值。 %x0 代表一个继续帧 %x1 代表一个文本帧 %x2 代表一个二进制帧 %x3-7 保留用于未来的非控制帧 %x8 代表连接关闭 %x9 代表ping %xA 代表pong %xB-F 保留用于未来的控制帧

Mask: 1 bit 定义是否“负载数据”是掩码的。如果设置为1,一个掩码键出现在masking-key,且这个是用于根据5.3节解掩码(unmask)“负载数据”。从客户端发送到服务器的所有帧有这个位设置为1。

Payload length: 7 bits, 7+16 bits, 或者 7+64 bits “负载数据”的长度,以字节为单位:如果0-125,这是负载长度。如果126,之后的两字节解释为一个16位的无符号整数是负载长度。如果127,之后的8字节解释为一个64位的无符号整数(最高有效位必须是0)是负载长度。多字节长度数量以网络字节顺序来表示。注意,在所有情况下,最小数量的字节必须用于编码长度,例如,一个124字节长的字符串的长度不能被编码为序列126,0,124。负载长度是“扩展数据”长度+“应用数据”长度。“扩展数据”长度可能是零,在这种情况下,负载长度是“应用数据”长度。

Masking-key: 0 or 4 bytes 客户端发送到服务器的所有帧通过一个包含在帧中的32位值来掩码。如果mask位设置为1,则该字段存在,如果mask位设置为0,则该字段缺失。详细信息请参见5.3节 客户端到服务器掩码。

Payload data: (x+y) bytes “负载数据”定义为“扩展数据”连接“应用数据”。

Extension data: x bytes “扩展数据”是0字节除非已经协商了一个扩展。任何扩展必须指定“扩展数据”的长度,或长度是如何计算的,以及扩展如何使用必须在打开阶段握手期间协商。 如果存在,“扩展数据”包含在总负载长度中。

Application data: y bytes 任意的“应用数据”,占用“扩展数据”之后帧的剩余部分。“应用数据”的长度等于负载长度减去“扩展数据”长度。

FIN + RSV1 + RSV2 + RSV3 + Opcode + Mask + Payload length + Masking-key = 业务数据以外的消息大小 1bit + 1bit + 1bit + 1bit + 4bit + 1bit + 7bit + 4bytes = 6bytes

与http对比

以发送JSON字符串 {“req”:”123”} 为例,字符串本身13 bytes 通过http发送的话,http消息总大小 523+13 通过WebSocket发送的话,消息总大小是 6+13

第二章:Nodejs 的Websocket模块选型

由于工作原因,主要用Nodejs进行开发,因此只对比Nodejs实现的WebSocket库 GitHub上面,用nodejs实现的WebSocket库非常多,我挑选了几个靠前的库进行对比

本地Windows环境,对比Ajax与WebSocket发送消息的耗时。可以看到WebSocket的耗时远远低于Ajax

本地Windows环境 不同消息大小的耗时对比库对比

本地Windows环境,处理不同消息大小的耗时对比。 测试结果: websocket-node < faye < ws < socket.io

因为本地Windows环境与生产环境并不一样,因此上面的数据仅作Windows环境参考。因为下面在生产环境进行对比后,数据会有较大差异

以下生产环境测试,都是在2G内存、10个ecu环境下进行的测试对比

生产linux环境 不同消息大小的耗时对比库对比

这个测试与上一个Windows测试是一样的,但结果完全不同。ws表现最好 测试结果:ws< socket.io < websocket-node < faye < ajax

生产linux环境 测试内存波动

使用同样大小的消息,对服务发起大量的请求。测试服务的内存消耗。socket.io/ws/websocket-node 表现都不错,比较稳定。faye表现最差,占用内存高。 测试结果:socket.io < ws < websocket-node < faye

生产linux环境 测试CPU波动

使用同样大小的消息,对服务发起大量的请求。测试服务的CPU占用情况。socket.io表现最差,CPU占比很高。 测试结果:websocket-node = faye < ws < socket.io

生产linux环境 测试最大连接数

在2G内存的服务器上,测试各个库的最大连接数。最好的结果也是差异巨大。最好的ws是最差的socket.io的近三倍 测试结果:ws > websocket-node > faye > socket.io

websocket-node 在连接数超过140000的时候,连接速度比较慢。服务器没响应,但之前的连接不会断开 而faye和ws在到极限的时候,会出现异常。所有连接会断开 socket.io 连接在20000左右 的时候,就非常慢了

生产linux环境 测试最大连接数时的内存与CPU波动

测试最大连接数的时候,同时监控了内存和CPU的波动。

内存

在内存方面,ws的增长最为平缓,而socket.io早早的攀升到了极限最后挂掉了 测试结果:ws < websocket-node < faye < socket.io

CPU

在CPU方面,ws同样保持稳定,占用比也非常低。 测试结果:ws < websocket-node < faye < socket.io

总结

按第一得分4,第二得3分,第三得2分,第四得1分计算各个库的得分情况

得分

ws

21

websocket-node

17

faye

11

socket.io

11

ws表现最好简单易用,连接数最大,内存和CPU控制的稳定。缺点是在到达最大连接数极限之后,会断开所有连接

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

唐昌林的专栏

1 篇文章1 人订阅

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏技术记录

websocket(二) websocket的简单实现,识别用户属性的群聊

没什么好说的,websocket实现非常简单,我们直接看代码。 运行环境:jdk8 tomcat8 无须其他jar包。 具体环境支持自己百度 pac...

2919
来自专栏Python中文社区

Flask-SocketIO 文档译文

專 欄 ❈译者:詹聪聪 投稿 邮箱: zhancongc@gmail.com❈—— 序言: 本人工作中需要用到flask-socketio,在学习英文文档时...

9897
来自专栏大魏分享(微信公众号:david-share)

重点来了:事务一致性的深入研究&EJB的全生命周期 | 从开发角度看应用架构5

1154
来自专栏西安-晁州

GraphQL介绍&使用nestjs构建GraphQL查询服务

GraphQL介绍&使用nestjs构建GraphQL查询服务(文章底部附demo地址) GraphQL一种用为你 API 而生的查询语言。出自于Faceboo...

4099
来自专栏大魏分享(微信公众号:david-share)

非开发出身学JavaEE全集-中

当应用程序将数据存储在永久性存储中(例如flat file,XML文件或数据库的持久性数据)时,它被称为数据的持久性。 关系数据库是企业应用程序用来保存数据以供...

1023
来自专栏互联网杂技

js多线程编程

HTML5之Javascript多线程 Javascript执行机制 在HTML5之前,浏览器中JavaScript的运行都是以单线程的方式工作的,...

3779
来自专栏贾老师の博客

HTML5 之 WebSocket

1293
来自专栏Jerry的SAP技术分享

用JavaScript访问SAP云平台上的服务遇到跨域问题该怎么办

关于JavaScript的跨域问题(Cross Domain)的讨论, 网上有太多的资源了。国内的程序猿写了非常多的优秀文章,Jerry这里就不再重复了。

1794
来自专栏cmazxiaoma的架构师之路

Redis分布式锁解决方案

我们知道分布式锁的特性是排他、避免死锁、高可用。分布式锁的实现可以通过数据库的乐观锁(通过版本号)或者悲观锁(通过for update)、Redis的setnx...

1154
来自专栏非典型程序猿

你不知道的gRPC反向代理

可用性、可靠性和扩展性是衡量后台服务的基本标准,HTTP反向代理,是任何一个提供大型Web服务后台所必备的,用以提高服务的这些基础参数,且通过支持到负载均衡而进...

5648

扫码关注云+社区