欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺析nodejs實(shí)現(xiàn)Websocket的數(shù)據(jù)接收與發(fā)送

 更新時(shí)間:2015年11月19日 14:34:21   投稿:mrr  
WebSocket是HTML5開(kāi)始提供的一種瀏覽器與服務(wù)器間進(jìn)行全雙工通訊的網(wǎng)絡(luò)技術(shù),本文給大家介紹nodejs實(shí)現(xiàn)websocket的數(shù)據(jù)庫(kù)接收與發(fā)送,小伙伴們一起學(xué)習(xí)吧

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

WebSocket是一個(gè)通信的協(xié)議,分為服務(wù)器和客戶(hù)端。服務(wù)器放在后臺(tái),保持與客戶(hù)端的長(zhǎng)連接,完成雙方通信的任務(wù)??蛻?hù)端一般都是實(shí)現(xiàn)在支持HTML5瀏覽器核心中,通過(guò)提供JavascriptAPI使用網(wǎng)頁(yè)可以建立websocket連接。

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

客戶(hù)端的代碼就不說(shuō)了,websocket的API還是很簡(jiǎn)單的,就通過(guò)onmessage、onopen、onclose,以及send方法就可以實(shí)現(xiàn)了。
websocket api通過(guò)onmessage、onopen、onclose以及send方法實(shí)現(xiàn)客戶(hù)端的代碼。具體詳情就不多說(shuō)了。

主要說(shuō)服務(wù)端的代碼:

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

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事件其實(shí)是http這個(gè)模塊的封裝,再往底層就是net模塊的實(shí)現(xiàn),其實(shí)都差不多,如果直接用net模塊來(lái)實(shí)現(xiàn)的話(huà),就是監(jiān)聽(tīng)net.createServer返回的server對(duì)象的data事件,接收到的第一份數(shù)據(jù)就是客戶(hù)端發(fā)來(lái)的升級(jí)請(qǐng)求報(bào)文。

上面那段代碼就完成了websocket的握手,然后就可以開(kāi)始數(shù)據(jù)傳輸了。

看數(shù)據(jù)傳輸之前,先看看websocket數(shù)據(jù)幀的定義(因?yàn)橛X(jué)得深入淺出nodejs里的幀定義圖最容易理解,所以就貼這張了):

 

上面的圖中,每一列就是一個(gè)字節(jié),一個(gè)字節(jié)總共是8位,每一位就是一個(gè)二進(jìn)制數(shù),不同位的值會(huì)對(duì)應(yīng)不同的意義。

fin:指示這個(gè)是消息的最后片段。第一個(gè)片段可能也是最后的片段。如果為1即為最后片段,(其實(shí)這個(gè)位的用途我個(gè)人有點(diǎn)疑惑,按照書(shū)上以及網(wǎng)上查的資料,當(dāng)數(shù)據(jù)被分片的時(shí)候,不同片應(yīng)該都會(huì)有fin位,會(huì)根據(jù)fin為是不是0來(lái)判斷是否為最后一幀,但是實(shí)際實(shí)現(xiàn)中卻發(fā)現(xiàn),當(dāng)數(shù)據(jù)比較大需要分片時(shí),服務(wù)端收到的數(shù)據(jù)就只有第一幀是有fin位為1,其他幀則整個(gè)幀都是數(shù)據(jù)段,也就是說(shuō),感覺(jué)這個(gè)fin位似乎用不上,至少我自己寫(xiě)的demo中是通過(guò)數(shù)據(jù)長(zhǎng)度來(lái)判斷是否到了最后一幀,完全沒(méi)用到這個(gè)fin位是否為1來(lái)判斷)

rsv1、rsv2、rsv3:各占一個(gè)位,用于擴(kuò)展協(xié)商,基本上不怎么需要理,一般都是0

opcode:占四個(gè)位,可以表示0~15的十進(jìn)制,0表示為附加數(shù)據(jù)幀,1表示為文本數(shù)據(jù)幀,2表示二進(jìn)制數(shù)據(jù)幀,8表示發(fā)送一個(gè)連接關(guān)閉的數(shù)據(jù)幀,9表示ping,10表示pong,ping和pong都是用于心跳檢測(cè),當(dāng)一端發(fā)送ping時(shí),另一端必須響應(yīng)pong表示自己仍處于響應(yīng)狀態(tài)。

masked:占一個(gè)位,表示是否進(jìn)行掩碼處理,客戶(hù)端發(fā)送給服務(wù)端時(shí)為1,服務(wù)端發(fā)送給客戶(hù)端時(shí)為0

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

masking key:當(dāng)masked為1的時(shí)候才存在,用于對(duì)我們需要的數(shù)據(jù)進(jìn)行解密。

payload data:我們需要的數(shù)據(jù),如果masked為1,該數(shù)據(jù)會(huì)被加密,要通過(guò)masking key進(jìn)行異或運(yùn)算解密才能獲取到真實(shí)數(shù)據(jù)。

