导语 对Websocket的基础原理研究,并在nodejs的WebSocket库中进行选型对比,选出最适合我们的库。本文分为两章,第一张对WebSocket基础原理进行研究,第二章将从Nodejs库中选出最适合的WebSocket库。
WebSocket连接本质上是TCP连接,在网页打开后通过http协议握手之后建立长连接。真正实现了Web的实时通信,使B/S模式具备了C/S模式的实时通信能力
分为三个阶段:
第一阶段:由客户端发起的握手阶段,握手后建立连接 第二阶段:数据交换,客户端与服务端可以互相主动发送消息 第三阶段:关闭连接,可以由任意一端发起关闭的命令
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/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: UFCeZ5AiDPquwYZOEHuNHBnbZ94=
Sec-WebSocket-Extensions: permessage-deflate
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
以发送JSON字符串 {“req”:”123”} 为例,字符串本身13 bytes 通过http发送的话,http消息总大小 523+13 通过WebSocket发送的话,消息总大小是 6+13
由于工作原因,主要用Nodejs进行开发,因此只对比Nodejs实现的WebSocket库 GitHub上面,用nodejs实现的WebSocket库非常多,我挑选了几个靠前的库进行对比
在本地Windows环境,对比Ajax与WebSocket发送消息的耗时。可以看到WebSocket的耗时远远低于Ajax
在本地Windows环境,处理不同消息大小的耗时对比。 测试结果: websocket-node < faye < ws < socket.io
因为本地Windows环境与生产环境并不一样,因此上面的数据仅作Windows环境参考。因为下面在生产环境进行对比后,数据会有较大差异
以下生产环境测试,都是在2G内存、10个ecu环境下进行的测试对比
这个测试与上一个Windows测试是一样的,但结果完全不同。ws表现最好 测试结果:ws< socket.io < websocket-node < faye < ajax
使用同样大小的消息,对服务发起大量的请求。测试服务的内存消耗。socket.io/ws/websocket-node 表现都不错,比较稳定。faye表现最差,占用内存高。 测试结果:socket.io < ws < websocket-node < faye
使用同样大小的消息,对服务发起大量的请求。测试服务的CPU占用情况。socket.io表现最差,CPU占比很高。 测试结果:websocket-node = faye < ws < socket.io
在2G内存的服务器上,测试各个库的最大连接数。最好的结果也是差异巨大。最好的ws是最差的socket.io的近三倍 测试结果:ws > websocket-node > faye > socket.io
websocket-node 在连接数超过140000的时候,连接速度比较慢。服务器没响应,但之前的连接不会断开 而faye和ws在到极限的时候,会出现异常。所有连接会断开 socket.io 连接在20000左右 的时候,就非常慢了
测试最大连接数的时候,同时监控了内存和CPU的波动。
在内存方面,ws的增长最为平缓,而socket.io早早的攀升到了极限最后挂掉了 测试结果:ws < websocket-node < faye < socket.io
在CPU方面,ws同样保持稳定,占用比也非常低。 测试结果:ws < websocket-node < faye < socket.io
按第一得分4,第二得3分,第三得2分,第四得1分计算各个库的得分情况
库 | 得分 |
---|---|
ws | 21 |
websocket-node | 17 |
faye | 11 |
socket.io | 11 |
ws表现最好简单易用,连接数最大,内存和CPU控制的稳定。缺点是在到达最大连接数极限之后,会断开所有连接
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。