Websocket協議詳解
關于websocket的協議是用來干嘛的,請參考其他文章。
WebSocket關鍵詞
HTML5協議,實時,全雙工通信,長連接
WebSocket比傳統Http的好處
數據幀格式
下圖為手工打造的數據幀格式
/** * 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 /r/n' + "Upgrade:WebSocket/r/n" + "Connection : Upgrade/r/n" + "sec-websocket-accept: " + key + '/r/n/r/n'); /** * 接收數據 */ 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); //讀取數據幀的前8bit var 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)1111111 if (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);//緩存buffer return 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 ,offset buf.writeUInt8(b1, 0);//讀前8bit buf.writeUInt8(b2, 1);//讀8 主站蜘蛛池模板: 大足县| 北辰区| 肃南| 滁州市| 余江县| 武定县| 阳谷县| 沭阳县| 于田县| 高青县| 枣庄市| 浑源县| 新竹县| 苏尼特右旗| 平利县| 大同市| 枣强县| 呼图壁县| 西盟| 青州市| 合江县| 建德市| 玉林市| 特克斯县| 高陵县| 北安市| 东城区| 开原市| 宝应县| 安达市| 皮山县| 东莞市| 达州市| 潮安县| 莱阳市| 习水县| 黄骅市| 宁国市| 湖北省| 广宁县| 南江县|