搭建HTML5簡易聊天室

前面的話


以前曾經寫過一個符合xmpp協議的Web IM,但使用的是JSJaC,後臺用的也是與之配套的jabber client,發現nodejs的事件模式更適合做爲Web IM的客戶端。html

而傳統的ajax輪詢機制也遲早被全雙工websocket所取代,因此就打算在個人畢業設計的Web IM平臺中使用websocket。html5

在這裏調研一下並做出了一個簡單的版聊demo,這裏講一下這個簡單demo的實現方式node

WebSocket


什麼是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

  1. onopen 在WS客戶端和WS服務器創建鏈接成功後調用
  2. onmessage 在WS服務器給WS客戶端發送數據時調用
  3. onerror 若是鏈接失敗,發送、接收數據失敗或者處理數據出現錯誤,則會被調用
  4. onclose 在WS客戶端接收到WS服務器關閉時進行調用

WebSocket服務器實現


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"
    });
});

WebSocket客戶端實現


在瀏覽器中,則須要創建一個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

相關文章
相關標籤/搜索