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