node的websocket实现

Nodejs cyanprobe 8年前 (2016-10-14) 4233次浏览 已收录 1个评论

前言:

经历了了各种懵逼,结合了各种资料,我必须要发这个了,再不发就憋死了有木有,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次戒毒能成功。
 
 


CyanProbe , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:node的websocket实现
喜欢 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
(1)个小伙伴在吐槽
  1. 别骗自己,兄dei
    sway2017-12-17 00:39 回复