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); //把數據幀輸出到控制台
};
  
這樣服務器接收客戶端穿過了的數據就沒問題了。嘛,這篇文章就只說接收,至於從服務器發送到客戶的情況會有更復雜的情況出現,咱下一篇再說。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM