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事件。當然,在斷開之前也可以傳送個同樣的斷連請求,幷包含狀態碼和原因描述。