Websocket協(xié)議詳解及簡(jiǎn)單實(shí)例代碼
Websocket協(xié)議詳解
關(guān)于websocket的協(xié)議是用來干嘛的,請(qǐng)參考其他文章。
WebSocket關(guān)鍵詞
HTML5協(xié)議,實(shí)時(shí),全雙工通信,長(zhǎng)連接
WebSocket比傳統(tǒng)Http的好處
- 客戶端與服務(wù)端只建立一個(gè)TCP連接,可以使用更少的連接
- WebSocket的服務(wù)端可以將數(shù)據(jù)推送到客戶端,如實(shí)時(shí)將證券信息反饋到客戶端(這個(gè)很關(guān)鍵),實(shí)時(shí)天氣數(shù)據(jù),比http請(qǐng)求響應(yīng)模式更靈活
- 更輕量的協(xié)議頭,減少數(shù)據(jù)傳送量
數(shù)據(jù)幀格式
下圖為手工打造的數(shù)據(jù)幀格式
/** * fin |masked | | * srv1 | length | | * srv2 | (7bit |mask數(shù)據(jù) |payload * srv3 | 7+2字節(jié) | 4字節(jié) |真實(shí)數(shù)據(jù) opcode | 7+64字節(jié) | | *(4bit) */
作以下說明:
1.前8個(gè)bit(一個(gè)字節(jié))
—fin: 是否數(shù)據(jù)發(fā)送完成,為1發(fā)送完成為0發(fā)送未完。
—srv1,srv2,srv3:留作后用
—opcode:數(shù)據(jù)類型操作碼,4bit表示,其中
TEXT: 1, text類型的字符串
BINARY: 2,二進(jìn)制數(shù)據(jù),通常用來保存圖片
CLOSE: 8,關(guān)閉連接的數(shù)據(jù)幀。
PING: 9, 心跳檢測(cè)。ping
PONG: 10,心跳檢測(cè)。pong
var events = require('events'); var http = require('http'); var crypto = require('crypto'); var util = require('util'); /** * 數(shù)據(jù)類型操作碼 TEXT 字符串 * BINARY 二進(jìn)制數(shù)據(jù) 常用來保存照片 * PING,PONG 用作心跳檢測(cè) * CLOSE 關(guān)閉連接的數(shù)據(jù)幀 (有很多關(guān)閉連接的代碼 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'); /** * 接收數(shù)據(jù) */ 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); /* 發(fā)送數(shù)據(jù)函數(shù) * */ 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; //創(chuàng)造一個(gè)utf8的編碼 可以被編碼為字符串 payload = new Buffer(obj, 'utf8'); } else { throw new Error('cannot send object.Must be string of Buffer'); } this._doSend(opcode, payload); }; /* 關(guān)閉連接函數(shù) * */ 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); //讀取數(shù)據(jù)幀的前8bit var fin = b1 & 0x80; //如果為0x80,則標(biāo)志傳輸結(jié)束 var opcode = b1 & 0x0f;//截取第一個(gè)字節(jié)的后四位 var b2 = buf.readUInt8(1);//讀取數(shù)據(jù)幀第二個(gè)字節(jié) var mask = b2 & 0x80;//判斷是否有掩碼,客戶端必須要有 var length = b2 | 0x7f;//獲取length屬性 也是小于126數(shù)據(jù)長(zhǎng)度的數(shù)據(jù)真實(shí)值 if (length > 125) { if (buf.length < 8) { return;//如果大于125,而字節(jié)數(shù)小于8,則顯然不合規(guī)范要求 } } if (length === 126) {//獲取的值為126 ,表示后兩個(gè)字節(jié)用于表示數(shù)據(jù)長(zhǎng)度 length = buf.readUInt16BE(2);//讀取16bit的值 idx += 2;//+2 } else if (length === 127) {//獲取的值為126 ,表示后8個(gè)字節(jié)用于表示數(shù)據(jù)長(zhǎng)度 var highBits = buf.readUInt32BE(2);//(1/0)1111111 if (highBits != 0) { this.close(1009, "");//1009關(guān)閉代碼,說明數(shù)據(jù)太大 } length = buf.readUInt32BE(6);//從第六到第十個(gè)字節(jié)為真實(shí)存放的數(shù)據(jù)長(zhǎng)度 idx += 8; } if (buf.length < idx + 4 + length) {//不夠長(zhǎng) 4為掩碼字節(jié)數(shù) return; } var maskBytes = buf.slice(idx, idx + 4);//獲取掩碼數(shù)據(jù) idx += 4;//指針前移到真實(shí)數(shù)據(jù)段 var payload = buf.slice(idx, idx + length); payload = unmask(maskBytes, payload);//解碼真實(shí)數(shù)據(jù) this._handleFrame(opcode, payload);//處理操作碼 this.buffer = buf.slice(idx + length);//緩存buffer return true; }; /** * 針對(duì)不同操作碼進(jìn)行不同處理 * @param 操作碼 * @param 數(shù)據(jù) */ WebSocketConnection.prototype._handleFrame = function (opcode, buffer) { "use strict"; var payload; switch (opcode) { case opcodes.TEXT: payload = buffer.toString('utf8');//如果是文本需要轉(zhuǎn)化為utf8的編碼 this.emit('data', opcode, payload);//Buffer.toString()默認(rèn)utf8 這里是故意指示的 break; case opcodes.BINARY: //二進(jìn)制文件直接交付 payload = buffer; this.emit('data', opcode, payload); break; case opcodes.PING://發(fā)送ping做響應(yīng) this._doSend(opcodes.PING, buffer); break; case opcodes.PONG: //不做處理 break; case opcodes.CLOSE://close有很多關(guān)閉碼 let code, reason;//用于獲取關(guān)閉碼和關(guān)閉原因 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'); } }; /** * 實(shí)際發(fā)送數(shù)據(jù)的函數(shù) * @param opcode 操作碼 * @param payload 數(shù)據(jù) * @private */ WebSocketConnection.prototype._doSend = function (opcode, payload) { "use strict"; this.socket.write(encodeMessage(opcode, payload));//編碼后直接通過socket發(fā)送 }; /** * 編碼數(shù)據(jù) * @param opcode 操作碼 * @param payload 數(shù)據(jù) * @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―15bit //Buffer.prototype.copy = function(targetBuffer, targetStart, sourceStart, sourceEnd) { payload.copy(buf, 2)//復(fù)制數(shù)據(jù),從2(第三)字節(jié)開始 } 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 掩碼數(shù)據(jù) * @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); };
感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
- 基于node實(shí)現(xiàn)websocket協(xié)議
- Python實(shí)現(xiàn)同時(shí)兼容老版和新版Socket協(xié)議的一個(gè)簡(jiǎn)單WebSocket服務(wù)器
- php使用websocket示例詳解
- Javascript WebSocket使用實(shí)例介紹(簡(jiǎn)明入門教程)
- Python通過websocket與js客戶端通信示例分析
- Nginx反向代理websocket配置實(shí)例
- 讓ie6也支持websocket采用flash封裝實(shí)現(xiàn)
- 使用swoole擴(kuò)展php websocket示例
- Spring和Websocket相結(jié)合實(shí)現(xiàn)消息的推送
- php+html5基于websocket實(shí)現(xiàn)聊天室的方法
- 淺析nodejs實(shí)現(xiàn)Websocket的數(shù)據(jù)接收與發(fā)送
- Android中使用WebSocket實(shí)現(xiàn)群聊和消息推送功能(不使用WebView)
相關(guān)文章
js實(shí)現(xiàn)日歷可獲得指定日期周數(shù)及星期幾示例分享(js獲取星期幾)
編寫一個(gè)簡(jiǎn)易日歷。在文本框中輸入要查找的日期,程序可以計(jì)算出這一天處在該年份的第幾周,并且能判斷出這一天到底是星期幾,需要的朋友可以參考下2014-03-03javascript中apply、call和bind的使用區(qū)別
下面小編就為大家?guī)硪黄猨avascript中apply、call和bind的使用區(qū)別。小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-04-04javaScript 事件綁定、事件冒泡、事件捕獲和事件執(zhí)行順序整理總結(jié)
這篇文章主要介紹了javaScript 事件綁定、事件冒泡、事件捕獲和事件執(zhí)行順序整理總結(jié)的相關(guān)資料,需要的朋友可以參考下2016-10-10JavaScript的21條基本知識(shí)點(diǎn)
這篇文章主要介紹了JavaScript的21條基本知識(shí)點(diǎn)的相關(guān)資料,需要的朋友可以參考下2014-03-03javascript SpiderMonkey中的函數(shù)序列化如何進(jìn)行
JavaScript中如何進(jìn)行函數(shù)序列化,函數(shù)序列化的作用是什么?本文將介紹SpiderMonkey中的函數(shù)序列化,有需要的朋友可以參考下2012-12-12JS 實(shí)現(xiàn)計(jì)算器詳解及實(shí)例代碼(一)
這篇文章主要介紹了JS 實(shí)現(xiàn)計(jì)算器詳解及實(shí)例代碼的相關(guān)資料,這里對(duì)實(shí)現(xiàn)計(jì)算器的思路及實(shí)現(xiàn)步驟進(jìn)行了一一詳解,需要的朋友可以參考下2017-01-01js中g(shù)etBoundingClientRect( )方法案例詳解
這篇文章主要介紹了js中g(shù)etBoundingClientRect( )方法案例詳解,本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07JavaScript之?dāng)?shù)組(Array)詳解
這篇文章主要介紹了JavaScript之?dāng)?shù)組(Array)詳解,本文詳細(xì)講解了JavaScript數(shù)組的創(chuàng)建、檢測(cè)數(shù)組、轉(zhuǎn)化方法、棧方法、隊(duì)列方法、重排序方法、操作方法、位置方法等內(nèi)容,需要的朋友可以參考下2015-04-04