XMPP(Extensible Messaging and Presence Protocol)是一種網絡即時通信協議,它基於XML,具備很強的擴展性,被普遍使用在即時通信軟件、網絡遊戲聊天、Web聊天及Web消息推送、移動設備的消息推送等場景,例如Google的GTalk、《英雄聯盟LOL》遊戲聊天模塊。css
因爲在Web瀏覽器上的JavaScript不能直接處理TCP協議,因此XMPP服務器一般會提供BOSH(Bidirectional-streams Over Synchronous HTTP)接口,經過HTTP長輪訓(long-polling)能夠實現Web瀏覽器即時聊天。Strophe.js是一個經過BOSH鏈接Web瀏覽器和XMPP服務器的工具庫。html
XMPP協議簡介:
mysql
XMPP服務器和客戶端之間,是經過XML節(XML Stanza)來進行通信。其中有三種很是重要的XML Stanza類型:<message>、<presence>、<iq>。jquery
<message>:git
聊天消息的發送和接收就是經過message節來實現。例如xxg1@host發送一條信息"你好"給xxg2@host,xxg1@host客戶端會將下面的這段XML發送到XMPP服務器,服務器再推送給xxg2@host客戶端。其中<message>的from屬性是發送者,to屬性是接收者,<body>子元素的內容就是聊天信息。github
<message from="xxg1@host" to="xxg2@host" type="chat"> <body>你好</body> </message>
<presence>:sql
可用於代表用戶的狀態,例如用戶狀態改變成「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。