zoukankan      html  css  js  c++  java
  • WebSocket之解析数据帧

    知道了怎么握手只是让客户端和服务器建立连接而已,WebSocket真正麻烦的地方是在数据的传输上!为了环保,它使用了特定格式的数据帧,这个数据帧需要自己去解析(当然也有别人编写好的库可以用)。虽然官方文档描述的很详细,但是看起来还是蛋疼。  
      当客户端向服务器发送一个数据时服务器收到一个数据帧,比如下面的程序   //客户端程序
    var ws=new WebSocket("ws://127.0.0.1:8000");
    ws.onopen=function(e){
      ws.send("次碳酸钴"); //发送数据
    };
       //服务器程序
    var crypto=require('crypto');
    var WS='258EAFA5-E914-47DA-95CA-C5AB0DC85B11';

    require('net').createServer(function(o){
      var key;
      o.on('data',function(e){
        if(!key){ //握手
          key=e.toString().match(/Sec-WebSocket-Key: (.+)/)[1];
          key=crypto.createHash('sha1').update(key+WS).digest('base64');
          o.write('HTTP/1.1 101 Switching Protocols ');
          o.write('Upgrade: websocket ');
          o.write('Connection: Upgrade ');
          o.write('Sec-WebSocket-Accept: '+key+' ');
          o.write(' ');
        }else onmessage(e); //接收并交给处理函数
      });
    }).listen(8000);

    function onmessage(e){
      console.log(e); //把数据输出到控制台
    };
       这里是直接把接收到的数据输出了,得到这样一个东西  

      这就是一个完整的数据帧,直接的16进制数据我们当然无法直接阅读,需要按照数据帧的格式把它里面的数据取出来才行。对于这个数据帧,官方文档提供了一个结构图     0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     +-+-+-+-+-------+-+-------------+-------------------------------+
     |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
     |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
     |N|V|V|V|       |S|             |   (if payload len==126/127)   |
     | |1|2|3|       |K|             |                               |
     +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
     |     Extended payload length continued, if payload len == 127  |
     + - - - - - - - - - - - - - - - +-------------------------------+
     |                               |Masking-key, if MASK set to 1  |
     +-------------------------------+-------------------------------+
     | Masking-key (continued)       |          Payload Data         |
     +-------------------------------- - - - - - - - - - - - - - - - +
     :                     Payload Data continued ...                :
     + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
     |                     Payload Data continued ...                |
     +---------------------------------------------------------------+
       光拿出这个实在很难看懂,顶部数字用十进制而不是八进制太让人蛋疼了。当然官方文档在后面的描述中也有详细介绍,看完后再回头来看图表才能看明白。其实WebSocket目前还不太完善,很多实验性的东西,所以完全按照官方文档来理解是蛋疼的。这里就说我自己的理解。  
      现在再看左上角上面的图标,左上角的四个小列,也就是4位,第一位是FIN,后面三位是RSV1到3。官方文档上说RSV是预留的空间,正常为0,这就意味着,正常情况下他们可以当做0填充,那么前4位只有第一位的FIN需要设置,FIN表示帧结束,由于这篇中它不重要就不特别介绍了。接着后面的四位是储存opcode的值,这个opcode是标识数据类型的。这样数据的第一个字节我们就能理解它的含义了,看上面16进制的数据的第一个字节81换成二进制是1000001,第一个1是FIN的值,最后一个1是opcode的值。  
      接着是第二个字节的数据,它由1位的MASK和7位的PayloadLen组成,MASK标识这个数据帧的数据是否使用掩码,PayloadLen表示数据部分的长度。但是PayloadLen只有7位,换成无符号整型的话只有0到127的取值,这么小的数值当然无法描述较大的数据,因此规定当数据长度小于或等于125时候它才作为数据长度的描述,如果这个值为126,则时候后面的两个字节来储存储存数据长度,如果为127则用后面四个字节来储存数据长度。所以上面的图片第一行的最右侧那块和第二行看起来有些颓然。从我们的示例数据来看,第二个字节的8C中80是最高位为1,这意味着MASK为1,后面的C表示这个数据部分有12个字节。  
      再接着是上面图表中的MaskingKey,它占四个字节,储存掩码的实体部分。但是只有在前面的MASK被设置为1时候才存在这个数据,否则不使用掩码也就没有这个数据了。看我们的示例数据,由于前面的MASK为1,所以3到6字节的“79 77 3d 41”是数据的掩码实体。  
      最后是数据部分,如果掩码存在,那么所有数据都需要与掩码做一次异或运算,四个字节的掩码与所有数据字节轮流发生性关系。如果不存在掩码,那么后面的数据就可以直接使用。  
      这样数据帧就解析完了。下面是我写的数据帧解析的程序,请不要吐槽代码没优化   function decodeDataFrame(e){
      var i=0,j,s,frame={
        //解析前两个字节的基本数据
        FIN:e[i]>>7,Opcode:e[i++]&15,Mask:e[i]>>7,
        PayloadLength:e[i++]&0x7F
      };
      //处理特殊长度126和127
      if(frame.PayloadLength==126)
        frame.length=(e[i++]<<8)+e[i++];
      if(frame.PayloadLength==127)
        frame.length=(e[i++]<<24)+(e[i++]<<16)+(e[i++]<<8)+e[i++];
      //判断是否使用掩码
      if(frame.Mask){
        //获取掩码实体
        frame.MaskingKey=[e[i++],e[i++],e[i++],e[i++]];
        //对数据和掩码做异或运算
        for(j=0,s=[];j<frame.PayloadLength;j++)
          s.push(e[i+j]^frame.MaskingKey[j%4]);
      }else s=e.slice(i,frame.PayloadLength); //否则直接使用数据
      //数组转换成缓冲区来使用
      s=new Buffer(s);
      //如果有必要则把缓冲区转换成字符串来使用
      if(frame.Opcode==1)s=s.toString();
      //设置上数据部分
      frame.PayloadData=s;
      //返回数据帧
      return frame;
    };
       既然有了解析程序,那么我们就可以把上面实例服务器端的onmessage方法修改一下   function onmessage(e){
      e=decodeDataFrame(e); //解析数据帧
      console.log(e); //把数据帧输出到控制台
    };
      
    这样服务器接收客户端穿过了的数据就没问题了。嘛,这篇文章就只说接收,至于从服务器发送到客户的情况会有更复杂的情况出现,咱下一篇再说。

  • 相关阅读:
    poj2976 Dropping tests (01分数规划)
    bzoj5281/luogu4377 Talent Show (01分数规划+背包dp)
    bzoj5280/luogu4376 MilkingOrder (二分答案+拓扑序)
    bzoj1492/luogu4027 货币兑换 (斜率优化+cdq分治)
    [模板]树状数组
    匿名函数 python
    yield解析你要知道的源自IBM
    stackoverflow yield 帖子
    pandas 生成器,生成大数据
    pd.contact,dataframe 拼接
  • 原文地址:https://www.cnblogs.com/Charles-xu/p/4980909.html
Copyright © 2011-2022 走看看