浅析nodejs实现Websocket的数据接收与发送(2)

帧定义解释完了,就可以根据数据来进行解析了,当有data过来的时候,先获取需要的数据信息,下面这段代码将获取到数据在data里的位置,以及数据长度,masking key以及opcode:

WebSocket.prototype.handleDataStat = function (data) { if (!this.stat) { var dataIndex = 2; //数据索引,因为第一个字节和第二个字节肯定不为数据,所以初始值为2 var secondByte = data[1]; //代表masked位和可能是payloadLength位的第二个字节 var hasMask = secondByte >= 128; //如果大于或等于128,说明masked位为1 secondByte -= hasMask ? 128 : 0; //如果有掩码,需要将掩码那一位去掉 var dataLength, maskedData; //如果为126,则后面16位长的数据为数据长度,如果为127,则后面64位长的数据为数据长度 if (secondByte == 126) { dataIndex += 2; dataLength = data.readUInt16BE(2); } else if (secondByte == 127) { dataIndex += 8; dataLength = data.readUInt32BE(2) + data.readUInt32BE(6); } else { dataLength = secondByte; } //如果有掩码,则获取32位的二进制masking key,同时更新index if (hasMask) { maskedData = data.slice(dataIndex, dataIndex + 4); dataIndex += 4; } //数据量最大为10kb if (dataLength > 10240) { this.send("Warning : data limit 10kb"); } else { //计算到此处时,dataIndex为数据位的起始位置,dataLength为数据长度,maskedData为二进制的解密数据 this.stat = { index: dataIndex, totalLength: dataLength, length: dataLength, maskedData: maskedData, opcode: parseInt(data[0].toString(16).split("")[1] , 16) //获取第一个字节的opcode位 }; } } else { this.stat.index = 0; } };

代码中均有注释,理解起来应该不难,直接看下一步,获取到数据信息后,就要对数据进行实际解析了:

经过上面handleDataStat方法的处理,stat中已经有了data的相关数据,先判断opcode,如果为9说明是客户端发起的ping心跳检测,直接返回pong响应,如果为10则为服务端发起的心跳检测。如果有masking key,则遍历数据段,对每个字节都与masking key的字节进行异或运算(网上看到一个说法很形象:就是轮流发生X关系),^符号就是进行异或运算啦。如果没有masking key则直接通过slice方法把数据截取下来。

获取到数据后,放进datas里保存,因为有可能数据被分片了,所以再将stat里的长度减去当前数据长度,只有当stat里的长度为0的时候,说明当前帧为最后一帧,然后通过Buffer.concat将所有数据合并,此时再判断一下opcode,如果opcode为8,则说明客户端发起了一个关闭请求,而我们获取到的数据则是关闭原因。如果不为8,则这数据就是我们需要的数据。然后再将stat重置为null,datas数组置空即可。至此,我们的数据解析就完成了。

WebSocket.prototype.dataHandle = function (data) { this.handleDataStat(data); var stat; if (!(stat = this.stat)) return; //如果opcode为9,则发送pong响应,如果opcode为10则置pingtimes为0 if (stat.opcode === 9 || stat.opcode === 10) { (stat.opcode === 9) ? (this.sendPong()) : (this.pingTimes = 0); this.reset(); return; } var result; if (stat.maskedData) { result = new Buffer(data.length-stat.index); for (var i = stat.index, j = 0; i < data.length; i++, j++) { //对每个字节进行异或运算,masked是4个字节,所以%4,借此循环 result[j] = data[i] ^ stat.maskedData[j % 4]; } } else { result = data.slice(stat.index, data.length); } this.datas.push(result); stat.length -= (data.length - stat.index); //当长度为0,说明当前帧为最后帧 if (stat.length == 0) { var buf = Buffer.concat(this.datas, stat.totalLength); if (stat.opcode == 8) { this.close(buf.toString()); } else { this.emit("message", buf.toString()); } this.reset(); } };

完成了客户端发来的数据解析,还需要一个服务端发数据至客户端的方法,也就是按照上面所说的帧定义来组装数据并且发送出去。下面的代码中基本上每一行都有注释,应该还是比较容易理解的。

//数据发送 WebSocket.prototype.send = function (message) { if(this.state !== "OPEN") return; message = String(message); var length = Buffer.byteLength(message); // 数据的起始位置,如果数据长度16位也无法描述,则用64位,即8字节,如果16位能描述则用2字节,否则用第二个字节描述 var index = 2 + (length > 65535 ? 8 : (length > 125 ? 2 : 0)); // 定义buffer,长度为描述字节长度 + message长度 var buffer = new Buffer(index + length); // 第一个字节,fin位为1,opcode为1 buffer[0] = 129; // 因为是由服务端发至客户端,所以无需masked掩码 if (length > 65535) { buffer[1] = 127; // 长度超过65535的则由8个字节表示,因为4个字节能表达的长度为4294967295,已经完全够用,因此直接将前面4个字节置0 buffer.writeUInt32BE(0, 2); buffer.writeUInt32BE(length, 6); } else if (length > 125) { buffer[1] = 126; // 长度超过125的话就由2个字节表示 buffer.writeUInt16BE(length, 2); } else { buffer[1] = length; } // 写入正文 buffer.write(message, index); this.socket.write(buffer); };

最后还要实现一个功能,就是心跳检测:防止服务端长时间不与客户端交互而导致客户端关闭连接,所以每隔十秒都会发送一次ping进行心跳检测

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wgzjjx.html