基于node實(shí)現(xiàn)websocket協(xié)議
一、協(xié)議
WebSocket是一種基于TCP之上的客戶端與服務(wù)器全雙工通訊的協(xié)議,它在HTML5中被定義,也是新一代webapp的基礎(chǔ)規(guī)范之一。
它突破了早先的AJAX的限制,關(guān)鍵在于實(shí)時(shí)性,服務(wù)器可以主動(dòng)推送內(nèi)容 到客戶端!可能的應(yīng)用有:多人在線游戲,即時(shí)聊天,實(shí)時(shí)監(jiān)控,遠(yuǎn)程桌面,新聞服務(wù)器等等。
對(duì)于我自己,當(dāng)前最想嘗試的是canvas+websocket組合起來(lái)能做什么。
二、實(shí)現(xiàn)
由于握手的過(guò)程是一個(gè)標(biāo)準(zhǔn)的HTTP請(qǐng)求,因此 websocket 的實(shí)現(xiàn)有兩種選擇:1)TCP上實(shí)現(xiàn); 2) 現(xiàn)有HTTP軟件上實(shí)現(xiàn)。后者的優(yōu)勢(shì)在于可以共用現(xiàn)有的HTTP服務(wù)器端口,并且不用重新實(shí)現(xiàn)認(rèn)證功能和解析HTTP請(qǐng)求的功能。
這個(gè)示例中使用的 node 的HTTP模塊。(TCP版及所有文件見(jiàn) 附件)
1、node服務(wù)器端代碼:
var http = require('http'); var url = require('url'); // var mime = require('mime'); var crypto = require('crypto'); var port = 4400; var server = http.createServer(); server.listen(port,function() { console.log('server is running on localhost:',port); server .on('connection',function(s) { console.log('on connection ',s); }) .on('request',onrequest) .on('upgrade',onupgrade); }); var onrequest = function(req,res) { console.log( Object.keys(req) ,req.url,req['upgrade']); if( !req.upgrade ){ // 非upgrade請(qǐng)求選擇:中斷或提供普通網(wǎng)頁(yè) res.writeHead(200, { 'content-type': 'text/plain' }); res.write( 'WebSocket server works!' ); } res.end(); return; }; var onupgrade = function (req,sock,head) { // console.log('方法:',Object.keys(sock)); if(req.headers.upgrade !== 'WebSocket'){ console.warn('非法連接'); sock.end(); return; } bind_sock_event(sock); try{ handshake(req,sock,head); }catch(e){ console.error(e); sock.end(); } }; // 包裝將要發(fā)送的幀 var wrap = function(data) { var fa = 0x00, fe = 0xff, data = data.toString() len = 2+Buffer.byteLength(data), buff = new Buffer(len); buff[0] = fa; buff.write(data,1); buff[len-1] = fe; return buff; } // 解開(kāi)接收到的幀 var unwrap = function(data) { return data.slice(1,data.length-1); } var bind_sock_event = function(sock) { sock .on('data',function(buffer) { var data = unwrap(buffer); console.log('socket receive data : ',buffer,data,'\n>>> '+data); // send('hello html5,'+Date.now()) sock.emit('send',data); }) .on('close',function() { console.log('socket close'); }) .on('end',function() { console.log('socket end'); }) .on('send',function(data) { //自定義事件 sock.write(wrap(data),'binary'); }) }; var get_part = function(key) { var empty = '', spaces = key.replace(/\S/g,empty).length, part = key.replace(/\D/g,empty); if(!spaces) throw {message:'Wrong key: '+key,name:'HandshakeError'} return get_big_endian(part / spaces); } var get_big_endian = function(n) { return String.fromCharCode.apply(null,[3,2,1,0].map(function(i) { return n >> 8*i & 0xff })) } var challenge = function(key1,key2,head) { var sum = get_part(key1) + get_part(key2) + head.toString('binary'); return crypto.createHash('md5').update(sum).digest('binary'); } var handshake = function(req,sock,head) { var output = [],h = req.headers, br = '\r\n'; // header output.push( 'HTTP/1.1 101 WebSocket Protocol Handshake','Upgrade: WebSocket','Connection: Upgrade', 'Sec-WebSocket-Origin: ' + h.origin, 'Sec-WebSocket-Location: ws://' + h.host + req.url, 'Sec-WebSocket-Protocol: my-custom-chat-protocol'+br ); // body var c = challenge(h['sec-websocket-key1'],h['sec-websocket-key2'],head); output.push(c); sock.write(output.join(br),'binary'); }
2、瀏覽器客戶端代碼:
<html> <head> <title>WebSocket Demo</title> </head> <style type="text/css"> textarea{width:400px;height:150px;display:block;overflow-y:scroll;} #output{width:600px;height:400px;background:whiteSmoke;padding:1em .5em;color:#000;border:none;} button{padding:.2em 1em;} </style> <link href="layout.css" rel="stylesheet" type="text/css" /> <body> <textarea id="output" readonly="readonly"></textarea> <br> <textarea id="input"></textarea> <button id="send">send</button> <script type="text/javascript"> // localhost var socket = new WebSocket('ws://192.168.144.131:4400/') socket.onopen = function(e) { log(e.type); socket.send('hello node'); } socket.onclose = function(e) { log(e.type); } socket.onmessage = function(e) { log('receive @ '+ new Date().toLocaleTimeString() +'\n'+e.data); output.scrollTop = output.scrollHeight } socket.onclose = function(e) { log(e.type); } socket.addEventListener('close',function() { log('a another close event handler..'); },false); // dom var id = function(id) { return document.getElementById(id); } var output = id('output'), input = id('input'), send = id('send'); var log = function(msg) { output.textContent += '> '+msg + '\n' } send.addEventListener('click',function() { socket.send(input.value); },false); </script> </body> </html>
三、細(xì)節(jié)
在 http 協(xié)議之上的 websocket 協(xié)議實(shí)現(xiàn)只有兩步:握手,發(fā)送數(shù)據(jù)。
1、握手
握手的過(guò)程被稱為 challenge-response。首先客戶端發(fā)起一個(gè)名為Upgrade的HTTP GET請(qǐng)求,服務(wù)器驗(yàn)證此請(qǐng)求,給出101響應(yīng)以表示接受此次協(xié)議升級(jí),握手即完成了。
chrome inspector美化過(guò)的握手信息:
Request URL:ws://192.168.144.131:4400/pub/chat?q=me
Request Method:GET
Status Code:101 WebSocket Protocol Handshake
Request Headers
Connection:Upgrade
Host:192.168.144.131:4400
Origin:http://localhost:800
Sec-WebSocket-Key1:p2 G 947T 80 661 jAf2
Sec-WebSocket-Key2:z Z Q ^326 5 9= 7s1 1 7H4
Sec-WebSocket-Protocol::my-custom-chat-protocol
Upgrade:WebSocket
(Key3):7C:44:56:CA:1F:19:D2:0A
Response Headers
Connection:Upgrade
Sec-WebSocket-Location:ws://192.168.144.131:4400/pub/chat?q=me
Sec-WebSocket-Origin:http://localhost:800
Sec-WebSocket-Protocol:my-custom-chat-protocol
Upgrade:WebSocket
(Challenge Response):52:DF:2C:F4:50:C2:8E:98:14:B7:7D:09:CF:C8:33:40
請(qǐng)求頭部分
Host: websocket服務(wù)器主機(jī)
Connection: 連接類型
Upgrade: 協(xié)議升級(jí)類型
Origin: 訪問(wèn)來(lái)源
Sec-WebSocket-Protocol: 可選,子協(xié)議名稱,由應(yīng)用自己定義,多個(gè)協(xié)議用空格分割。(*另外一個(gè)僅剩的可選項(xiàng)是cookie)
Sec-WebSocket-Key1: 安全認(rèn)證key,xhr請(qǐng)求不能偽造'sec-'開(kāi)頭的請(qǐng)求頭。
Sec-WebSocket-Key2: 同上
Key3: 響應(yīng)體內(nèi)容,8字節(jié)隨機(jī)。
響應(yīng)頭部分
Sec-WebSocket-Protocol: 必須包含請(qǐng)求的子協(xié)議名
Sec-WebSocket-Origin: 必須等于請(qǐng)求的來(lái)源
Sec-WebSocket-Location: 必須等于請(qǐng)求的地址
Challenge Response: 響應(yīng)體內(nèi)容,根據(jù)請(qǐng)求中三個(gè)key計(jì)算得來(lái),16字節(jié)。
應(yīng)答字符串計(jì)算過(guò)程偽代碼:
part_1 = key1中所有數(shù)字 / key1中空格數(shù)量 part_2 同上 sum = big_endian(part_1)+big_endian(part_2)+key3 challenge_response = md5_digest(sum);
32位整數(shù)的big_endian計(jì)算策略:
# 很類似于rgba顏色計(jì)算,從下面的函數(shù)可以看出計(jì)算過(guò)程 var big_endian = function(n) { return [3,2,1,0].map(function(i) { return n >> 8*i & 0xff }); } big_endian(0xcc77aaff); // -> [204, 119, 170, 255]
2、發(fā)送數(shù)據(jù)
WebSocket API的被設(shè)計(jì)成用事件處理數(shù)據(jù),客戶端只要得到事件通知就可以獲取到完整的數(shù)據(jù),而不需要手動(dòng)處理緩沖器。
這種情況下,每一筆數(shù)據(jù)被稱為一幀。在規(guī)范的定義中,它的頭部必須以0x00開(kāi)始,尾部屬性以0xff結(jié)束,這樣每一次數(shù)據(jù)發(fā)送至少有兩個(gè)字節(jié)。
服務(wù)器實(shí)現(xiàn)中,收到數(shù)據(jù)時(shí)要截掉頭尾;而發(fā)送數(shù)據(jù)是要包裝頭尾。格式如下:
# '你好'的原始二進(jìn)制表示,請(qǐng)求頭和這里都是utf8編碼
<Buffer e4 bd a0 e5 a5 bd>
# 包裝后的二進(jìn)制表示。
<Buffer 00 e4 bd a0 e5 a5 bd ff>
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。
- Python實(shí)現(xiàn)同時(shí)兼容老版和新版Socket協(xié)議的一個(gè)簡(jiǎn)單WebSocket服務(wù)器
- php使用websocket示例詳解
- Javascript WebSocket使用實(shí)例介紹(簡(jiǎn)明入門(mén)教程)
- Python通過(guò)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)
- Websocket協(xié)議詳解及簡(jiǎn)單實(shí)例代碼
相關(guān)文章
為什么Node.js會(huì)這么火呢?Node.js流行的原因
是什么原因讓Node.js突然間如此流行呢?聽(tīng)起來(lái)像是有了一種新的Web開(kāi)發(fā)技術(shù),是這樣嗎?我們來(lái)匯總一下。2014-12-12node.js實(shí)現(xiàn)逐行讀取文件內(nèi)容的代碼
這篇文章主要介紹了node.js實(shí)現(xiàn)逐行讀取文件內(nèi)容的代碼,本文還介紹了一個(gè)node.js的按行讀取內(nèi)容開(kāi)源項(xiàng)目,需要的朋友可以參考下2014-06-06Win7系統(tǒng)中如何安裝高版本的NodeJS(親測(cè)有效!)
Node.js是基于Chrome V8引擎的JavaScript運(yùn)行環(huán)境,能夠使JavaScript在服務(wù)器端運(yùn)行,這篇文章主要給大家介紹了關(guān)于Win7系統(tǒng)中如何安裝高版本的NodeJS的相關(guān)資料,需要的朋友可以參考下2023-12-125分鐘教你用nodeJS手寫(xiě)一個(gè)mock數(shù)據(jù)服務(wù)器的方法
這篇文章主要介紹了5分鐘教你用nodeJS手寫(xiě)一個(gè)mock數(shù)據(jù)服務(wù)器的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09Nodejs 中的 Buffer 類的創(chuàng)建與基本使用
這篇文章主要為大家介紹了Nodejs中Buffer的使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10node.js 和HTML5開(kāi)發(fā)本地桌面應(yīng)用程序
這篇文章主要介紹了node.js 和HTML5開(kāi)發(fā)本地桌面應(yīng)用程序的相關(guān)資料,需要的朋友可以參考下2016-12-12Node.js同時(shí)安裝多個(gè)版本及相關(guān)配置指南(簡(jiǎn)單易操作)
在實(shí)際開(kāi)發(fā)過(guò)程中我們可能需要安裝多個(gè)版本的 nodejs,下面這篇文章主要給大家介紹了關(guān)于Node.js同時(shí)安裝多個(gè)版本及相關(guān)配置的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11