亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

淺析nodejs實現Websocket的數據接收與發送

2019-11-20 11:13:55
字體:
來源:轉載
供稿:網友

WebSocket是HTML5開始提供的一種瀏覽器與服務器間進行全雙工通訊的網絡技術。在WebSocket API中,瀏覽器和服務器只需要要做一個握手(handshaking)的動作,然后,瀏覽器和服務器之間就形成了一條快速通道。兩者之間就直接可以數據互相傳送。

WebSocket是一個通信的協議,分為服務器和客戶端。服務器放在后臺,保持與客戶端的長連接,完成雙方通信的任務??蛻舳艘话愣际菍崿F在支持HTML5瀏覽器核心中,通過提供JavascriptAPI使用網頁可以建立websocket連接。

在我寫這篇文章里:基于html5和nodejs相結合實現websocket即使通訊,里面主要是借助了nodejs-websocket這個插件,后來還用了socket.io做了些demo,但是,這些都是借助于別人封裝好的插件做出來的,websocket到底是怎么實現的呢?之前真沒想過,最近看樸靈大神的《深入淺析node.js》時候,看到了websocket那一節,看了websocket的數據幀定義,想著用nodejs實現。經過一番折騰實現了。

客戶端的代碼就不說了,websocket的API還是很簡單的,就通過onmessage、onopen、onclose,以及send方法就可以實現了。
websocket api通過onmessage、onopen、onclose以及send方法實現客戶端的代碼。具體詳情就不多說了。

主要說服務端的代碼:

首先是協議的升級,這個比較簡單,就簡述一下:當在客戶端執行new Websocket("ws://XXX.com/")的時候,客戶端就會發起請求報文進行握手申請,報文中有個很重要的key就是Sec-WebSocket-Key,服務端獲取到key,然后將這個key與字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11相連,對新的字符串通過sha1安全散列算法計算出結果后,再進行base64編碼,并且將結果放在請求頭的"Sec-WebSocket-Accept"中返回即可完成握手。具體請看代碼:

server.on('upgrade', function (req, socket, upgradeHead) { var key = req.headers['sec-websocket-key']; key = crypto.createHash("sha1").update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest("base64"); var headers = [ 'HTTP/1.1 101 Switching Protocols', 'Upgrade: websocket', 'Connection: Upgrade', 'Sec-WebSocket-Accept: ' + key ]; socket.setNoDelay(true); socket.write(headers.join("/r/n") + "/r/n/r/n", 'ascii'); var ws = new WebSocket(socket); webSocketCollector.push(ws); callback(ws);});

upgrade事件其實是http這個模塊的封裝,再往底層就是net模塊的實現,其實都差不多,如果直接用net模塊來實現的話,就是監聽net.createServer返回的server對象的data事件,接收到的第一份數據就是客戶端發來的升級請求報文。

上面那段代碼就完成了websocket的握手,然后就可以開始數據傳輸了。

看數據傳輸之前,先看看websocket數據幀的定義(因為覺得深入淺出nodejs里的幀定義圖最容易理解,所以就貼這張了):

 

上面的圖中,每一列就是一個字節,一個字節總共是8位,每一位就是一個二進制數,不同位的值會對應不同的意義。

fin:指示這個是消息的最后片段。第一個片段可能也是最后的片段。如果為1即為最后片段,(其實這個位的用途我個人有點疑惑,按照書上以及網上查的資料,當數據被分片的時候,不同片應該都會有fin位,會根據fin為是不是0來判斷是否為最后一幀,但是實際實現中卻發現,當數據比較大需要分片時,服務端收到的數據就只有第一幀是有fin位為1,其他幀則整個幀都是數據段,也就是說,感覺這個fin位似乎用不上,至少我自己寫的demo中是通過數據長度來判斷是否到了最后一幀,完全沒用到這個fin位是否為1來判斷)

rsv1、rsv2、rsv3:各占一個位,用于擴展協商,基本上不怎么需要理,一般都是0

opcode:占四個位,可以表示0~15的十進制,0表示為附加數據幀,1表示為文本數據幀,2表示二進制數據幀,8表示發送一個連接關閉的數據幀,9表示ping,10表示pong,ping和pong都是用于心跳檢測,當一端發送ping時,另一端必須響應pong表示自己仍處于響應狀態。

