DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> JavaScript基礎知識 >> Websocket協議詳解及簡單實例代碼
Websocket協議詳解及簡單實例代碼
編輯:JavaScript基礎知識     

Websocket協議詳解

關於websocket的協議是用來干嘛的,請參考其他文章。

WebSocket關鍵詞

HTML5協議,實時,全雙工通信,長連接

WebSocket比傳統Http的好處

  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,關閉連接的數據幀。
PING: 9, 心跳檢測。ping
PONG: 10,心跳檢測。pong

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);
};

 感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!

XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved