websocket的原理和應用
在繼續(xù)本文之前,讓我們了解下websocket的原理:
websocket通信協(xié)議實現(xiàn)的是基于瀏覽器的原生socket,這樣原先只有在c/s模式下的大量開發(fā)模式都可以搬到web上來了,基本就是通過瀏覽器的支持在web上實現(xiàn)了與服務器端的socket通信。
WebSocket沒有試圖在HTTP之上模擬server推送,而是直接在TCP之上定義了幀協(xié)議,因此WebSocket能夠支持雙向的通信。
首先來介紹下websocket客戶端與服務端建立連接的過程:
先用js創(chuàng)建一個WebSocket實例,使用ws協(xié)議建立服務器連接,ws://www.cnodejs.org:8088
ws開頭是普通的websocket連接,wss是安全的websocket連接,類似于https。
客戶端與服務端建立握手,發(fā)送如下信息:
GET /echo HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host:http://www.cnodejs.org:8088
Origin:http://www.cnodejs.com
服務端會發(fā)回如下:
HTTP/1.1 101 Web Socket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
WebSocket-Origin:http://www.cnodejs.org
WebSocket-Location: ws://www.cnodejs.org:8088/echo
具體的ws協(xié)議,可以參考:http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
我們在開發(fā)過程中不需要考慮協(xié)議的細節(jié),因為websocket API已經(jīng)幫我們封裝好了。
需要注意的是所有的通信數(shù)據(jù)都是以”\x00″開頭以”\xFF”結尾的,并且都是UTF-8編碼的。
這個過程類似于http的建立連接過程,不同的是,建立連接之后,接下來客戶端和服務端的任何交互都需要再有這個動作??蛻舳送ㄟ^websocket API提供的如下4個事件進行編程:
onopen 建立連接后觸發(fā)
onmessage 收到消息后觸發(fā)
onerror 發(fā)生錯誤時觸發(fā)
onclose 關閉連接時觸發(fā)
讓我們?nèi)媪私庖幌聎ebsocket API,他其實非常簡單,下面是所有的API:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23[Constructor(in DOMString url, in optional DOMString protocols)]
[Constructor(in DOMString url, in optional DOMString[] protocols)]
interface WebSocket {
readonly attribute DOMString url;
// ready state
const unsigned short CONNECTING = 0;
const unsigned short OPEN = 1;
const unsigned short CLOSING = 2;
const unsigned short CLOSED = 3;
readonly attribute unsigned short readyState;
readonly attribute unsigned long bufferedAmount;
// networking
attribute Function onopen;
attribute Function onmessage;
attribute Function onerror;
attribute Function onclose;
readonly attribute DOMString protocol;
void send(in DOMString data);
void close();
};
WebSocket implements EventTarget;
詳細的websocket API,可以參考此文:http://dev.w3.org/html5/websockets/
node.js與websocket的結合
終于講到了正題了,node.js如何與websocket結合,websocket API是基于事件的,他是對于客戶端而言,而對于服務端來說,如何來處理呢?其實可以簡單的理解為實現(xiàn)websocket協(xié)議的socket server開發(fā)。
node.js天生就是一個高效的服務端語言,可以直接使用 javascript直接來處理來自客戶端的請求,這樣如果服務端這邊需要大量的業(yè)務邏輯開發(fā),則可以直接使用node開發(fā)。通過node和 websocket的結合可以開發(fā)出很多實時性要求很高的web應用,如游戲、直播、股票、監(jiān)控、IM等等。
而node.js如何實現(xiàn)websocket的支持,已經(jīng)有一個比較成熟的開源系統(tǒng)node-websocket-server:https://github.com/miksago/node-websocket-server,讓我們來探究一二:
其實原理也是很簡單就是用node實現(xiàn)了websocket draft-76的協(xié)議,同時他對外提供了api,可以方便其他應用程序簡化編程。
它繼承了node的http.Server的事件和方法,這樣它簡化了服務端的編程,同時可以處理http的請求。
為了實現(xiàn)連接之間的通信和消息的廣播,它實現(xiàn)了一個manager類,給每一個連接創(chuàng)建一個id,然后在內(nèi)存中維護一個連接鏈表,并提供了上線和下線的自動管理。
它還提供對以下幾個事件的接口:
listening 當服務器準備好接受客戶端請求時
request 當一個http 請求發(fā)生時觸發(fā)
stream
close
clientError
error
讓我們看看一個node-websocket-server提供的一個server的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27var sys = require("sys")
, ws = require('../lib/ws/server');
var server = ws.createServer({debug: true});
// Handle WebSocket Requests
server.addListener("connection", function(conn){
conn.send("Connection: "+conn.id);
conn.addListener("message", function(message){
conn.broadcast("<"+conn.id+"> "+message);
if(message == "error"){
conn.emit("error", "test");
}
});
});
server.addListener("error", function(){
console.log(Array.prototype.join.call(arguments, ", "));
});
server.addListener("disconnected", function(conn){
server.broadcast("<"+conn.id+"> disconnected");
});
server.listen(8000);
這個例子非常的簡單,可以看到對于websocket的server端開發(fā),我們已經(jīng)不需要考慮 websocket協(xié)議的實現(xiàn),他幾乎有著和客戶端瀏覽器上websocket API一樣的事件,只有對連接、斷開連接、消息、錯誤等事件進行處理,這樣應用的開發(fā)就非常的靈活了。
實例:用websocket和node.js搭建實時監(jiān)控系統(tǒng)
通過websocket打通了瀏覽器和服務端之后,我們就可以嘗試搭建一個實際的應用,這里以實時監(jiān)控系統(tǒng)為例。
直接與linux自身監(jiān)控工具的結合,將監(jiān)控結果通過websocket直接更到網(wǎng)頁上,由于建立了socket長連接,綁定iostat的標準輸 出的事件,做到了真正的實時。同時可以支持對監(jiān)控結果的討論,增加了一個簡單的chat,基于事件的通訊中,chat和監(jiān)控同時發(fā)送完全不受影響,所以還 可以把更多的事件加入進來。
讓我們來看看這個過程:
首先是用node.js捕獲iostat的輸出:
1
2
3
4
5
6var sys = require("sys")
, ws = require('../lib/ws/server');
var sys = require('sys');
var spawn = require('child_process').spawn;
var mon = spawn("iostat",["-I","5"]);
spawn可以根據(jù)參數(shù)啟動一個進程,同時可以對stdout, stderr, exit code進行捕獲,當這些事件觸發(fā)時,可以綁定我們的函數(shù),同時捕獲其輸出。
這里是iostat的標準輸出:
disk0 cpu load average
KB/t tps MB/s us sy id 1m 5m 15m
14.64 4 0.06 7 5 88 0.76 0.95 0.90
我們捕獲他的輸出,將其發(fā)送到客戶端去:
1
2
3
4
5mon.stdout.on('data',function(data) {
data = format_string(data);
sys.puts(data);
conn.send("#mon:"+data+"");
});
客戶端也就是瀏覽器,在收到消息后,對其進行簡單的字符串處理,然后就可以直接在網(wǎng)頁中輸出了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15w.onmessage = function(e) {
var msg = e.data;
if(msg.match(/#mon:/)) {
var monarr = msg.split(":")[1].split(" ");
var body = "";
for(var item in monarr) {
"+monarr[item]+"
}
$("#iobody").html(body);
//log(monarr[0]);
}
else
log(e.data);
}
這里自定義了一個#mon的簡單協(xié)議,這樣可以對更多類型的輸出分開處理。
服務端和客戶端總共100多行的代碼,就已經(jīng)實現(xiàn)了一個實時服務器性能監(jiān)控系統(tǒng)。
全部代碼下載地址:http://cnodejs.googlecode.com/svn/trunk/monsocket/examples/
(注:本程序僅在mac osx下測試通過)
如果加上RGraph(基于html5),則可以打造更加精美的實時展現(xiàn): ?http://www.rgraph.net/docs/dynamic.html
總結
這篇文章適合node.js的初學者或者對于websocket不夠了解的人,總結起來,就是以下幾個點:
使用websocket API可以開發(fā)web應用實時
websocket api和 node.js可以很完美的配合
node-websocket-server 封裝了websocket協(xié)議,使服務端進行websocket的開發(fā),非常的簡單
node的易用性,使其在服務端略加編程,即可以打造一個完美的后臺服務
node的事件驅(qū)動的機制保證了系統(tǒng)的高效,可以使用EventEmitter定義自己的事件觸發(fā)
對于命令行輸出可以使用spawn來捕獲,通過在web應用中充分利用linux的各種系統(tǒng)工具