masked:占一個位,表示是否進行掩碼處理,客戶端發送給服務端時為1,服務端發送給客戶端時為0

payload length:占7位,或者7+16位、或者7+64位。如果第二個字節的后面七個位的十進制值小于或等于125,則直接用這七個位表示數據長度;如果該值為126,說明 125<數據長度<65535(16個位能描述的最大值,也就是16個1的時候),就用第三個字節及第四個字節即16個位來表示;如果該值為127,則說明數據長度已經大于65535,16個位也已經不足以描述數據長度了,就用第三到第十個字節這八個字節來描述數據長度。

masking key:當masked為1的時候才存在,用于對我們需要的數據進行解密。

payload data:我們需要的數據,如果masked為1,該數據會被加密,要通過masking key進行異或運算解密才能獲取到真實數據。

幀定義解釋完了,就可以根據數據來進行解析了,當有data過來的時候,先獲取需要的數據信息,下面這段代碼將獲取到數據在data里的位置,以及數據長度,masking key以及opcode:

WebSocket.prototype.handleDataStat = function (data) { if (!this.stat) { var dataIndex = 2; //數據索引,因為第一個字節和第二個字節肯定不為數據,所以初始值為2 var secondByte = data[1]; //代表masked位和可能是payloadLength位的第二個字節 var hasMask = secondByte >= 128; //如果大于或等于128,說明masked位為1 secondByte -= hasMask ? 128 : 0; //如果有掩碼,需要將掩碼那一位去掉 var dataLength, maskedData; //如果為126,則后面16位長的數據為數據長度,如果為127,則后面64位長的數據為數據長度 if (secondByte == 126) {  dataIndex += 2;  dataLength = data.readUInt16BE(2); } else if (secondByte == 127) {  dataIndex += 8;  dataLength = data.readUInt32BE(2) + data.readUInt32BE(6); } else {  dataLength = secondByte; } //如果有掩碼,則獲取32位的二進制masking key,同時更新index if (hasMask) {  maskedData = data.slice(dataIndex, dataIndex + 4);  dataIndex += 4; } //數據量最大為10kb if (dataLength > 10240) {  this.send("Warning : data limit 10kb"); } else {  //計算到此處時,dataIndex為數據位的起始位置,dataLength為數據長度,maskedData為二進制的解密數據  this.stat = {  index: dataIndex,  totalLength: dataLength,  length: dataLength,  maskedData: maskedData,  opcode: parseInt(data[0].toString(16).split("")[1] , 16) //獲取第一個字節的opcode位  }; } } else { this.stat.index = 0; }};

代碼中均有注釋,理解起來應該不難,直接看下一步,獲取到數據信息后,就要對數據進行實際解析了:

經過上面handleDataStat方法的處理,stat中已經有了data的相關數據,先判斷opcode,如果為9說明是客戶端發起的ping心跳檢測,直接返回pong響應,如果為10則為服務端發起的心跳檢測。如果有masking key,則遍歷數據段,對每個字節都與masking key的字節進行異或運算(網上看到一個說法很形象:就是輪流發生X關系),^符號就是進行異或運算啦。如果沒有masking key則直接通過slice方法把數據截取下來。

獲取到數據后,放進datas里保存,因為有可能數據被分片了,所以再將stat里的長度減去當前數據長度,只有當stat里的長度為0的時候,說明當前幀為最后一幀,然后通過Buffer.concat將所有數據合并,此時再判斷一下opcode,如果opcode為8,則說明客戶端發起了一個關閉請求,而我們獲取到的數據則是關閉原因。如果不為8,則這數據就是我們需要的數據。然后再將stat重置為null,datas數組置空即可。至此,我們的數據解析就完成了。

WebSocket.prototype.dataHandle = function (data) { this.handleDataStat(data); var stat; if (!(stat = this.stat)) return; //如果opcode為9,則發送pong響應,如果opcode為10則置pingtimes為0 if (stat.opcode === 9 || stat.opcode === 10) { (stat.opcode === 9) ? (this.sendPong()) : (this.pingTimes = 0); this.reset(); return; } var result; if (stat.maskedData) { result = new Buffer(data.length-stat.index); for (var i = stat.index, j = 0; i < data.length; i++, j++) {  //對每個字節進行異或運算,masked是4個字節,所以%4,借此循環  result[j] = data[i] ^ stat.maskedData[j % 4]; } } else { result = data.slice(stat.index, data.length); } this.datas.push(result); stat.length -= (data.length - stat.index); //當長度為0,說明當前幀為最后幀 if (stat.length == 0) { var buf = Buffer.concat(this.datas, stat.totalLength); if (stat.opcode == 8) {  this.close(buf.toString()); } else {  this.emit("message", buf.toString()); } this.reset(); }};

完成了客戶端發來的數據解析,還需要一個服務端發數據至客戶端的方法,也就是按照上面所說的幀定義來組裝數據并且發送出去。下面的代碼中基本上每一行都有注釋,應該還是比較容易理解的。

//數據發送WebSocket.prototype.send = function (message) { if(this.state !== "OPEN") return; message = String(message); var length = Buffer.byteLength(message);// 數據的起始位置,如果數據長度16位也無法描述,則用64位,即8字節,如果16位能描述則用2字節,否則用第二個字節描述 var index = 2 + (length > 65535 ? 8 : (length > 125 ? 2 : 0));// 定義buffer,長度為描述字節長度 + message長度 var buffer = new Buffer(index + length);// 第一個字節,fin位為1,opcode為1 buffer[0] = 129;// 因為是由服務端發至客戶端,所以無需masked掩碼 if (length > 65535) { buffer[1] = 127;// 長度超過65535的則由8個字節表示,因為4個字節能表達的長度為4294967295,已經完全夠用,因此直接將前面4個字節置0 buffer.writeUInt32BE(0, 2); buffer.writeUInt32BE(length, 6); } else if (length > 125) { buffer[1] = 126;// 長度超過125的話就由2個字節表示 buffer.writeUInt16BE(length, 2); } else { buffer[1] = length; }// 寫入正文 buffer.write(message, index); this.socket.write(buffer);};

最后還要實現一個功能,就是心跳檢測:防止服務端長時間不與客戶端交互而導致客戶端關閉連接,所以每隔十秒都會發送一次ping進行心跳檢測

//每隔10秒進行一次心跳檢測,若連續發出三次心跳卻沒收到響應則關閉socketWebSocket.prototype.checkHeartBeat = function () { var that = this; setTimeout(function () { if (that.state !== "OPEN") return; if (that.pingTimes >= 3) {  that.close("time out");  return; } //記錄心跳次數 that.pingTimes++; that.sendPing(); that.checkHeartBeat(); }, 10000);};WebSocket.prototype.sendPing = function () { this.socket.write(new Buffer(['0x89', '0x0']))};WebSocket.prototype.sendPong = function () { this.socket.write(new Buffer(['0x8A', '0x0']))};

至此,整個websocket的實現就完成了,此demo只是大概實現了一下websocket而已,在安全之類方面肯定還是有很多問題,若是真正生產環境中還是用socket.io這類成熟的插件比較好。不過這還是很值得一學的。

以上內容就是小編給大家分享的淺析nodejs實現Websocket的數據接收與發送的全部內容,希望大家喜歡。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
一区二区三区天堂av| 色妞色视频一区二区三区四区| 久久精品国产99国产精品澳门| 欧美国产高跟鞋裸体秀xxxhd| 伊人伊成久久人综合网站| 久久精品亚洲国产| 亚洲精品720p| 久久这里只有精品视频首页| 欧美日韩一区二区三区在线免费观看| 国产亚洲成av人片在线观看桃| 国产免费一区视频观看免费| 国产日韩精品综合网站| 久久久成人av| 欧美日韩午夜激情| 久久国产精品久久精品| 亚洲激情免费观看| 日韩欧美在线视频免费观看| 91豆花精品一区| 日韩av电影在线免费播放| 91精品综合久久久久久五月天| 日韩精品久久久久久福利| 国产精品视频久久久| 成人网欧美在线视频| 久久久免费精品视频| 欧美第一页在线| 91视频8mav| 欧美视频二区36p| 亚洲国产欧美一区二区丝袜黑人| 久久国产精品影视| 91亚洲国产精品| 欧美视频在线免费看| 欧美日韩国产丝袜美女| 亚洲影院污污.| 日本乱人伦a精品| 国产精品自拍偷拍视频| 啪一啪鲁一鲁2019在线视频| 日韩第一页在线| 亚洲国产美女精品久久久久∴| 久久av在线播放| 日韩有码视频在线| 97在线视频精品| 国产精品久久久久久久久久久久久| 91在线观看免费高清完整版在线观看| 国产视频亚洲精品| 最新国产精品拍自在线播放| 色婷婷综合久久久久中文字幕1| 国语自产偷拍精品视频偷| 国产精品久久久久久久久久小说| 欧美性受xxx| 国内外成人免费激情在线视频网站| 国产69精品久久久久久| 国产成人97精品免费看片| 亚洲福利视频网| 国产精品av电影| 欧美日韩福利电影| 欧美一区在线直播| 亚洲黄一区二区| 久久91亚洲人成电影网站| 日韩中文在线视频| 国产成人在线亚洲欧美| 亚洲a在线观看| 一区二区av在线| 亚洲欧美日本伦理| 欧美孕妇孕交黑巨大网站| 欧美亚洲国产成人精品| 精品视频在线播放免| 欧美网站在线观看| 亚洲女同性videos| 欧美成人免费一级人片100| 欧美日韩国产精品一区| 91精品国产综合久久香蕉922| 最好看的2019的中文字幕视频| 国产精品普通话| 亚洲欧美999| 国产精品久久久久久久电影| 免费不卡在线观看av| 亚洲二区中文字幕| 亚洲欧美另类国产| 在线日韩av观看| 亚洲精品www久久久| 亚洲一区二区三区视频| 色综合天天狠天天透天天伊人| 中文字幕九色91在线| 久久久久久这里只有精品| 亚洲美女精品成人在线视频| 亚洲精品国产欧美| 精品人伦一区二区三区蜜桃免费| 一区二区欧美激情| 精品成人乱色一区二区| 91精品国产91久久久久久| 亚洲激情视频网| 国产国语videosex另类| www.日韩视频| 国产精品美女999| 97在线观看视频| 亚洲精品午夜精品| 成人情趣片在线观看免费| 九色精品美女在线| 国产大片精品免费永久看nba| 亚洲第一国产精品| 欧美电影在线播放| 亚洲欧美日韩中文在线制服| 国产精品美女www爽爽爽视频| 萌白酱国产一区二区| 国产欧美一区二区| 亚洲精品日韩激情在线电影| 精品亚洲国产成av人片传媒| 欧美性少妇18aaaa视频| 久久精品国产成人| 伊人久久五月天| 日韩亚洲欧美中文高清在线| 日韩av电影在线网| 国产精品96久久久久久又黄又硬| 正在播放亚洲1区| 91高清视频免费| 久久频这里精品99香蕉| 国产精品色婷婷视频| 亚洲精品国产精品久久清纯直播| 欧美疯狂性受xxxxx另类| 亚洲精品97久久| 亚洲欧美视频在线| 国产精品久久久久久超碰| 中文字幕亚洲综合久久| 亚洲天堂av综合网| 黄色精品一区二区| 日韩欧美在线一区| 国产一区二区三区在线播放免费观看| 亚洲高清一二三区| 国产精品入口尤物| 精品国产91乱高清在线观看| 国产偷国产偷亚洲清高网站| 国产乱人伦真实精品视频| 亚洲国产精品美女| 91亚洲精品一区二区| 亚洲精品福利在线| 亚洲欧洲国产精品| 欧美福利在线观看| 亚洲精品小视频| 亚洲精品视频二区| 亚洲成色777777女色窝| 琪琪亚洲精品午夜在线| 欧美国产视频日韩| 日韩视频永久免费观看| 欧美日韩国产中文字幕| 亚洲丝袜一区在线| 成人激情黄色网| 成人激情视频免费在线| 国产91网红主播在线观看| 久久这里有精品| 亚洲网站在线看| 亚洲www在线观看| 国产精品久久久久久亚洲调教| 国产一区二区三区中文| 精品久久久久久久中文字幕| 国产精品18久久久久久麻辣| 亚洲毛片在线观看| 日韩一区在线视频| 精品国产精品三级精品av网址| 亚洲精品美女久久| 成人网在线免费看| 欧美久久久精品| 国产精品爽黄69天堂a| 欧美大荫蒂xxx| 久久国产精品久久久久久|