A, 预备工作
1, 序
最近正在研究 websocket 相关的知识, 想着如何能自己实现 Websocket 协议. 到网上搜罗了一番资料后用 Node.JS 实现该协议, 倒也没有想象中那么复杂, 除去注释语句和 console 语句后, 大约 200 行代码左右. 本文记录了实现过程中的经验和总结.
如果你想要写一个 WebSocket 服务器, 首先需要读懂对应的网络协议 RFC6455 https://tools.ietf.org/html/rfc6455 , 不过这对于一般人来说有些 "晦涩", 英文且不说, 还得咬文嚼字理解 网络编程 含义.
好在 WebSocket 技术出现比较早, 所以可以搜到 RFC6455 中文版, 网上也有很多针对该协议的剖析文章, 很多文章里还有现成的实现代码可以参考, 所以说实现一个简单的 Websocket 服务并非难事.
本文更偏向实战(in action), 会从知识储备, 具体代码分析以及注意事项角度去讲解如何用 Node.JS 实现一个简单的 Websocket 服务, 至于 Websocket 概念, 定义, 解释和用途等基础知识不会涉及, 因为这些知识在本文所列的参考文章中轻松找到.(也可以自行网上随便一搜, 就能找到很多)
2, 知识储备
如果要自己写一个 Websocket 服务, 主要有两个难点:
熟练掌握 Websocket 的协议, 这个需要多读现有的解读类文章;(下面会给出参考文章)
操作二进制数据流, 在 Node.JS 中需要对 Buffer http://Node.JS.cn/API/buffer.HTML 这个类稍微熟悉些.
同时还需要具备两个基础知识点:
- var ws = new WebSocket("ws://127.0.0.1:3000");
- ws.onmessage = function(evt) {
- console.log( "Received Message:" + evt.data);
- };
- // HTTP 服务器部分
- var server = http.createServer(function(req, res) {
- res.end('websocket test\r\n');
- });
- // Upgrade 请求处理
- server.on('upgrade', function(req, socket, upgradeHead){
- // 初始化 ws
- var ws = new WebSocket(req, socket, upgradeHead);
- // ... ws 监听 data,error 的逻辑等
- });
- class WebSocket extends EventEmitter {
- constructor(req, socket, upgradeHead){
- super(); // 调用 EventEmitter 构造函数
- // 1. 构造响应头 resHeaders 部分
- // 2. 监听 socket 的 data 事件, 以及 error 事件
- // 3. 初始化成员属性
- }
- }
- var resKey = hashWebSocketKey(req.headers['sec-websocket-key']);
- // 构造响应头
- var resHeaders = [
- 'HTTP/1.1 101 Switching Protocols',
- 'Upgrade: websocket',
- 'Connection: Upgrade',
- 'Sec-WebSocket-Accept:' + resKey
- ]
- .concat('','')
- .join('\r\n');
- socket.write(resHeaders);
- socket.on('data', data => {
- this.buffer = Buffer.concat([this.buffer, data]);
- while (this._processBuffer()) {} // 循环处理返回的 data 数据
- });
- socket.on('close', had_error => {
- if (!this.closed) {
- this.emit('close', 1006);
- this.closed = true;
- }
- });
- _handleFrame(opcode, buffer) {
- var payload;
- switch (opcode) {
- case OPCODES.TEXT:
- payload = buffer.toString('utf8'); // 如果是文本需要转化为 utf8 的编码
- this.emit('data', opcode, payload); //Buffer.toString()默认 utf8 这里是故意指示的
- break;
- case OPCODES.BINARY: // 二进制文件直接交付
- payload = buffer;
- this.emit('data', opcode, payload);
- break;
- case OPCODES.PING: // 发送 pong 做响应
- this._doSend(OPCODES.PONG, buffer);
- break;
- case OPCODES.PONG: // 不做处理
- console.log('server receive pong');
- break;
- case OPCODES.CLOSE: // close 有很多关闭码
- let code, reason; // 用于获取关闭码和关闭原因
- if (buffer.length>= 2) {
- code = buffer.readUInt16BE(0);
- reason = buffer.toString('utf8', 2);
- }
- this.close(code, reason);
- this.emit('close', code, reason);
- break;
- default:
- this.close(1002, 'unhandle opcode:' + opcode);
- }
- }
- var FIN = byte1 & 0x80; // 如果为 0x80, 则标志传输结束, 获取高位 bit
- // 如果是 0 的话, 说明是延续帧, 需要保存好 opCode
- if (!FIN) {
- this.frameOpcode = opcode || this.frameOpcode; // 确保不为 0;
- }
- //....
- // 有可能是分帧, 需要拼接数据
- this.frames = Buffer.concat([this.frames, payload]); // 保存到 frames 中
- if (FIN) {
- payload = this.frames.slice(0); // 获取所有拼接完整的数据
- opcode = opcode || this.frameOpcode; // 如果是 0 , 则保持获取之前保存的 code
- this.frames = Buffer.alloc(0); // 清空 frames
- this.frameOpcode = 0; // 清空 opcode
- this._handleFrame(opcode, payload); // 处理操作码
- }
- _doSend(opcode, payload) {
- // 1. 考虑数据分片
- this.socket.write(
- encodeMessage(count> 0 ? OPCODES.CONTINUE : opcode, payload)
- ); // 编码后直接通过 socket 发送
- // ...
- var len = Buffer.byteLength(payload);
- // 分片的距离逻辑
- var count = 0;
- // 这里可以针对 payload 的长度做分片
- while (len> MAX_FRAME_SIZE) {
- var framePayload = payload.slice(0, MAX_FRAME_SIZE);
- payload = payload.slice(MAX_FRAME_SIZE);
- this.socket.write(
- encodeMessage(
- count> 0 ? OPCODES.CONTINUE : opcode,
- framePayload,
- false
- )
- ); // 编码后直接通过 socket 发送
- count++;
- len = Buffer.byteLength(payload);
- }
- // ...
- var ws = new WebSocket("ws://127.0.0.1:3000");
- ws.onmessage = function(evt) {
- console.log( "Received Message:" + evt.data);
- };
- var myArray = new ArrayBuffer(131072 * 2 + 1);
- ws.send(myArray);
- server detect fragment, sizeof payload: 131072
- server detect fragment, sizeof payload: 131072
- receive data: 2 262145
- Firefox(62.0,64bit)
- Safari (11.1.2 - 13605.3.8)
- IE 11
来源: https://segmentfault.com/a/1190000016467409