一、WebSocket简介
WebSocket是一种基于TCP连接的全双工通信的协议,其工作在应用层,建立连接的时候通过复用http握手通道,完成http协议的切换升级,即切换到WebSocket协议,协议切换成功后,将不再需要客户端发起请求,服务端就可以直接向客户端发送数据,实现双向通信。
相对于Http协议而言,WebSocket有以下优点:
可以支持双向通信
WebSocket协议可以更好的支持二进制,可以直接传送二进制数据。
同时WebSocket协议的头部非常小,服务器发到客户端的数据包的包头,只有2~10个字节(取决于数据包的长度),客户端发送服务端的包头稍微大一点,因为其要进行掩码加密,所以还要加上4个字节的掩码。总得来说,头部不超过14个字节。
支持扩展,用户可以扩展协议实现自己的子协议。
二、实现一个WebSocket协议
① 设计WebSocket协议
我们使用WebSocket的时候,是通过new一个WebSocket对象,所以Websocket是一个类,同时在创建对象的时候,需要传递一些配置对象作为参数。由于WebSocket可以复用http握手通道,所以我们需要有一个web服务器,才能复用http的握手通道,所以如果我们已经创建好了一个web服务器,那么我们可以把这个web服务器传递给WebSocket,那么WebSocket就不需要再创建自己的web服务器了;当然我们也可以只传递一个端口号,由WebSocket自己创建一个web服务器。当客户端发来了消息,我们还需要能够通知到Websocket对象,所以我们还需要让WebSocket继承EventEmitter,如:
const http = require("http");
const { EventEmitter } = require('events')
class WebSocket extends EventEmitter {
constructor(options) {
super();
if (options.server) {
this.server = options.server; // 使用了传递的web server
} else {
this.server = http.createServer(); // 自己创建一个web server
this.server.listen(options.port || 3000);// 监听端口,默认3000
}
}
}
② 建立连接,切换升级到WebSocket协议
上一步,我们的WebSocket已经拥有web服务器了,接下来我们就是需要客户端通过http请求协议切换升级,当浏览器通过new WebSocket()创建WebSocket客户端的时候,浏览器会自动发起协议升级请求,如:
Connection: Upgrade 表示要升级协议
Upgrade: websocket 表示要升级到websocket协议
Sec-WebSocket-version 表示websocket的版本。
Sec-WebSocket-Key 浏览器生成的一个字符串,服务器端需要取出该字符串与258EAFA5-E914-47DA-95CA-C5AB0DC85B11拼接,然后通过SHA1算法计算出摘要,并转成base64字符串,然后作为Sec-WebSocket-Accept的值放到响应头部返回给客户端,否则会连接失败。
所以建立连接的关键就是要监听upgrade事件,然后根据Sec-WebSocket-Key生成对应Sec-WebSocket-Accept的key值,然后返回给客户端。
const crypto = require('crypto');
const MAGIC_STRING = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; // 固定的字符串
function hashWebSocketKey (key) {
var sha1 = crypto.createHash('sha1'); // 拿到sha1算法
sha1.update(key + MAGIC_STRING, 'ascii');
return sha1.digest('base64');
};
class WebSocket extends EventEmitter {
constructor(options) {
this.server.on("upgrade", (req, socket, upgradeHead) => {
this.socket = socket;
// 处理协议升级请求
var resKey = hashWebSocketKey(req.headers['sec-websocket-key']); // 对浏览器生成的key进行加密
// 构造响应头
var resHeaders = [
'HTTP/1.1 101 Switching Protocols',
'Upgrade: websocket',
'Connection: Upgrade',
'Sec-WebSocket-Accept: ' + resKey
]
.concat('', '')
.join('\r\n');
socket.write(resHeaders); // 返回响应头部
});
}
}
在upgrade事件回调中,我们可以拿到客户端的socket,然后通过客户端socket直接把响应头写入即可。
③ 接收客户端发过来的数据
经过上面,客户端已经可以和服务器端连接成功了,接下来客户端会发送数据到服务器端,服务器可以通过连接时的socket监听客户端发送过来的数据,同样是在监听到upgrade事件的时候,拿到客户端socket进行监听data事件即可,如:
class WebSocket extends EventEmitter {
constructor(options) {