1. 程式人生 > >WebSocket(伍) 斷開連線

WebSocket(伍) 斷開連線

WebSocket是很民主的,啥都要協商!建立連線時需要握手協議,連斷開連線都需要雙方共同完成!其實斷開連線直接斷開TCP連線就可以了,但是這有點暴力。文明點的方法是發個請求,讓對方自己斷開。客戶端要主動斷開就必須向伺服器傳送8這個操作碼。
  首先是伺服器主導斷開的情況,最簡單的方法是直接把TCP連線斷開,這裡就不演示了。由於這對客戶端來說是個意外斷開,WebSocket物件採取應急措施也觸發close事件。咱是文明人,當然要做點有紳士風度的事情。於是咱不從伺服器斷開連線,而是向客戶端傳送個請求斷開的操作碼來請求客戶端自己斷開。
  其實就是個操作碼為8的幀。但要注意的是資料部分比較特殊。當然如果嫌麻煩可以不傳,不過要是不傳就和前面的霸王硬上弓一樣無節操了。資料部分的前兩個位元組是狀態碼,之後的部分是關閉連線原因的文字描述,這些東西可以傳到客戶端。
  (encodeDataFrame與decodeDataFrame函式見生成資料幀和解析資料幀)

//客戶端程式
var ws=new WebSocket("ws://127.0.0.1:8000");
ws.onclose=function(e){
  console.log(e);
  ws.close(); //關閉TCP連線
};

========================================================
//伺服器程式
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');
      //構造斷連請求的資料部分,前面留兩位元組存放狀態碼
      var buf=new Buffer('\0\0孩子,地球太危險了,快回火星去吧!');
      buf.writeUInt16BE(1000,0); //在頭兩個位元組寫入一個狀態碼
      //傳送斷連請求
      o.write(encodeDataFrame({FIN:1,Opcode:8,PayloadData:buf}));
    };
  });
}).listen(8000);


客戶端會在onclose的引數中接收到一個這樣的東西,狀態碼和結束原因描述分別在code和reason兩個引數中。規範文件中規定了很多狀態碼的含義,不過這個目前不是強制性的,我就不列舉了。見RFC6455#section-7.4。客戶端在收到伺服器的這個斷連請求後應該呼叫close方法來關閉,否則連線會先入停滯狀態等待客戶端響應。
  伺服器主導斷開的情況就是這樣。下面是客戶端主導斷開的情況。客戶端先要呼叫close方法,這個操作會發送一個斷連請求到伺服器上,伺服器收到這個請求後把TCP連線斷開即可。但是伺服器程式是自己寫的,這個請求也需要自己解析。

//客戶端程式
var ws=new WebSocket("ws://127.0.0.1:8000");
ws.onopen=function(){
  ws.close(); //發起斷連請求
};
ws.onclose=function(e){
  console.log(e);
};

//服務端程式序
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{
      var frame=decodeDataFrame(e);
      console.log(frame);
      if(frame.Opcode==8){
        //這裡也可以傳送個結束包來給客戶端的onclose中帶引數
        //var buf=new Buffer('\0\0孩子,地球太危險了,快回火星去吧!');
        //buf.writeUInt16BE(1000,0);
        //o.write(encodeDataFrame({FIN:1,Opcode:8,PayloadData:buf}));
        o.end(); //斷開連線
      };
    };
  });
}).listen(8000);


總之,客戶端直接呼叫close方法並不會關閉連線,而是傳送請求到伺服器請求對方。伺服器接收請求後可以斷開連線。這會觸發客戶端的close事件。當然,在斷開之前也可以傳送個同樣的斷連請求,幷包含狀態碼和原因描述。