前言:
经历了了各种懵逼,结合了各种资料,我必须要发这个了,再不发就憋死了有木有,Geemo狗的webscoket实现。点击=》参考资料
自己实在是菜的一B,表示并木优(并不是AV)重写,总算懵懵圈圈的看懂大概了,在这记录下。 Geemo狗的 https://github.com/geemo/test/blob/master/node/ws/index.js 大家可以去看看。我这里直接拷贝代码了。
昨天写的并木有发,卸载了守望先锋之后,突然懂了,然后自己撸了一遍。解析和编码过程都懂了,附加数据帧那里还恍恍惚惚。果断卸载游戏之后效率就是高啊。半天撸完。
拷贝的代码: 自己撸的:
'use strict'; const http = require('http'); const fs = require('fs'); const crypto = require('crypto'); const PORT = process.env.PORT || 80; server.on('upgrade', function (req, socket, upgradeHead) { var key = req.headers['sec-websocket-key']; key = crypto.createHash("sha1").update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest("base64"); var headers = [ 'HTTP/1.1 101 Switching Protocols', 'Upgrade: websocket', 'Connection: Upgrade', 'Sec-WebSocket-Accept: ' + key ]; socket.setNoDelay(true); socket.write(headers.join("\r\n") + "\r\n\r\n", 'ascii'); eventMount(socket); handle(socket); }); server.listen(PORT,()=>{console.log(`服务启动 端口 ${PORT}`)}); function eventMount(socket) { socket.on('message', msg => { console.log(msg); socket.write(encodeFrame({isFinal: false, opcode: 1, payloadData: 'bbb'})); socket.write(encodeFrame({isFinal: false, opcode: 0, payloadData: 'ccc'})); socket.write(encodeFrame({isFinal: true, opcode: 0, payloadData: 'ddd'})); }); socket.on('close', () => { }); } function handle(socket) { let frame, frameArr = []; socket.on('data', rawFrame => { frame = decodeWsFrame(rawFrame); if(frame.isFinal) { if(frame.opcode === 0) { payloadDataArr = []; payloadDataArr = frameArr.map(frame => frame.payloadData); frame.payloadData = Buffer.concat(payloadDataArr); opHandle(socket, frame); } else { opHandle(socket, frame); } } else { frameArr.push(frame); } }); } function opHandle(socket,frame){ switch(frame.opcode){ case 1: socket.emit('message',{type: 'text',data:frame.payloadData.toString('utf8')}); break; case 2: socket.emit('message',{type: 'binary',data:frame.payloadData}); break; case 8: socket.emit('close'); socket.end(); break; case 9: socket.emit('ping'); socket.write(encodeWsFrame({opcode: 10})); break; case 10: socket.emit('pong'); break; default: socket.emit('close'); socket.end(); } } function decodeFrame(aframe) { let start=0; let frame= { isFinal:(aframe[start] & 0x80)===0x80,//高位1 10000000 opcode:(aframe[start++] & 0xF), //取后四位 00001111 masked:(aframe[start] & 0x80)===0x80, payloadLength:(aframe[start++] & 0x7F), //取后7 0111111 payloadData:null, MaskingKey:'' } if(frame.payloadLength === 126) { frame.payloadLength=(aframe[start++] << 8)+aframe[start++]; } if(frame.payloadLength === 127) { //长度一般用4字节整形前4字节留空 取后4字节 start+=4; frame.payloadLength=(aframe[start++] << 24) +(aframe[start << 16])+(aframe[start++] << 8)+aframe[start++]; } if(frame.masked===1) { //获得掩码 frame.MaskingKey=[arame[start++],aframe[start++],aframe[start++],aframe[start++]]; //对数据和掩码进行抑或运算 frame.payloadData=aframe.slice(start, start + frame.payloadLen) .map((byte, index) => byte ^ maskingKey[index %4]); }else{ //直接接数据 frame.payloadData=data.slice(start, start + frame.payloadLen); } return frame; } function encodeFrame() { const isFinal = data.isFinal != undefined ? data.isFinal : true, opcode = data.opcode !== undefined ? data.opcode : 1, payloadData = data.payloadData ? new Buffer(data.payloadData) : null, payloadLen = payloadData ? payloadData.length : 0; let frame = []; if(isFinal) { frame.push((1 << 7) + opcode); //FIN Opcode }else{ frame.push(opcode); } if(payloadLen < 126) { frame.push(payloadLen) } else if(payloadLen < 65536){ //2字节无用 frame.push(126, payloadLen >> 8, payloadLen & 0xFF); } else { //获取长度 frame.push(127, 0, 0, 0, 0, (payloadLen & 0xFF000000) >> 24, (payloadLen & 0xFF0000) >> 16, (payloadLen & 0xFF00) >> 8, payloadLen & 0xFF); } frame = payloadData ? Buffer.concat([new Buffer(frame), payloadData]) : new Buffer(frame); //打出来看看 console.log(decodeWsFrame(frame)); return frame; }
概述:
WebScoket协议中,数据以帧序列的形式传输。
考虑到数据安全性,客户端向服务器传输的数据帧必须进行掩码处理。服务器若接收到未经过掩码处理的数据帧,则必须主动关闭连接。
服务器向客户端传输的数据帧一定不能进行掩码处理。客户端若接收到经过掩码处理的数据帧,则必须主动关闭连接。
针对上情况,发现错误的一方可向对方发送close帧(状态码是1002,表示协议错误),用来关闭连接。
懒得码字,可以结合下参考资料里的链接理解,次碳酸钴博客里有详细的解析包括Opcode等… 下面是拷贝的内容,反正我是看着撸出来了。下面只是大概其他用的到的东东可以看链接了。
数据帧结构图如下:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
OPCODE:4位
解释PayloadData,如果接收到未知的opcode,接收端必须关闭连接。
0x0表示附加数据帧
0x1表示文本数据帧
0x2表示二进制数据帧
0x3-7暂时无定义,为以后的非控制帧保留
0x8表示连接关闭
0x9表示ping
0xA表示pong
0xB-F暂时无定义,为以后的控制帧保留
MASK:1位
用于标识PayloadData是否经过掩码处理。如果是1,Masking-key域的数据即是掩码密钥,用于解码PayloadData。客户端发出的数据帧需要进行掩码处理,所以此位是1。
Payload length:7位,7+16位,7+64位
PayloadData的长度(以字节为单位)。
如果其值在0-125,则是payload的真实长度。
如果值是126,则后面2个字节形成的16位无符号整型数的值是payload的真实长度。注意,网络字节序,需要转换。
如果值是127,则后面8个字节形成的64位无符号整型数的值是payload的真实长度。注意,网络字节序,需要转换。
收获:
理解并照图自撸了ws协议,位运算,各种参考资料, 卸载了守望先锋(PS:重要),撸协议要看文档(重要)。感悟:不要把东西想的太难,除了吃喝之外精神力还用充实一些,前一段时间很荒废,希望我弟6次戒毒能成功。