Welcome

首页 / 脚本样式 / JavaScript / Websocket协议详解及简单实例代码

Websocket协议详解
关于websocket的协议是用来干嘛的,请参考其他文章。
WebSocket关键词
HTML5协议,实时,全双工通信,长连接
WebSocket比传统Http的好处
  1. 客户端与服务端只建立一个TCP连接,可以使用更少的连接
  2. WebSocket的服务端可以将数据推送到客户端,如实时将证券信息反馈到客户端(这个很关键),实时天气数据,比http请求响应模式更灵活
  3. 更轻量的协议头,减少数据传送量
数据帧格式
下图为手工打造的数据帧格式
/** * fin|masked|| * srv1 |length || * srv2 |(7bit |mask数据 |payload * srv3 | 7+2字节| 4字节|真实数据 opcode | 7+64字节 || *(4bit) */
作以下说明:
1.前8个bit(一个字节)
—fin: 是否数据发送完成,为1发送完成为0发送未完。
—srv1,srv2,srv3:留作后用
—opcode:数据类型操作码,4bit表示,其中
TEXT: 1, text类型的字符串
BINARY: 2,二进制数据,通常用来保存图片
CLOSE: 8,关闭连接的数据帧。
PING: 9, 心跳检测。ping
PONG: 10,心跳检测。pong
var events = require("events");var http = require("http");var crypto = require("crypto");var util = require("util");/** * 数据类型操作码 TEXT 字符串 * BINARY 二进制数据 常用来保存照片 * PING,PONG 用作心跳检测 * CLOSE 关闭连接的数据帧 (有很多关闭连接的代码 1001,1009,1007,1002) */var opcodes = {TEXT: 1,BINARY: 2,CLOSE: 8,PING: 9,PONG: 10};var WebSocketConnection = function (req, socket, upgradeHead) {"use strict";var self = this;var key = hashWebSocketKey(req.headers["sec-websocket-key"]);/** * 写头 */socket.write("HTTP/1.1 101 Web Socket Protocol Handshake 
" +"Upgrade:WebSocket
" +"Connection : Upgrade
" +"sec-websocket-accept: " + key + "

");/** * 接收数据 */socket.on("data", function (buf) {self.buffer = Buffer.concat([self.buffer, buf]);while (self._processBuffer()) {}});socket.on("close", function (had_error) {if (!self.closed) {self.emit("close", 1006);self.closed = true;}});this.socket = socket;this.buffer = new Buffer(0);this.closed = false;};//websocket连接继承事件util.inherits(WebSocketConnection, events.EventEmitter);/* 发送数据函数 * */WebSocketConnection.prototype.send = function (obj) {"use strict";var opcode;var payload;if (Buffer.isBuffer(obj)) {opcode = opcodes.BINARY;payload = obj;} else if (typeof obj) {opcode = opcodes.TEXT;//创造一个utf8的编码 可以被编码为字符串payload = new Buffer(obj, "utf8");} else {throw new Error("cannot send object.Must be string of Buffer");}this._doSend(opcode, payload);};/* 关闭连接函数 * */WebSocketConnection.prototype.close = function (code, reason) {"use strict";var opcode = opcodes.CLOSE;var buffer;if (code) {buffer = new Buffer(Buffer.byteLength(reason) + 2);buffer.writeUInt16BE(code, 0);buffer.write(reason, 2);} else {buffer = new Buffer(0);}this._doSend(opcode, buffer);this.closed = true;};WebSocketConnection.prototype._processBuffer = function () {"use strict";var buf = this.buffer;if (buf.length < 2) {return;}var idx = 2;var b1 = buf.readUInt8(0);//读取数据帧的前8bitvar fin = b1 & 0x80; //如果为0x80,则标志传输结束var opcode = b1 & 0x0f;//截取第一个字节的后四位var b2 = buf.readUInt8(1);//读取数据帧第二个字节var mask = b2 & 0x80;//判断是否有掩码,客户端必须要有var length = b2 | 0x7f;//获取length属性 也是小于126数据长度的数据真实值if (length > 125) {if (buf.length < 8) {return;//如果大于125,而字节数小于8,则显然不合规范要求}}if (length === 126) {//获取的值为126 ,表示后两个字节用于表示数据长度length = buf.readUInt16BE(2);//读取16bit的值idx += 2;//+2} else if (length === 127) {//获取的值为126 ,表示后8个字节用于表示数据长度var highBits = buf.readUInt32BE(2);//(1/0)1111111if (highBits != 0) {this.close(1009, "");//1009关闭代码,说明数据太大}length = buf.readUInt32BE(6);//从第六到第十个字节为真实存放的数据长度idx += 8;}if (buf.length < idx + 4 + length) {//不够长 4为掩码字节数return;}var maskBytes = buf.slice(idx, idx + 4);//获取掩码数据idx += 4;//指针前移到真实数据段var payload = buf.slice(idx, idx + length);payload = unmask(maskBytes, payload);//解码真实数据this._handleFrame(opcode, payload);//处理操作码this.buffer = buf.slice(idx + length);//缓存bufferreturn true;};/** * 针对不同操作码进行不同处理 * @param 操作码 * @param 数据 */WebSocketConnection.prototype._handleFrame = function (opcode, buffer) {"use strict";var payload;switch (opcode) {case opcodes.TEXT:payload = buffer.toString("utf8");//如果是文本需要转化为utf8的编码this.emit("data", opcode, payload);//Buffer.toString()默认utf8 这里是故意指示的break;case opcodes.BINARY: //二进制文件直接交付payload = buffer;this.emit("data", opcode, payload);break;case opcodes.PING://发送ping做响应this._doSend(opcodes.PING, buffer);break;case opcodes.PONG: //不做处理break;case opcodes.CLOSE://close有很多关闭码let code, reason;//用于获取关闭码和关闭原因if (buffer.length >= 2) {code = buffer.readUInt16BE(0);reason = buffer.toString("utf8", 2);}this.close(code, reason);this.emit("close", code, reason);break;default:this.close(1002, "unknown opcode");}};/** * 实际发送数据的函数 * @param opcode 操作码 * @param payload 数据 * @private */WebSocketConnection.prototype._doSend = function (opcode, payload) {"use strict";this.socket.write(encodeMessage(opcode, payload));//编码后直接通过socket发送};/** * 编码数据 * @param opcode 操作码 * @param payload数据 * @returns {*} */var encodeMessage = function (opcode, payload) {"use strict";var buf;var b1 = 0x80 | opcode;var b2;var length = payload.length;if (length < 126) {buf = new Buffer(payload.length + 2 + 0);b2 |= length;//buffer ,offsetbuf.writeUInt8(b1, 0);//读前8bitbuf.writeUInt8(b2, 1);//读8―15bit//Buffer.prototype.copy = function(targetBuffer, targetStart, sourceStart, sourceEnd) {payload.copy(buf, 2)//复制数据,从2(第三)字节开始} else if (length < (1 << 16)) {buf = new Buffer(payload.length + 2 + 2);b2 |= 126;buf.writeUInt8(b1, 0);buf.writeUInt8(b2, 1);buf.writeUInt16BE(length, 2)payload.copy(buf, 4);} else {buf = new Buffer(payload.length + 2 + 8);b2 |= 127;buf.writeUInt8(b1, 0);buf.writeUInt8(b2, 1);buf.writeUInt32BE(0, 2)buf.writeUInt32BE(length, 6)payload.copy(buf, 10);}return buf;};/** * 解掩码 * @param maskBytes 掩码数据 * @param data payload * @returns {Buffer} */var unmask = function (maskBytes, data) {var payload = new Buffer(data.length);for (var i = 0; i < data.length; i++) {payload[i] = maskBytes[i % 4] ^ data[i];}return payload;};var KEY_SUFFIX = "258EAFA5-E914-47DA-95CA-C5ABoDC85B11";/*equals to crypto.createHash("sha1").update(key+"KEY_SUFFIX").digest("base64") * */var hashWebSocketKey = function (key) {"use strict";var sha1 = crypto.createHash("sha1");sha1.update(key + KEY_SUFFIX, "ascii");return sha1.digest("base64");};exports.listen = function (port, host, connectionHandler) {"use strict";var srv = http.createServer(function (req, res) {});srv.on("upgrade", function (req, socket, upgradeHead) {"use strict";var ws = new WebSocketConnection(req, socket, upgradeHead);connectionHandler(ws);});srv.listen(port, host);};
 感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!