├── ReadMe.md ├── server ├── index.js └── ws.js └── index.html /ReadMe.md: -------------------------------------------------------------------------------- 1 | #### 使用流程 2 | 3 | ``` 4 | git clone https://github.com/JinJieTan/my-websocket.git 5 | 6 | cd my-websocket 7 | 8 | cd server 9 | 10 | node index.js 11 | 12 | 打开index.html访问即可 13 | 14 | ``` 15 | 16 | #### 欢迎Star,加入2000人的大前端交流群 17 | 18 | 加我的微信:CALASFxiaotan(备注加群) -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | const MyWebSocket = require('./ws'); 2 | const ws = new MyWebSocket({ port: 8080 }); 3 | 4 | ws.on('data', (data) => { 5 | console.log('receive data:' + data); 6 | ws.send('this message from server'); 7 | }); 8 | 9 | ws.on('close', (code, reason) => { 10 | console.log('close:', code, reason); 11 | }); 12 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 38 | 39 | 40 | 41 | 42 | 43 |
44 | 运行 WebSocket 45 |
46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /server/ws.js: -------------------------------------------------------------------------------- 1 | const { EventEmitter } = require('events'); 2 | const { createServer } = require('http'); 3 | const crypto = require('crypto'); 4 | const MAGIC_STRING = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; // 固定的字符串 5 | function hashWebSocketKey(key) { 6 | const sha1 = crypto.createHash('sha1'); // 拿到sha1算法 7 | sha1.update(key + MAGIC_STRING, 'ascii'); 8 | return sha1.digest('base64'); 9 | } 10 | function handleMask(maskBytes, data) { 11 | const payload = Buffer.alloc(data.length); 12 | for (let i = 0; i < data.length; i++) { 13 | // 遍历真实数据 14 | payload[i] = maskBytes[i % 4] ^ data[i]; // 掩码有4个字节依次与真实数据进行异或运算即可 15 | } 16 | return payload; 17 | } 18 | const OPCODES = { 19 | CONTINUE: 0, 20 | TEXT: 1, 21 | BINARY: 2, 22 | CLOSE: 8, 23 | PING: 9, 24 | PONG: 10, 25 | }; 26 | function encodeMessage(opcode, payload) { 27 | let buf; 28 | // 0x80 二进制为 10000000 | opcode 进行或运算就相当于是将首位置为1 29 | let b1 = 0x80 | opcode; // 如果没有数据了将FIN置为1 30 | let b2; // 存放数据长度 31 | let length = payload.length; 32 | console.log(`encodeMessage: length is ${length}`); 33 | if (length < 126) { 34 | buf = Buffer.alloc(payload.length + 2 + 0); // 服务器返回的数据不需要加密,直接加2个字节即可 35 | b2 = length; // MASK为0,直接赋值为length值即可 36 | buf.writeUInt8(b1, 0); //从第0个字节开始写入8位,即将b1写入到第一个字节中 37 | buf.writeUInt8(b2, 1); //读8―15bit,将字节长度写入到第二个字节中 38 | payload.copy(buf, 2); //复制数据,从2(第三)字节开始,将数据插入到第二个字节后面 39 | } 40 | return buf; 41 | } 42 | 43 | class MyWebsocket extends EventEmitter { 44 | constructor(options) { 45 | super(options); 46 | this.options = options; 47 | this.server = createServer(); 48 | options.port ? this.server.listen(options.port) : this.server.listen(8080); //默认端口8080 49 | this.server.on('upgrade', (req, socket, header) => { 50 | this.socket = socket; 51 | socket.setKeepAlive(true); 52 | console.log(req.headers['sec-websocket-key'], 'key'); 53 | const resKey = hashWebSocketKey(req.headers['sec-websocket-key']); // 对浏览器生成的key进行加密 54 | // 构造响应头 55 | const resHeaders = [ 56 | 'HTTP/1.1 101 Switching Protocols', 57 | 'Upgrade: websocket', 58 | 'Connection: Upgrade', 59 | 'Sec-WebSocket-Accept: ' + resKey, 60 | ] 61 | .concat('', '') 62 | .join('\r\n'); 63 | console.log(resHeaders, 'resHeaders'); 64 | socket.write(resHeaders); // 返回响应头部 65 | socket.on('data', (data) => { 66 | // 监听客户端发送过来的数据,该数据是一个Buffer类型的数据 67 | this.buffer = data; // 将客户端发送过来的帧数据保存到buffer变量中 68 | this.processBuffer(); // 处理Buffer数据 69 | }); 70 | socket.on('close', (error) => { 71 | // 监听客户端连接断开事件 72 | if (!this.closed) { 73 | this.emit('close', 1006, 'timeout'); 74 | this.closed = true; 75 | } 76 | }); 77 | }); 78 | } 79 | 80 | // 处理客户端发送过来的真实数据 81 | handleRealData(opcode, realDataBuffer) { 82 | switch (opcode) { 83 | case OPCODES.TEXT: 84 | this.emit('data', realDataBuffer.toString('utf8')); // 服务端WebSocket监听data事件即可拿到数据 85 | break; 86 | case OPCODES.BINARY: //二进制文件直接交付 87 | this.emit('data', realDataBuffer); 88 | break; 89 | default: 90 | this.close(1002, 'unhandle opcode:' + opcode); 91 | } 92 | } 93 | 94 | processBuffer() { 95 | let buf = this.buffer; 96 | let idx = 2; // 首先分析前两个字节 97 | // 处理第一个字节 98 | const byte1 = buf.readUInt8(0); // 读取buffer数据的前8 bit并转换为十进制整数 99 | // 获取第一个字节的最高位,看是0还是1 100 | const str1 = byte1.toString(2); // 将第一个字节转换为二进制的字符串形式 101 | const FIN = str1[0]; 102 | // 获取第一个字节的后四位,让第一个字节与00001111进行与运算,即可拿到后四位 103 | let opcode = byte1 & 0x0f; //截取第一个字节的后4位,即opcode码, 等价于 (byte1 & 15) 104 | // 处理第二个字节 105 | const byte2 = buf.readUInt8(1); // 从第一个字节开始读取8位,即读取数据帧第二个字节数据 106 | const str2 = byte2.toString(2); // 将第二个字节转换为二进制的字符串形式 107 | const MASK = str2[0]; // 获取第二个字节的第一位,判断是否有掩码,客户端必须要有 108 | let length = parseInt(str2.substring(1), 2); // 获取第二个字节除第一位掩码之后的字符串并转换为整数 109 | if (length === 126) { 110 | // 说明125<数据长度<65535(16个位能描述的最大值,也就是16个1的时候) 111 | length = buf.readUInt16BE(2); // 就用第三个字节及第四个字节表示数据的长度 112 | idx += 2; // 偏移两个字节 113 | } else if (length === 127) { 114 | // 说明数据长度已经大于65535,16个位也已经不足以描述数据长度了,就用第三到第十个字节这八个字节来描述数据长度 115 | const highBits = buf.readUInt32BE(2); // 从第二个字节开始读取32位,即4个字节,表示后8个字节(64位)用于表示数据长度,其中高4字节是0 116 | if (highBits != 0) { 117 | // 前四个字节必须为0,否则数据异常,需要关闭连接 118 | this.close(1009, ''); //1009 关闭代码,说明数据太大; 协议里是支持 63 位长度,不过这里我们自己实现的话,只支持 32 位长度,防止数据过大; 119 | } 120 | length = buf.readUInt32BE(6); // 获取八个字节中的后四个字节用于表示数据长度,即从第6到第10个字节,为真实存放的数据长度 121 | idx += 8; 122 | } 123 | let realData = null; // 保存真实数据对应字符串形式 124 | if (MASK) { 125 | // 如果存在MASK掩码,表示是客户端发送过来的数据,是加密过的数据,需要进行数据解码 126 | const maskDataBuffer = buf.slice(idx, idx + 4); //获取掩码数据, 其中前四个字节为掩码数据 127 | idx += 4; //指针前移到真实数据段 128 | const realDataBuffer = buf.slice(idx, idx + length); // 获取真实数据对应的Buffer 129 | realData = handleMask(maskDataBuffer, realDataBuffer); //解码真实数据 130 | console.log(`realData is ${realData}`); 131 | } 132 | let realDataBuffer = Buffer.from(realData); // 将真实数据转换为Buffer 133 | this.buffer = buf.slice(idx + length); // 清除已处理的buffer数据 134 | if (FIN) { 135 | // 如果第一个字节的第一位为1,表示是消息的最后一个分片,即全部消息结束了(发送的数据比较少,一次发送完成) 136 | this.handleRealData(opcode, realDataBuffer); // 处理操作码 137 | } 138 | } 139 | 140 | // 根据发送数据的类型设置上对应的操作码,将数据转换为Buffer形式 141 | send(data) { 142 | let opcode; 143 | let buffer; 144 | if (Buffer.isBuffer(data)) { 145 | // 如果是二进制数据 146 | opcode = OPCODES.BINARY; // 操作码设置为二进制类型 147 | buffer = data; 148 | } else if (typeof data === 'string') { 149 | // 如果是字符串 150 | opcode = OPCODES.TEXT; // 操作码设置为文本类型 151 | buffer = Buffer.from(data, 'utf8'); // 将字符串转换为Buffer数据 152 | } else { 153 | throw new Error('cannot send object.Must be string of Buffer'); 154 | } 155 | this.doSend(opcode, buffer); 156 | } 157 | 158 | // 开始发送数据 159 | doSend(opcode, buffer) { 160 | this.socket.write(encodeMessage(opcode, buffer)); //编码后直接通过socket发送 161 | } 162 | } 163 | 164 | module.exports = MyWebsocket; 165 | --------------------------------------------------------------------------------