摘要:用實(shí)現(xiàn)簡單協(xié)議從瀏覽器說起瀏覽器提供的非常簡潔。創(chuàng)建連接連接建立時(shí)的回調(diào)收到消息時(shí)的回調(diào)連接出錯(cuò)時(shí)的回調(diào)連接終止時(shí)的回調(diào)發(fā)送消息告訴我們也就是說,在創(chuàng)建對(duì)象時(shí),瀏覽器嘗試與服務(wù)端建立連接發(fā)送請(qǐng)求建立個(gè)服務(wù)端一旦收到數(shù)據(jù),就會(huì)觸發(fā)。
用 Node 實(shí)現(xiàn)簡單 WebSocket 協(xié)議 從瀏覽器 WebSocket API 說起
瀏覽器提供的 WebSocket API 非常簡潔。
let ws = new WebSocket("ws://localhost:8124") // 創(chuàng)建連接
ws.onopen = function(e){...} // 連接建立時(shí)的回調(diào)
ws.onmessage = function(e){...} // 收到消息時(shí)的回調(diào)
ws.onerror = function(e){...} // 連接出錯(cuò)時(shí)的回調(diào)
ws.onclose = function(e){...} // 連接終止時(shí)的回調(diào)
ws.send("Hello Server!") // 發(fā)送消息
MDN告訴我們
In order to communicate using the WebSocket protocol, you need to create a WebSocket object; this will automatically attempt to open the connection to the server.
也就是說,在 let ws = new WebSocket("ws://localhost:8124") 創(chuàng)建 WebSocket 對(duì)象時(shí),瀏覽器嘗試與服務(wù)端建立連接(發(fā)送請(qǐng)求)
建立個(gè)服務(wù)端const net = require("net") const server = net.createServer(s => { s.on("data", d => { console.log(d.toString()) }) }) server.listen(8124, () => { console.log("listening on 8124...") })
一旦收到數(shù)據(jù),就會(huì)觸發(fā) data。
啟動(dòng)服務(wù)端,監(jiān)聽8124端口;在瀏覽器中打開控制臺(tái),輸入let ws = new WebSocket("ws://localhost:8124");得到結(jié)果如下
通過這次請(qǐng)求,瀏覽器告訴服務(wù)端,要升級(jí)協(xié)議為 websocket
服務(wù)端收到這個(gè)請(qǐng)求后,向?yàn)g覽器發(fā)出響應(yīng),這個(gè)過程就叫做握手(handshaking)
服務(wù)端向?yàn)g覽器發(fā)出同意升級(jí)協(xié)議的響應(yīng)后會(huì)觸發(fā)瀏覽器端 websocket.onopen 的回調(diào)
服務(wù)端的響應(yīng)Obtain the value of Sec-WebSocket-Key request header without any leading and trailing whitespace
Link it with "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
Compute SHA-1 and Base64 code of it
Write it back as value of Sec-WebSocket-Accept response header as part of a HTTP response.
根據(jù)MDN的指示,修改服務(wù)端代碼
const net = require("net") const crypto = require("crypto") const server = net.createServer(s => { s.on("data", d => { let req = d.toString() // Obtain the value of Sec-WebSocket-Key request header without any leading and trailing whitespace let secWebsocketKey = /Sec-WebSocket-Key:s(.*)/.exec(req)[1] // Link it with "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" let key = secWebsocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" // Compute SHA-1 and Base64 code of it let secWebSocketAccept = crypto.createHash("sha1").update(key).digest("base64") // Write it back as value of Sec-WebSocket-Accept response header as part of a HTTP response. let res = "HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: websocket Sec-WebSocket-Accept: " + secWebSocketAccept + " " s.write(res) }) }) server.listen(8124, () => { console.log("listening on 8124...") })
同時(shí)為了方便測(cè)試,寫個(gè)html文件
socketClient socketClient
啟動(dòng)服務(wù)端,用瀏覽器打開html文件
至此之后,瀏覽器和服務(wù)端之間就可以更加愉快地聊天了,之前的服務(wù)端很矜持,瀏覽器問一句,服務(wù)端答一句;而握手之后,服務(wù)端也會(huì)主動(dòng)向?yàn)g覽器發(fā)送消息,這樣就會(huì)觸發(fā)瀏覽器端 websocket.onmessage 的回調(diào)
服務(wù)端主動(dòng)發(fā)送消息修改服務(wù)端代碼
... s.on("data", d => { let req = d.toString() console.log(req) // Obtain the value of Sec-WebSocket-Key request header without any leading and trailing whitespace let secWebsocketKey = /Sec-WebSocket-Key:s(.*)/.exec(req)[1] // Link it with "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" let key = secWebsocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" // Compute SHA-1 and Base64 code of it let secWebSocketAccept = crypto.createHash("sha1").update(key).digest("base64") // Write it back as value of Sec-WebSocket-Accept response header as part of a HTTP response. let res = "HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: websocket Sec-WebSocket-Accept: " + secWebSocketAccept + " " console.log(res) s.write(res) // 服務(wù)端主動(dòng)發(fā)消息 Hi, Client! l"m Server. let dataBuffer = new Buffer(`Hi, Client! l"m Server.`) let payload_len = dataBuffer.length let assistData = [] assistData.push(129) assistData.push(payload_len) let assistBuffer = new Buffer(assistData) let message = Buffer.concat([assistBuffer, dataBuffer]) console.log(message) s.write(message) }) }) ...
代碼中
// 服務(wù)端主動(dòng)發(fā)消息 Hi, Client! l"m Server. let dataBuffer = new Buffer(`Hi, Client! l"m Server.`) let payload_len = dataBuffer.length let assistData = [] assistData.push(129) assistData.push(payload_len) let assistBuffer = new Buffer(assistData) let message = Buffer.concat([assistBuffer, dataBuffer]) console.log(message) s.write(message)
實(shí)際上是將數(shù)據(jù)編碼成數(shù)據(jù)幀的過程,其具體細(xì)節(jié)稍后再說。
修改html
websocket.send()瀏覽器發(fā)送消息很簡單
修改html
修改服務(wù)端代碼
const net = require("net") const crypto = require("crypto") const server = net.createServer(s => { s.handshaking = false s.on("data", d => { if (!s.handshaking) { let req = d.toString() console.log(req) // Obtain the value of Sec-WebSocket-Key request header without any leading and trailing whitespace let secWebsocketKey = /Sec-WebSocket-Key:s(.*)/.exec(req)[1] // Link it with "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" let key = secWebsocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" // Compute SHA-1 and Base64 code of it let secWebSocketAccept = crypto.createHash("sha1").update(key).digest("base64") // Write it back as value of Sec-WebSocket-Accept response header as part of a HTTP response. let res = "HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: websocket Sec-WebSocket-Accept: " + secWebSocketAccept + " " console.log(res) s.write(res) // 服務(wù)端主動(dòng)發(fā)消息 Hi, Client! l"m Server. let dataBuffer = new Buffer(`Hi, Client! l"m Server.`) let payload_len = dataBuffer.length let assistData = [] assistData.push(129) assistData.push(payload_len) let assistBuffer = new Buffer(assistData) let message = Buffer.concat([assistBuffer, dataBuffer]) console.log(message) s.write(message) s.handshaking = true } else { //解析瀏覽器發(fā)送消息 let fin = d[0] >> 7 let opcode = d[0] & parseInt(1111, 2) // 1 表示文本數(shù)據(jù)幀 let mask = d[1] >> 7 // 標(biāo)示是否進(jìn)行掩碼處理,客戶端發(fā)送給服務(wù)端時(shí)為1,服務(wù)端發(fā)送給客戶端時(shí)為0 let payload_len = d[1] & parseInt(1111111, 2) // 這里假設(shè)發(fā)送的數(shù)據(jù)長度小于 125 let masking_key = d.slice(2, 6) let payload_data = new Buffer(payload_len) for (let i = 0; i < payload_len; i++) { let j = i % 4 payload_data[i] = d[6 + i] ^ masking_key[j] } console.log(payload_data.toString()) } }) }) server.listen(8124, () => { console.log("listening on 8124...") })編碼解碼數(shù)據(jù)幀
解碼Hi, Server! l"m Client.
let fin = d[0] >> 7 let opcode = d[0] & parseInt(1111, 2) // 1 表示文本數(shù)據(jù)幀 let mask = d[1] >> 7 // 標(biāo)示是否進(jìn)行掩碼處理,客戶端發(fā)送給服務(wù)端時(shí)為1,服務(wù)端發(fā)送給客戶端時(shí)為0 let payload_len = d[1] & parseInt(1111111, 2) // 這里假設(shè)發(fā)送的數(shù)據(jù)長度小于 125 let masking_key = d.slice(2, 6) let payload_data = new Buffer(payload_len) for (let i = 0; i < payload_len; i++) { let j = i % 4 payload_data[i] = d[6 + i] ^ masking_key[j] } console.log(payload_data.toString())
編碼Hi, Client! l"m Server.
let dataBuffer = new Buffer(`Hi, Client! l"m Server.`) let payload_len = dataBuffer.length let assistData = [] assistData.push(129) assistData.push(payload_len) let assistBuffer = new Buffer(assistData) let message = Buffer.concat([assistBuffer, dataBuffer]) console.log(message)
解碼編碼依據(jù)Base Framing Protocol
主要用到 nodeAPI 中 Buffer 的模塊以及位運(yùn)算
其實(shí)我主要看的是《深入淺出NodeJS》第七章websocket的那部分
小結(jié)
握手之后才可以通信
從請(qǐng)求頭獲取websocketKey
設(shè)置響應(yīng)頭
通信
編碼信息
解碼信息
位運(yùn)算/Buffer/正則
let socketArr = [] const server = net.createServer(s => { s.handShaking === false s.name = `Client_${socketArr.length}` socketArr.push(s) s.on("data", d => {}) ... })
將每個(gè)socket對(duì)象標(biāo)記并保存,從而實(shí)現(xiàn)對(duì)象之間的通信
參考資料WebSocket
Writing WebSocket client applicaitons
Writing a WebSocket server in Java
《深入淺出NodeJS》第六章、第七章部分
The WebSocket Protocol
用Node實(shí)現(xiàn)WebSocket協(xié)議
基于node實(shí)現(xiàn)websocket協(xié)議
nodejs實(shí)現(xiàn)websocket的數(shù)據(jù)接收發(fā)送
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/90464.html
摘要:預(yù)備工作序最近正在研究相關(guān)的知識(shí),想著如何能自己實(shí)現(xiàn)協(xié)議。監(jiān)聽事件就是協(xié)議的抽象,直接在上面監(jiān)聽已有的事件和事件這兩個(gè)事件。表示當(dāng)前數(shù)據(jù)幀為消息的最后一個(gè)數(shù)據(jù)幀,此時(shí)接收方已經(jīng)收到完整的消息,可以對(duì)消息進(jìn)行處理。 A、預(yù)備工作 1、序 最近正在研究 Websocket 相關(guān)的知識(shí),想著如何能自己實(shí)現(xiàn) Websocket 協(xié)議。到網(wǎng)上搜羅了一番資料后用 Node.js 實(shí)現(xiàn)該協(xié)議,倒也沒...
摘要:接口用于接收服務(wù)器發(fā)送的事件。因此,是目前來說最佳的選擇。最大特點(diǎn)就是,服務(wù)器可以主動(dòng)向客戶端推送消息,客戶端也可以主動(dòng)向服務(wù)器發(fā)送信息,是一種不受限的全雙工通信。若是,則交給的回調(diào)函數(shù)處理,否則,還是走正常的回調(diào)的路子。 使用 WebSocket 的理由 傳統(tǒng)的http協(xié)議有一個(gè)根本性的缺陷,那就是請(qǐng)求只能由客戶端向服務(wù)器發(fā)起,服務(wù)器接收到請(qǐng)求后再進(jìn)行響應(yīng),把數(shù)據(jù)返回給客戶端。也就是...
摘要:服務(wù)器推遲響應(yīng),直到發(fā)生更改更新或超時(shí)。旨在取代現(xiàn)有的雙向通信技術(shù)。只要我們對(duì)套接字事件和有了充分的了解,理解和實(shí)現(xiàn)就非常簡單。 翻譯:瘋狂的技術(shù)宅 原文:blog.logrocket.com/websockets-… showImg(https://user-gold-cdn.xitu.io/2019/5/21/16ada008fc1f81d7); Web 為了支持客戶端和服務(wù)器之間的全...
閱讀 719·2021-11-22 13:54
閱讀 3081·2021-09-26 10:16
閱讀 3511·2021-09-08 09:35
閱讀 1590·2019-08-30 15:55
閱讀 3438·2019-08-30 15:54
閱讀 2085·2019-08-30 10:57
閱讀 503·2019-08-29 16:25
閱讀 884·2019-08-29 16:15