该文章阅读需要5分钟,更多文章请点击本人博客halu886
构建WebSocket服务
webSocket和node配合起来非常完美,因为
- webSocket基于事件的编程模型和Node的自定义事件十分类似
- webSocket中客户端和服务器端需要保持长连接,同时node基于事件驱动十分适合处理大量的高并发客户端连接
而且webSocket相较于http有以下三点优势
- 客户端与服务器端只存在一个tcp连接,
- 服务端可以推送数据至客户端,更加灵活
- 更加轻量的协议头
webSocket最早是作为HTML特性推出的,在W3C和ITEF的推动下,成为了一个RFC6455的一个规范,现在大部分浏览器都支持这个规范
const webSocket = new WebSocket({uri:"ws://127.0.0.1:12353/updates"});
webSocket.open(()=>{
setInterval(()=>{
if(webSocket.bufferAmount==0){
webSocket.send("hello world")
}
},50)
})
webSocket.onmessage=(event)=>{
/**
* event data
* /
})
上述代码的含义是当WebSocket确认链接后,每隔50毫秒发送一个hello world至客户端。同时也能接收到客户端的消息。这个行为类似于TCP通信,同时又能和客户端双向通信,所以说应用空间非常大。
在这之前客户端和服务器通信效率最高则是Comet和iframe流。Comet是基于长轮询(long-polling)实现的,客户端向服务器发送一个连接,当服务器返回数据(res.end())或者超时断开连接,客户端里面向服务端重新发送一个请求,由于这样每个请求后面都会拖着长长的尾巴,所以叫做Comet(慧星)连接。
相较于HTTP,WebSocket基于TCP协议重新定义了服务端推送数据的协议,但是三次握手是由HTTP完成的。
webSocket握手
WebSocket发起连接时,通过HTTP发送请求报文
GET \chat HTTP/1.1
host:server.example.com
Upgrate:websocket
Connect:Upgrade
Sec-WebSocket-key:asdfqwer321asd==
Sec-WebSocket-Protocol:chat,superchat
Socket-version:13
与普通HTTP连接的区别在于upgrate和connect字段,表示将请求升级为WebSocket。
Sec-WebSocket-key表示用于安全性校验
客户端将随机生成的字符串asdfqwer321asd==基于Base64编码后发送给服务端,服务端再通过将随机字符串与服务端密钥进行拼接后进行SHA1随机数加密编码为Base64后返回给客户端。
返回报文如下
http/1.1 101 Swiching Protocols
Upgrate:websocket
Connect:Upgrade
Sec-WebSocket-Accept:asdfasdf1234==
Sec-WebSocket-Protocol:chat,superchat
这个报文告诉客户端正在更新协议,将应用层协议更新为WebSocket,同时将套接字连接升级为WebSocket连接。并且客户端会校验服务端基于Sec-WebSocket-Key生成的密钥,如果成功则开始传输数据。
webSocket数据传输
当完成升级协议后,则不再进行http的数据交互,而采用WebSocket的协议传输。
为了完成TCP套接字到WebSocket的事件封装,需要从接收数据时开始进行封装。WebSocket的数据帧协议是在底层data事件开始封装的。
WebSocket.prototype.setSocket = function(socket){
this.socket = socket;
this.socket.on('data',this.reciever)
}
以及发送数据也是经过了一层封装
WebSocket.prototype.send = function(data){
this._send(data);
}
为了安全考虑,客户端发送数据帧至服务端时需要对数据进行掩码操作(防止中间层篡改),如果服务端接收到数据没有掩码加密,则关闭连接。但是客户端接收服务端的数据帧时,数据帧则不用进行掩码操作,如果接收到了数据进行掩码操作则断开连接。
以下我们以hello world作为实例,以WebSocket协议分析数据帧构成
- fin:只占一个字节,1表示最后一个数据帧,0则相反
- rv1,rv2,rv3:表示拓展,当都为0时,则是默认格式,不加拓展
- opcode:1~15位,1表示二进制格式,2表示文本格式,8表示ping,9表示pong。当一端发送ping时,另一端则需要发送pong响应,表示存货,其他则都是未被定义的数据格式。
- mask:1表示进行掩码,0表示不进行掩码。
- payload length:长度有可能为7,7+16,7+64为,前七个字节的表示的最大数值只占0~125,如果为126时表示paloadlength7+16。如果为127时,表示长度为7+64。
- masking key:当mask为1时存在,4个字节,用于解密数据。
- payload length:表示携带内容,8的倍数
hello world!一个12个字节一个数据帧完全够了,长度为(12*8)96个位。payload length表示为1100000,所以数据帧如下。
fin(1) + rv1,rv2,rv3(000) + opcode(0001) + mask(1) + payload length(110000) + masking key(32位) + payload length(hello world!加密后的二进制)
以文本格式发送时,编码格式UTF-8,由于这里不存在中文字符。所以一个字符占一个字节。
服务端接收编码数据后,触发data事件, 然后将数据解码位数据帧格式,同时将数据解密出来再执行onMessage事件。
服务端回复yakexi时,就不需要掩码操作了,格式如下
fin(1) + rv1,rv2,rv3(000) + opcode(0001) + mask(0) + payload length(011000) + payload length(yakexi加密后的二进制)
这里的行为类似于TCP套接字的Connect和data事件。
以上知识点均来自<<深入浅出Node.js>>,更多细节建议阅读书籍:-)