• 技术文章 >php教程 >PHP开发

    Websocket协议详解及简单实例代码

    黄舟黄舟2016-12-13 10:59:14原创967
    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); 
       };
    声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。
    专题推荐:Websocket
    上一篇:linux yum 命令 下一篇:linux编程之pipe()函数
    20期PHP线上班

    相关文章推荐

    精选22门好课,价值3725元,开通VIP免费学习!• WebService的简介, 原理, 使用• linux awk命令详解• Zend Framework教程之Application用法实例详解• php设计模式导言• jQuery语法总结及注意事项
    1/1

    PHP中文网