Strophe.js連接XMPP服務器Openfire、Tigase實現Web私聊、群聊(MUC)


XMPP(Extensible Messaging and Presence Protocol)是一種網絡即時通訊協議,它基於XML,具有很強的擴展性,被廣泛使用在即時通訊軟件、網絡游戲聊天、Web聊天及Web消息推送、移動設備的消息推送等場景,例如Google的GTalk、《英雄聯盟LOL》游戲聊天模塊。

由於在Web瀏覽器上的JavaScript不能直接處理TCP協議,所以XMPP服務器通常會提供BOSH(Bidirectional-streams Over Synchronous HTTP)接口,通過HTTP長輪訓(long-polling)可以實現Web瀏覽器即時聊天。Strophe.js是一個通過BOSH連接Web瀏覽器和XMPP服務器的工具庫。

XMPP協議簡介:

XMPP服務器和客戶端之間,是通過XML節(XML Stanza)來進行通訊。其中有三種非常重要的XML Stanza類型:<message>、<presence>、<iq>。

<message>:

聊天消息的發送和接收就是通過message節來實現。例如xxg1@host發送一條信息"你好"給xxg2@host,xxg1@host客戶端會將下面的這段XML發送到XMPP服務器,服務器再推送給xxg2@host客戶端。其中<message>的from屬性是發送者,to屬性是接收者,<body>子元素的內容就是聊天信息。

<message from="xxg1@host" to="xxg2@host" type="chat">
    <body>你好</body>
</message>

<presence>:

可用於表明用戶的狀態,例如用戶狀態改變成“Do not disturb”(“請勿打擾”),會向服務器發送:

<presence from="xxg@host">
    <status>Do not disturb</status>
    <priority>0</priority>
    <show>dnd</show>
</presence>

<iq>:

iq即Info/Query,采用“請求-響應”機制,類似於HTTP的機制。下面的例子是客戶端通過<iq>請求獲取聯系人,XMPP服務器將結果返回:

客戶端請求獲取聯系人:

<iq from='xxg@host' id='bv1bs71f' type='get'>
    <query xmlns='jabber:iq:roster'/>
</iq>

服務器結果返回:

<iq to='xxg@host' id='bv1bs71f' type='result'>
    <query xmlns='jabber:iq:roster'>
        <item jid='xxg2@host'/>
        <item jid='xxg3@host'/>
    </query>
</iq>

搭建XMPP服務器:

在實現Web瀏覽器聊天之前,首先要搭建一個XMPP服務器。例如Openfire、Tigase、Ejabberd是常用的XMPP服務器。其中Openfire、Tigase是基於Java實現,Ejabberd是Erlang實現。雖然實現的語言不同,但是都遵循XMPP協議,所以使用其中任意一個XMPP服務器即可。

下面以Openfire和Tigase為例。

Openfire可以自動化搭建很方便,本文不再介紹。Tigase的搭建可以參考我的另一篇博文:Linux搭建XMPP服務器Tigase(Spark客戶端測試)

XMPP服務器通常會實現BOSH擴展,下面是Openfire和Tigase的BOSH默認URL:

Openfire:http://host:7070/http-bind
Tigase:http://host:5280

在使用Strophe.js的時候,需要使用對應的HTTP地址才能連接上XMPP服務器。

如果使用Opnefire,還需要在管理后台配置一下:

Strophe.js:

下載:http://strophe.im/strophejs/

實現Web私聊:

私聊比較簡單,聊天信息是通過上面介紹的<message>來進行傳遞交換。例如接收到一條別人發來的聊天信息,即接收一個<message>元素,發送給別人一條聊天信息,即發送一個<message>元素。

HTML:

<!DOCTYPE html>
<html>
<head>
    <script src='http://cdn.bootcss.com/jquery/1.9.1/jquery.min.js'></script>
    <script src='http://cdn.bootcss.com/strophe.js/1.1.3/strophe.min.js'></script>
    <script src='test.js'></script>
</head>
<body>
    JID:<input type="text" id="input-jid">
    <br>
    密碼:<input type="password" id="input-pwd">
    <br>
    <button id="btn-login">登錄</button>
    <div id="msg" style="height: 400px; width: 400px; overflow: scroll;"></div>
    聯系人JID:
    <input type="text" id="input-contacts">
    <br>
    消息:
    <br>
    <textarea id="input-msg" cols="30" rows="4"></textarea>
    <br>
    <button id="btn-send">發送</button>