幀定義解釋完了,就可以根據(jù)數(shù)據(jù)來(lái)進(jìn)行解析了,當(dāng)有data過(guò)來(lái)的時(shí)候,先獲取需要的數(shù)據(jù)信息,下面這段代碼將獲取到數(shù)據(jù)在data里的位置,以及數(shù)據(jù)長(zhǎng)度,masking key以及opcode:

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

代碼中均有注釋?zhuān)斫馄饋?lái)應(yīng)該不難,直接看下一步,獲取到數(shù)據(jù)信息后,就要對(duì)數(shù)據(jù)進(jìn)行實(shí)際解析了:

經(jīng)過(guò)上面handleDataStat方法的處理,stat中已經(jīng)有了data的相關(guān)數(shù)據(jù),先判斷opcode,如果為9說(shuō)明是客戶(hù)端發(fā)起的ping心跳檢測(cè),直接返回pong響應(yīng),如果為10則為服務(wù)端發(fā)起的心跳檢測(cè)。如果有masking key,則遍歷數(shù)據(jù)段,對(duì)每個(gè)字節(jié)都與masking key的字節(jié)進(jìn)行異或運(yùn)算(網(wǎng)上看到一個(gè)說(shuō)法很形象:就是輪流發(fā)生X關(guān)系),^符號(hào)就是進(jìn)行異或運(yùn)算啦。如果沒(méi)有masking key則直接通過(guò)slice方法把數(shù)據(jù)截取下來(lái)。

獲取到數(shù)據(jù)后,放進(jìn)datas里保存,因?yàn)橛锌赡軘?shù)據(jù)被分片了,所以再將stat里的長(zhǎng)度減去當(dāng)前數(shù)據(jù)長(zhǎng)度,只有當(dāng)stat里的長(zhǎng)度為0的時(shí)候,說(shuō)明當(dāng)前幀為最后一幀,然后通過(guò)Buffer.concat將所有數(shù)據(jù)合并,此時(shí)再判斷一下opcode,如果opcode為8,則說(shuō)明客戶(hù)端發(fā)起了一個(gè)關(guān)閉請(qǐng)求,而我們獲取到的數(shù)據(jù)則是關(guān)閉原因。如果不為8,則這數(shù)據(jù)就是我們需要的數(shù)據(jù)。然后再將stat重置為null,datas數(shù)組置空即可。至此,我們的數(shù)據(jù)解析就完成了。

WebSocket.prototype.dataHandle = function (data) {
 this.handleDataStat(data);
 var stat;
 if (!(stat = this.stat)) return;
 //如果opcode為9,則發(fā)送pong響應(yīng),如果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++) {
  //對(duì)每個(gè)字節(jié)進(jìn)行異或運(yùn)算,masked是4個(gè)字節(jié),所以%4,借此循環(huán)
  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);
 //當(dāng)長(zhǎng)度為0,說(shuō)明當(dāng)前幀為最后幀
 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();
 }
};

完成了客戶(hù)端發(fā)來(lái)的數(shù)據(jù)解析,還需要一個(gè)服務(wù)端發(fā)數(shù)據(jù)至客戶(hù)端的方法,也就是按照上面所說(shuō)的幀定義來(lái)組裝數(shù)據(jù)并且發(fā)送出去。下面的代碼中基本上每一行都有注釋?zhuān)瑧?yīng)該還是比較容易理解的。

//數(shù)據(jù)發(fā)送
WebSocket.prototype.send = function (message) {
 if(this.state !== "OPEN") return;
 message = String(message);
 var length = Buffer.byteLength(message);
// 數(shù)據(jù)的起始位置,如果數(shù)據(jù)長(zhǎng)度16位也無(wú)法描述,則用64位,即8字節(jié),如果16位能描述則用2字節(jié),否則用第二個(gè)字節(jié)描述
 var index = 2 + (length > 65535 ? 8 : (length > 125 ? 2 : 0));
// 定義buffer,長(zhǎng)度為描述字節(jié)長(zhǎng)度 + message長(zhǎng)度
 var buffer = new Buffer(index + length);
// 第一個(gè)字節(jié),fin位為1,opcode為1
 buffer[0] = 129;
// 因?yàn)槭怯煞?wù)端發(fā)至客戶(hù)端,所以無(wú)需masked掩碼
 if (length > 65535) {
 buffer[1] = 127;
// 長(zhǎng)度超過(guò)65535的則由8個(gè)字節(jié)表示,因?yàn)?個(gè)字節(jié)能表達(dá)的長(zhǎng)度為4294967295,已經(jīng)完全夠用,因此直接將前面4個(gè)字節(jié)置0
 buffer.writeUInt32BE(0, 2);
 buffer.writeUInt32BE(length, 6);
 } else if (length > 125) {
 buffer[1] = 126;
// 長(zhǎng)度超過(guò)125的話(huà)就由2個(gè)字節(jié)表示
 buffer.writeUInt16BE(length, 2);
 } else {
 buffer[1] = length;
 }
// 寫(xiě)入正文
 buffer.write(message, index);
 this.socket.write(buffer);
};

