理解网络编程-3

该文章阅读需要5分钟,更多文章请点击本人博客halu886

构建WebSocket服务

webSocket和node配合起来非常完美,因为

  • webSocket基于事件的编程模型和Node的自定义事件十分类似
  • webSocket中客户端和服务器端需要保持长连接,同时node基于事件驱动十分适合处理大量的高并发客户端连接

而且webSocket相较于http有以下三点优势

  1. 客户端与服务器端只存在一个tcp连接,
  2. 服务端可以推送数据至客户端,更加灵活
  3. 更加轻量的协议头

webSocket最早是作为HTML特性推出的,在W3C和ITEF的推动下,成为了一个RFC6455的一个规范,现在大部分浏览器都支持这个规范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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发送请求报文

1
2
3
4
5
6
7
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后返回给客户端。

返回报文如下

1
2
3
4
5
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事件开始封装的。

1
2
3
4
WebSocket.prototype.setSocket = function(socket){
this.socket = socket;
this.socket.on('data',this.reciever)
}

以及发送数据也是经过了一层封装

1
2
3
WebSocket.prototype.send = function(data){
this._send(data);
}

为了安全考虑,客户端发送数据帧至服务端时需要对数据进行掩码操作(防止中间层篡改),如果服务端接收到数据没有掩码加密,则关闭连接。但是客户端接收服务端的数据帧时,数据帧则不用进行掩码操作,如果接收到了数据进行掩码操作则断开连接。

以下我们以hello world作为实例,以WebSocket协议分析数据帧构成

1

  • 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>>,更多细节建议阅读书籍:)

谢谢老板,老板必发大财!💰💰💰💰💰💰