</body>
</html>

JavaScript(test.js):

// XMPP服務器BOSH地址
var BOSH_SERVICE = 'http://host:5280';

// XMPP連接
var connection = null;

// 當前狀態是否連接
var connected = false;

// 當前登錄的JID
var jid = "";

// 連接狀態改變的事件
function onConnect(status) {
    console.log(status)
    if (status == Strophe.Status.CONNFAIL) {
        alert("連接失敗!");
    } else if (status == Strophe.Status.AUTHFAIL) {
        alert("登錄失敗!");
    } else if (status == Strophe.Status.DISCONNECTED) {
        alert("連接斷開!");
        connected = false;
    } else if (status == Strophe.Status.CONNECTED) {
        alert("連接成功,可以開始聊天了!");
        connected = true;
        
        // 當接收到<message>節,調用onMessage回調函數
        connection.addHandler(onMessage, null, 'message', null, null, null);
        
        // 首先要發送一個<presence>給服務器(initial presence)
        connection.send($pres().tree());
    }
}

// 接收到<message>
function onMessage(msg) {
    
    // 解析出<message>的from、type屬性,以及body子元素
    var from = msg.getAttribute('from');
    var type = msg.getAttribute('type');
    var elems = msg.getElementsByTagName('body');

    if (type == "chat" && elems.length > 0) {
        var body = elems[0];
        $("#msg").append(from + ":<br>" + Strophe.getText(body) + "<br>")
    }
    return true;
}

$(document).ready(function() {

    // 通過BOSH連接XMPP服務器
    $('#btn-login').click(function() {
        if(!connected) {
            connection = new Strophe.Connection(BOSH_SERVICE);
            connection.connect($("#input-jid").val(), $("#input-pwd").val(), onConnect);
            jid = $("#input-jid").val();
        }
    });
    
    // 發送消息
    $("#btn-send").click(function() {
        if(connected) {
            if($("#input-contacts").val() == '') {
                alert("請輸入聯系人!");
                return;
            }

            // 創建一個<message>元素並發送
            var msg = $msg({
                to: $("#input-contacts").val(), 
                from: jid, 
                type: 'chat'
            }).c("body", null, $("#input-msg").val());
            connection.send(msg.tree());

            $("#msg").append(jid + ":<br>" + $("#input-msg").val() + "<br>");
            $("#input-msg").val('');
        } else {
            alert("請先登錄!");
        }
    });
});

修改JavaScript代碼中的BOSH_SERVICE,用瀏覽器打開HTML文件,登錄后即可聊天:

實現Web群聊:

XMPP群聊通過XMPP協議的MUC(Multi-User Chat)擴展實現。

Openfire默認支持MUC,但是Tigase服務器默認不支持MUC,需要在init.properties文件中加入以下粗體部分的配置項:

config-type=--gen-config-def
--admins=admin@host
--virt-hosts = host
--debug=server
--user-db=mysql
--user-db-uri = jdbc:mysql://localhost:3306/tigasedb?user=root&password=xxx
--comp-name-1 = muc
--comp-class-1 = tigase.muc.MUCComponent
--external = muc.devel.tigase.org:muc-pass:connect:5270:devel.tigase.org:accept

創建房間:

創建房間實際上可以寫在代碼中,但是本文為了方便,就使用XMPP客戶端Spark或者其他工具來創建。

首先使用Spark任意登錄一個用戶,下圖是Spark創建房間的步驟:

如果使用的是Tigase,默認創建的房間是加鎖的,別的用戶無法進入,需要對房間解鎖。Spark進入房間后,點擊下面的設置按鈕,然后不用更改任何設置,直接更新即可解鎖房間(雖然沒有修改任何配置,但是更新時會發送一個<iq>給服務器,這個<iq>解鎖了房間,參考http://xmpp.org/extensions/xep-0045.html#createroom-instant):

另外,如果使用Openfire可以直接使用管理后台來創建:

加入房間:

房間創建好了之后,就有有對應的房間JID:

加入房間可以通過發送一個<presence>來實現(實際上如果房間不存在下面的這條<presence>也會創建房間,但是創建的房間默認加鎖,還需要發送一條<iq>解鎖,所以本文就直接用Spark創建房間):

<presence from='xxg@host' to='xxgroom@muc.host/xxg'>
    <x xmlns='http://jabber.org/protocol/muc'/>
</presence>

屬性to='xxgroom@muc.host/xxg'中,xxgroom@muc.host表示房間JID,xxg表示在房間的昵稱。

聊天:

和私聊一樣,群聊也是通過<message>來實現,不同的是<message>的type屬性,私聊是"chat",而群聊是"groupchat",另外,to屬性即為房間JID,這樣一條聊天消息就會發送給房間中的所有人。

<message from='xxg@host' to='myroom@muc.host' type='groupchat'>
  <body>大家好!</body>
</message>

實現:

HTML:

<!DOCTYPE html>
<html>
<head>
    <script src='http://cdn.bootcss.com/jquery/1.9.1/jquery.min.js'></script>
    <script src='http://cdn.bootcss.com/strophe.js/1.1.3/strophe.min.js'></script>
    <script src='test2.js'></script>
</head>
<body>
    JID:<input type="text" id="input-jid">
    <br>
    密碼:<input type="password" id="input-pwd">
    <br>
    <button id="btn-login">登錄</button>
    <div id="msg" style="height: 400px; width: 400px; overflow: scroll;"></div>
    <br>
    消息:
    <br>
    <textarea id="input-msg" cols="30" rows="4"></textarea>
    <br>
    <button id="btn-send">發送</button>
</body>
</html>

JavaScript(test2.js):

// XMPP服務器BOSH地址
var BOSH_SERVICE = 'http://host:5280';

// 房間JID
var ROOM_JID = 'xxgroom@muc.host';

// XMPP連接
var connection = null;

// 當前狀態是否連接
var connected = false;

// 當前登錄的JID
var jid = "";

// 連接狀態改變的事件
function onConnect(status) {
    if (status == Strophe.Status.CONNFAIL) {
        alert("連接失敗!");
    } else if (status == Strophe.Status.AUTHFAIL) {
        alert("登錄失敗!");
    } else if (status == Strophe.Status.DISCONNECTED) {
        alert("連接斷開!");
        connected = false;
    } else if (status == Strophe.Status.CONNECTED) {
        alert("連接成功,可以開始聊天了!");
        connected = true;
        
        // 當接收到<message>節,調用onMessage回調函數
        connection.addHandler(onMessage, null, 'message', null, null, null);
        
        // 首先要發送一個<presence>給服務器(initial presence)
        connection.send($pres().tree());

        // 發送<presence>元素,加入房間
        connection.send($pres({
            from: jid,
            to: ROOM_JID + "/" + jid.substring(0,jid.indexOf("@"))
        }).c('x',{xmlns: 'http://jabber.org/protocol/muc'}).tree());
    }
}

// 接收到<message>
function onMessage(msg) {
    
    console.log(msg);
    // 解析出<message>的from、type屬性,以及body子元素
    var from = msg.getAttribute('from');
    var type = msg.getAttribute('type');
    var elems = msg.getElementsByTagName('body');

    if (type == "groupchat" && elems.length > 0) {
        var body = elems[0];
        $("#msg").append(from.substring(from.indexOf('/') + 1) + ":<br>" + Strophe.getText(body) + "<br>")
    }
    return true;
}

$(document).ready(function() {

    // 通過BOSH連接XMPP服務器
    $('#btn-login').click(function() {
        if(!connected) {
            connection = new Strophe.Connection(BOSH_SERVICE);
            connection.connect($("#input-jid").val(), $("#input-pwd").val(), onConnect);
            jid = $("#input-jid").val();
        }
    });
    
    // 發送消息
    $("#btn-send").click(function() {
        if(connected) {

            // 創建一個<message>元素並發送
            var msg = $msg({
                to: ROOM_JID, 
                from: jid, 
                type: 'groupchat'
            }).c("body", null, $("#input-msg").val());
            connection.send(msg.tree());

            $("#input-msg").val('');
        } else {
            alert("請先登錄!");
        }
    });
});

創建好房間,修改JavaScript代碼中的BOSH_SERVICE和ROOM_JID,用瀏覽器打開HTML文件,登錄后即可群聊:

另外,Strophe.js還有一個專門的MUC插件,有興趣的同學可以自己研究下:https://github.com/strophe/strophejs-plugins/tree/master/muc


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM