以前曾經寫過一個符合xmpp協議的Web IM,但使用的是JSJaC,後臺用的也是與之配套的jabber client,發現nodejs的事件模式更適合做爲Web IM的客戶端。html
而傳統的ajax輪詢機制也遲早被全雙工websocket所取代,因此就打算在個人畢業設計的Web IM平臺中使用websocket。html5
在這裏調研一下並做出了一個簡單的版聊demo,這裏講一下這個簡單demo的實現方式node
什麼是WebSocket?git
WebSocket的協議 目前尚未仔細去研讀,有時間研讀一下github
根據WebSocket.org上的定義:web
The WebSocket specification—developed as part of the HTML5 initiative— introduced the WebSocket JavaScript interface, which defines a full-duplex single socket connection over which messages can be sent between client and server. The WebSocket standard simplifies much of the complexity around bi-directional web communication and connection management.ajax
如上所述websocket定義了一個瀏覽器和服務器之間的全雙工的單一的socket鏈接。chrome
WebSocket的API?express
W3C的WS的API ,定義了具體的WS的接口,而通常只要注意怎麼使用就好了,能夠清楚地看到WS客戶端的幾個事件:npm
nodejs有不少websocket的三方庫,都很實用,在stackoverflow上有人問過具體應該使用哪一個庫,而回答者給與了較爲全面的解答
這裏面對各個websocket庫進行了一個對比,能夠根據本身的須要選擇。
其中能夠注意一下socket.io,它對不一樣的瀏覽器有比較好的支持,在不支持websocket的時候能夠轉變成ajax的輪詢等其餘的替換,瀏覽器的支持也至關不錯。同時還能和目前比較流行的node的web框架express相結合,其文檔的例子寫的很好。
因爲我只是想搭建一個簡單快捷的WS服務器,因此選用了號稱probably the fastest WebSocket library for node.js的ws
在項目中使用npm安裝:
npm install ws
若是須要使用命令行的簡易WS客戶端,能夠:
npm install ws -g
建立一個WS服務器:
var WebSocketServer = require('ws').Server, wss = new WebSocketServer({ port: process.env.WSPORT || 3001 });
這樣wss就成爲了一個監聽3001端口的WS服務器,咱們須要爲WS服務器建立WS客戶端鏈接時候的事件:
wss.on('connection', function(ws) {});
這樣,在有WS客戶端鏈接咱們的WS服務器時就會觸發這個事件,但鏈接以後我麼還須要傳遞信息,因此須要豐富這個事件的回調函數。
回調函數有一個參數ws,這個ws掌管着和WS客戶端的鏈接,其事件也和WS客戶端相同,不過不須要onopen。須要綁定的還有message,close:
wss.on('connection', function(ws) { ws.on('message', function(data) { }); ws.on('close', function() { }); });
message事件在WS客戶端給這個WS服務器發數據時調用,data就是這個數據,通常爲string類型
close事件在WS客服端給這個WS服務器發送關閉請求時調用
一個簡單的聊天室,須要在一個用戶加入時告訴其餘全部用戶有新用戶加入,也就是須要一個廣播的方法,咱們能夠根據ws的示例來定義廣播方法:
wss.broadcast = function(data) { for (var i in this.clients) this.clients[i].send(JSON.stringify(data)); };
這裏能夠看到wss的clients存放了全部與wss相連的WS客戶端鏈接。
在一個WS客戶端鏈接了WS服務器,咱們須要把現有的全部房間內用戶的信息給新進入房間的用戶,並告訴全部房間內的用戶有新用戶加入,默認新進入房間的用戶叫「遊客」,修改代碼:
wss.on('connection', function(ws) { ws.on('message', function(data) { }); ws.on('close', function() { }); //給每一個用戶一個單獨的id ws.uid = uuid.v4(); //新進入房間的用戶的暱稱 ws.nick = "遊客"; //把目前全部房間內人員的信息發給新用戶 for (var i in this.clients) { ws.send(JSON.stringify({ nick: this.clients[i].nick, uid: this.clients[i].uid, type: "join" })); } //將新加入用戶的信息告訴全部房間內的用戶 wss.broadcast({ nick: ws.nick, uid: ws.uid, type: "join" }); });
這樣新用戶加入時的服務器端處理就完成了
在一個用戶向服務器發送信息時,須要廣播這條信息,同時也要指出發送人的信息,因此修改代碼:
ws.on('message', function(data) { wss.broadcast({ nick: ws.nick, uid: ws.uid, time: moment(data.time).format("HH:mm:ss"), message: data.message, type: "message" }); });
在一個WS客戶端向WS服務器發送關閉請求時,須要通知其餘全部房間內的用戶,因此修改代碼:
ws.on('close', function() { wss.broadcast({ nick: ws.nick, uid: ws.uid, type: "exit" }); });
在一個用戶要修改本身的暱稱,WS客戶端須要向WS服務器發送申請,因此修改代碼:
ws.on('message', function(data) { //解析數據 data = JSON.parse(data); //若爲message,則爲WS客戶端向WS服務器發送信息,進行廣播 if (data.type === "message") { wss.broadcast({ nick: ws.nick, uid: ws.uid, time: moment(data.time).format("HH:mm:ss"), message: data.message, type: "message" }); //若爲nickname,則爲WS客戶端向WS服務器發送暱稱修改請求,則修改用戶暱稱,並進行廣播 } else if (data.type === "nickname") { wss.broadcast({ oldnick: ws.nick, nick: data.nick, uid: ws.uid, type: "nickname" }); ws.nick = data.nick; } });
這樣一個簡單的聊天室的WS服務器就完成了,全部代碼以下:
var WebSocketServer = require('ws').Server, wss = new WebSocketServer({ port: process.env.WSPORT || 3001 }); wss.broadcast = function(data) { for (var i in this.clients) this.clients[i].send(JSON.stringify(data)); }; wss.on('connection', function(ws) { ws.on('message', function(data) { data = JSON.parse(data); if (data.type === "message") { wss.broadcast({ nick: ws.nick, uid: ws.uid, time: moment(data.time).format("HH:mm:ss"), message: data.message, type: "message" }); } else if (data.type === "nickname") { wss.broadcast({ oldnick: ws.nick, nick: data.nick, uid: ws.uid, type: "nickname" }); ws.nick = data.nick; } }); ws.on('close', function() { wss.broadcast({ nick: ws.nick, uid: ws.uid, type: "exit" }); }); ws.uid = uuid.v4(); ws.nick = "遊客"; for (var i in this.clients) { ws.send(JSON.stringify({ nick: this.clients[i].nick, uid: this.clients[i].uid, type: "join" })); } wss.broadcast({ nick: ws.nick, uid: ws.uid, type: "join" }); });
在瀏覽器中,則須要創建一個WS客戶端
//建立一個WS客戶端 var ws = new WebSocket("ws://localhost:3001");
給它按照WebSocket的API綁定事件:
//WS客戶端鏈接到WS服務器後, 設定默認暱稱,並加入版聊 ws.onopen = function(event) { $("#nickname").val("遊客"); $logs.append("<div class='alert alert-success'>您已加入版聊</div>"); }; //若是WS服務器關閉,給予斷開提示 ws.onclose = function(event) { $logs.append("<div class='alert alert-danger'>您已斷開版聊</div>"); }; //若是WS服務器向這個WS客戶端發送信息: ws.onmessage = function(event) { var data = JSON.parse(event.data); //發送文本信息, 顯示到頁面上 if (data.type === "message") { $chat.append("<p>" + data.nick + "(" + data.time + "): " + data.message + "</p>"); //有新用戶加入, 顯示用戶加入通知, 並修改當前用戶列表 } else if (data.type === "join") { if ($("p[uid='" + data.uid + "']", $users).length === 0) { $users.append("<p uid='" + data.uid + "'>" + data.nick + "</p>"); $logs.append("<div class='alert alert-warning'>" + data.nick + "加入了版聊</div>"); } //有用戶離開, 顯示用戶離開通知, 並修改當前用戶列表 } else if (data.type === "exit") { $("p[uid='" + data.uid + "']", $users).remove(); $logs.append("<div class='alert alert-warning'>" + data.nick + "離開了版聊</div>"); //有用戶修改暱稱, 顯示用戶修改暱稱, 修改用戶列表 } else if (data.type === "nickname") { $("#nickname").val(data.nick); $("p[uid='" + data.uid + "']", $users).text(data.nick); $logs.append("<div class='alert alert-warning'>" + data.oldnick + " 修改暱稱爲 " + data.nick + "</div>"); } };
具體須要發送信息時,使用ws.send發送:
//從WS客戶端向WS服務器發送信息數據 ws.send(JSON.stringify({ time: new Date().getTime(), message: message, type: "message" }));
須要發送修改暱稱請求時,採用一樣的方式:
//從WS客戶端向WS服務器發送暱稱修改請求 ws.send(JSON.stringify({ nick: nick, type: "nickname" }));
這樣一個完整的WS客戶端代碼:
//建立一個WS客戶端 var ws = new WebSocket("ws://localhost:3001"); //WS客戶端鏈接到WS服務器後, 設定默認暱稱,並加入版聊 ws.onopen = function(event) { $("#nickname").val("遊客"); $logs.append("<div class='alert alert-success'>您已加入版聊</div>"); }; //若是WS服務器關閉,給予斷開提示 ws.onclose = function(event) { $logs.append("<div class='alert alert-danger'>您已斷開版聊</div>"); }; //若是WS服務器向這個WS客戶端發送信息: ws.onmessage = function(event) { var data = JSON.parse(event.data); //發送文本信息, 顯示到頁面上 if (data.type === "message") { $chat.append("<p>" + data.nick + "(" + data.time + "): " + data.message + "</p>"); //有新用戶加入, 顯示用戶加入通知, 並修改當前用戶列表 } else if (data.type === "join") { if ($("p[uid='" + data.uid + "']", $users).length === 0) { $users.append("<p uid='" + data.uid + "'>" + data.nick + "</p>"); $logs.append("<div class='alert alert-warning'>" + data.nick + "加入了版聊</div>"); } //有用戶離開, 顯示用戶離開通知, 並修改當前用戶列表 } else if (data.type === "exit") { $("p[uid='" + data.uid + "']", $users).remove(); $logs.append("<div class='alert alert-warning'>" + data.nick + "離開了版聊</div>"); //有用戶修改暱稱, 顯示用戶修改暱稱, 修改用戶列表 } else if (data.type === "nickname") { $("#nickname").val(data.nick); $("p[uid='" + data.uid + "']", $users).text(data.nick); $logs.append("<div class='alert alert-warning'>" + data.oldnick + " 修改暱稱爲 " + data.nick + "</div>"); } }; //發送消息按鈕事件 $("#send").click(function(event) { var message = $("#message").val(); if (message.trim() !== "") { //從WS客戶端向WS服務器發送信息數據 ws.send(JSON.stringify({ time: new Date().getTime(), message: message, type: "message" })); } }); //修改暱稱按鈕事件 $("#changeNick").click(function(event) { var nick = $("#nickname").val(); if (nick.trim() !== "") { //從WS客戶端向WS服務器發送暱稱修改請求 ws.send(JSON.stringify({ nick: nick, type: "nickname" })); } });
這樣一個完整的基於WebSocket的簡單聊天室就完成了,試用一下,雖然功能不完善,可是已經能夠用了,而且兼容firefox25和chrome