Websocket協定詳解及簡單實例程式碼
Websocket協定詳解
關於websocket的協定是用來幹嘛的,請參考其他文章。
WebSocket關鍵字
HTML5協議,實時,全雙工通信,長連接
WebSocket比傳統Http的好處
1.客戶端與服務端只建立一個TCP連接,可以使用更少的連接
1.客戶端與服務端只建立一個TCP連接,可以使用更少的連接
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,關閉連接的數據幀。
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―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); };

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

隨著網路科技的不斷發展,即時通訊已經成為了日常生活中不可或缺的一部分。利用WebSockets技術可以實現高效、低延遲的即時通信,而PHP作為互聯網領域使用最廣泛的開發語言之一,也提供了相應的WebSocket支援。本文將為大家介紹如何使用PHP和WebSocket實現即時通信,並提供具體的程式碼範例。一、什麼是WebSocketWebSocket是一種在單

如何使用WebSocket和JavaScript實現線上語音辨識系統引言:隨著科技的不斷發展,語音辨識技術已成為了人工智慧領域的重要組成部分。而基於WebSocket和JavaScript實現的線上語音辨識系統,具備了低延遲、即時性和跨平台的特點,成為了廣泛應用的解決方案。本文將介紹如何使用WebSocket和JavaScript來實現線上語音辨識系

golangWebSocket與JSON的結合:實現資料傳輸和解析在現代的Web開發中,即時資料傳輸變得越來越重要。 WebSocket是一種用於實現雙向通訊的協議,與傳統的HTTP請求-回應模型不同,WebSocket允許伺服器向客戶端主動推送資料。而JSON(JavaScriptObjectNotation)是一種用於資料交換的輕量級格式,它簡潔易讀

隨著網路技術的不斷發展,即時視訊串流已成為了網路領域的重要應用。要實現即時視訊串流播放,其中的關鍵技術包括WebSocket和Java。本文將介紹如何結合使用WebSocket和Java實現即時視訊串流播放,並提供相關的程式碼範例。一、什麼是WebSocketWebSocket是一種在單一TCP連線上進行全雙工通訊的協議,它在Web

WebSocket與JavaScript:實現即時監控系統的關鍵技術引言:隨著互聯網技術的快速發展,即時監控系統在各個領域中得到了廣泛的應用。而實現即時監控的關鍵技術之一就是WebSocket與JavaScript的結合使用。本文將介紹WebSocket與JavaScript在即時監控系統中的應用,並給出程式碼範例,詳細解釋其實作原理。一、WebSocket技

PHP和WebSocket:實現即時資料傳輸的最佳實踐方法引言:在Web應用程式開發中,即時資料傳輸是一項非常重要的技術需求。傳統的HTTP協定是一種請求-回應模式的協議,不能有效地實現即時資料傳輸。為了滿足即時資料傳輸的需求,WebSocket協定應運而生。 WebSocket是一種全雙工通訊協議,它提供了一種在單一TCP連接上進行全雙工通訊的方式。相比於H

如何利用Java和WebSocket實現即時股票行情推播引言:隨著網路的快速發展,股票行情即時推播成為了投資人關注的焦點之一。傳統的股票行情推送方式存在延遲較高、刷新速度慢等問題,對於投資人來說,無法及時獲得最新的股票行情資訊可能會導致投資決策的誤差。而基於Java和WebSocket的即時股票行情推送可以有效解決這個問題,使投資者能夠第一時間獲取到最新的

在本文中,我們將比較伺服器發送事件(SSE)和 WebSocket,兩者都是用於傳遞資料的可靠方法。我們將在八個方面對它們進行分析,包括通訊方向、底層協定、安全性、易用性、效能、訊息結構、易用性和測試工具。這些方面的比較總結如下:類別伺服器發送事件(SSE)WebSocket通訊方向單向雙向底層協定HTTPWebSocket 協定安全性與HTTP 相同存在安全漏洞易用性設定簡單設定複雜效能訊息傳送速度快受訊息處理和連線管理影響訊息結構純文字文字或二進位易用性廣泛可用對WebSocket 整合有
