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\r\n');
      o.write('Upgrade: websocket\r\n');
      o.write('Connection: Upgrade\r\n');
      o.write('Sec-WebSocket-Accept: '+key+'\r\n');
      o.write('\r\n');
    }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); //把數據幀輸出到控制檯
};
  
這樣服務器接收客戶端穿過了的數據就沒問題了。嘛,這篇文章就只說接收,至於從服務器發送到客戶的狀況會有更復雜的狀況出現,咱下一篇再說。web

相關文章
相關標籤/搜索