最后還要實(shí)現(xiàn)一個(gè)功能,就是心跳檢測(cè):防止服務(wù)端長(zhǎng)時(shí)間不與客戶(hù)端交互而導(dǎo)致客戶(hù)端關(guān)閉連接,所以每隔十秒都會(huì)發(fā)送一次ping進(jìn)行心跳檢測(cè)

//每隔10秒進(jìn)行一次心跳檢測(cè),若連續(xù)發(fā)出三次心跳卻沒(méi)收到響應(yīng)則關(guān)閉socket
WebSocket.prototype.checkHeartBeat = function () {
 var that = this;
 setTimeout(function () {
 if (that.state !== "OPEN") return;
 if (that.pingTimes >= 3) {
  that.close("time out");
  return;
 }
 //記錄心跳次數(shù)
 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']))
};

至此,整個(gè)websocket的實(shí)現(xiàn)就完成了,此demo只是大概實(shí)現(xiàn)了一下websocket而已,在安全之類(lèi)方面肯定還是有很多問(wèn)題,若是真正生產(chǎn)環(huán)境中還是用socket.io這類(lèi)成熟的插件比較好。不過(guò)這還是很值得一學(xué)的。

以上內(nèi)容就是小編給大家分享的淺析nodejs實(shí)現(xiàn)Websocket的數(shù)據(jù)接收與發(fā)送的全部?jī)?nèi)容,希望大家喜歡。

相關(guān)文章

  • NodeJS爬蟲(chóng)實(shí)例之糗事百科

    NodeJS爬蟲(chóng)實(shí)例之糗事百科

    本篇文章主要給大家講解了一下用NodeJS學(xué)習(xí)爬蟲(chóng),并通過(guò)爬糗事百科來(lái)講解用法和效果,一起學(xué)習(xí)下吧。
    2017-12-12
  • Node.js讀取文件操作教程示例

    Node.js讀取文件操作教程示例

    這篇文章主要為大家介紹了Node.js讀取文件教程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-11-11
  • Node.js獲取本機(jī)Mac地址的兩種方案

    Node.js獲取本機(jī)Mac地址的兩種方案

    有時(shí)候我們的項(xiàng)目中可能會(huì)有日志記錄的功能或者其他需要ip的功能,于是這時(shí)我們需要獲取用戶(hù)的ip地址或mac地址,下面這篇文章主要給大家介紹了關(guān)于Node.js獲取本機(jī)Mac地址的兩種方案,需要的朋友可以參考下
    2022-09-09
  • 配置nodejs環(huán)境的方法

    配置nodejs環(huán)境的方法

    本篇文章主要介紹了配置nodejs環(huán)境變量的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-05-05
  • koa2 用戶(hù)注冊(cè)、登錄校驗(yàn)與加鹽加密的實(shí)現(xiàn)方法

    koa2 用戶(hù)注冊(cè)、登錄校驗(yàn)與加鹽加密的實(shí)現(xiàn)方法

    這篇文章主要介紹了koa2 用戶(hù)注冊(cè)、登錄校驗(yàn)與加鹽加密的實(shí)現(xiàn)方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下
    2019-07-07
  • Node.js API詳解之 string_decoder用法實(shí)例分析

    Node.js API詳解之 string_decoder用法實(shí)例分析

    這篇文章主要介紹了Node.js API詳解之 string_decoder用法,結(jié)合實(shí)例形式分析了Node.js API中string_decoder的功能、用法及操作注意事項(xiàng),需要的朋友可以參考下
    2020-04-04
  • 詳解NodeJs支付寶移動(dòng)支付簽名及驗(yàn)簽

    詳解NodeJs支付寶移動(dòng)支付簽名及驗(yàn)簽

    本文主要介紹了NodeJs支付寶移動(dòng)支付簽名及驗(yàn)簽的方法,具有一定的參考價(jià)值,下面跟著小編一起來(lái)看下吧
    2017-01-01
  • socket.io學(xué)習(xí)教程之深入學(xué)習(xí)篇(三)

    socket.io學(xué)習(xí)教程之深入學(xué)習(xí)篇(三)

    這篇文章更加深入的給大家介紹了socket.io的相關(guān)資料,之前已經(jīng)介紹了socket.io的基本教程和應(yīng)用,本文更為深入的來(lái)介紹下socket.io的使用,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。
    2017-04-04
  • node.js中的fs.createReadStream方法使用說(shuō)明

    node.js中的fs.createReadStream方法使用說(shuō)明

    這篇文章主要介紹了node.js中的fs.createReadStream方法使用說(shuō)明,本文介紹了fs.createReadStream方法說(shuō)明、語(yǔ)法、接收參數(shù)、使用實(shí)例和實(shí)現(xiàn)源碼,需要的朋友可以參考下
    2014-12-12
  • express提供http服務(wù)功能實(shí)現(xiàn)示例

    express提供http服務(wù)功能實(shí)現(xiàn)示例

    這篇文章主要為大家介紹了express提供http服務(wù)功能實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10

最新評(píng)論