近日工做閒暇之餘,對IM系統產生了興趣,轉而研究了IM的內容。找了半天,知道比較流行的是Openfire的系統,Openfire有許多平臺實現,因爲我是作Web的,因此固然是但願尋找Web的實現。Openfire的之前的Web實現,是基於Http-bind的一種長輪詢機制,固然也沒什麼很差,只是我如今HTML5都開始了,固然但願可以來個基於Websocket的機制了。然而Google&百度了很久,也沒有找到什麼教程,發現這個東西並非很火熱的樣子,那隻好本身開始研究了。因而有了這一篇文章。java
對了,我知道xmpp的JS框架有幾個,但那些框架彷佛都是按照本來的長輪詢的機制來作的,並非使用Websocket來作的。程序員
另外,作軟件2年了,當我剛涉足這個行業時,我被告知:「不要重複造輪子!」,曾經我對此深信不疑,但這兩年的工做讓我愈加認識,不要重複造輪子,僅僅是在於作項目當中,作一個商業產品的時候,考慮到開發速度和產品後期的穩定性及維護性,確實須要採用成熟的技術,但!這不表明做爲一個程序員,不該該抱着一種從0開始的研究精神,若是真的熱愛這個行業,這個領域,就應該嘗試着,根據RCF文檔協議,從基礎協議的層次開始作軟件。web
好了,廢話很少說。恩,開始正文吧。chrome
確定是去 Openfire 的官網下載最新的Openfire服務端程序啦,Openfire的是開源的,能夠免費下載。如圖:後端
這就不說了。瀏覽器
安裝了WebSocket插件以後,有兩個本來用於http-bind的端口,就被WebSocket佔用了(有了WebSocket,還須要Http-bind作啥= =)。這兩個接口分別是7443和7070,前者是用於HTTPS安全鏈接,後者是非安全鏈接。安全
創建連接:服務器
1 var connectionState = ["正在鏈接..", "鏈接已創建", "正在關閉..", "已經關閉"]; 2 var host = "ws://127.0.0.1:7070/ws/"; 3 if (window.WebSocket != 'undefined') { 4 //OpenFire是實現了WebSocket的子協議 5 var connection = new WebSocket(host, "xmpp"); 6 console.log(connectionState[connection.readyState]); 7 //註冊鏈接創建時的方法 8 connection.onopen = wsOpen; 9 //註冊鏈接關閉時的方法 10 connection.onclose = wsClose; 11 //註冊收到消息時的方法 12 connection.onmessage = wsMsg; 13 }
若是要使用Https加密信道,就把Host改爲:websocket
var host="wss://127.0.0.1:7443/ws/
恩,創建安全鏈接還須要添加安全證書到Keystore,這個在Openfire的根目錄下,有一個resource/security文件夾,裏面有keyStore文件,固然,這部分我還不是很懂,關於Https的加密信道,TSL/SSl證書的概念,還沒徹底弄明白,不過這也不是這篇文章的重點。暫時我就先用非加密的方式來作,至於加密的鏈接,除了host的區別,其餘也沒有區別。session
當Websocket握手以後,咱們要作的第一件事,就是發起一個創建流的請求,
1 function wsOpen(event) { 2 //打印連接狀態 3 console.log(connectionState[connection.readyState]); 4 //發送創建流請求 5 var steam = "<open to='127.0.0.1' from='wuxinzhe@127.0.0.1' xmlns='urn:ietf:params:xml:ns:xmpp-framing' xml:lang='zh' version='1.0'/>"; 6 connection.send(steam); 7 }
Websocket下,創建流不像其餘平臺那樣,使用<stream:stream/>標籤,而是使用<open/>標籤,其中to屬性是域名,from是你的JID。
發出請求以後,會馬上收到服務器的響應:
第一條響應式服務器贊成創建流,第二條是告訴你,安全驗證的幾種方式,其中最簡單的方式是PLAIN方式,這種方式僅僅是將你的帳號密碼進行BASE64加密後傳輸,能夠說是很不安全。固然,你也能夠選擇SCRAM-SHA-1的安全加密方式,只不過你的js庫要支持SHA-1加密,我由於是剛開始探路,因此一切從簡,SHA-1的加密方式請求流程跟PLAIN會有一點區別,回頭我們再說。
另外我這邊寫了一個當收到來自服務端信息的方法:
function wsMsg(event) { console.log("Server: " + event.data); }
打印出來而已。就像上面console面板中的信息同樣。那個是chrome的調試器,其餘瀏覽器也有對應的。
剛纔說了,我先用最基本的PLAIN的方式登陸:
function auth() { //Base64編碼 var token = window.btoa("wuxinzhe@127.0.0.1\0wuxinzhe"); var message = "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>" + token + "</auth>"; console.log("Client: " + message); connection.send(message); }
其中,window.btoa()是自帶的方法,不須要額外加載任何庫,因此我才說這個真的是最快最簡單的方法,而後咱們看方法體內的字符串,這個字符串格式是:jid+password,以\0做爲分隔符,後端java程序是一個DefaultAuthProvider提供驗證的,固然大家也能夠實現本身的驗證方式,默認的方式是以\0做爲分隔符,注意的是,我一開始密碼是123456,發現\0123456這樣到後端密碼會被分割成23456,\01整個會被轉譯,恩,解決的辦法要嘛就換一個分隔符(我是說後端的openfire那邊),要嘛就禁止以數字開頭的密碼。
發起安全驗證以後,會受到服務端的響應, 若是成功了,如圖:
至於要是失敗了,會返回這個錯誤,固然這個錯誤信息不多,究竟是用戶名找不到,仍是密碼錯誤了,單憑這個錯誤信息是看不出來的,不過也沒辦法咯,要不,去修改一下服務端唄:
當咱們發起安全驗證成功了之後,緊接着就要開啓一個新的流,新的流服務端會給予一些新的XML節點權限(<iq/>、<presence/>),這樣才能發送一些其餘功能的信息,好比發送消息,獲取聯繫人列表,再剛開始創建的第一個流失不能發送這些節點的。
創建新流一樣適用<open/>標籤,但有一個地方與以前不一樣,就是此次是須要攜帶id屬性的,什麼是id屬性?咱們回顧第一個流創建時,服務端返回的<open/>信息,是否是就有一個id,沒錯,這個id據個人理解,每次創建websocket鏈接時,都會爲每一個鏈接生成一個獨一無二的id,這個id表明了這個鏈接,因此後續咱們會在不少不少地方都須要使用這個id。
發起新的流:
1 <open xmlns='jabber:client' to='127.0.0.1' version='1.0' from='wuxinzhe@127.0.0.1' id='70tvu3ooiu' xml:lang='zh'/>
服務端會返回兩條信息,第一條,是贊成打開新流,第二條,是告訴你,接下來要作的是bind操做,就是要綁定客戶端:
發起綁定也要用到剛纔說的id屬性:
<iq type='set' id='6ps7q3ideb'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>
綁定的時候,還能夠加入一些標籤,來對當前的客戶端,作一些比較具備語義的說明,用來描述你的客戶端類型:
<iq id="wSBRk-4" type="set"> <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"> <resource>Showings</resource> </bind> </iq>
咱們看看這兩種bind之間的區別:
這是bind的客戶端請求內容和服務端響應內容,上面的是沒有加入<resource/>標籤的,下面是加入了之後。咱們能夠看到,服務端返回信息中的<jid/>是有區別的,當不加描述節點的時候,是將前面的id屬性直接用做後綴拼接如JID的,加入了之後天然會更具語義化。
這個過程彷佛是Openfire用於區分登陸的客戶端類型的方式。這樣若是同一個帳號,在不一樣的客戶端登陸,也會有所區別。
而後咱們要獲取session。
<iq xmlns="jabber:client" id="ak014gz6x7" type="set"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>
這部分我暫時還不知道用來幹嗎的,畢竟我還沒熟悉openfire的xmpp協議的整個過程,因此有些部分不是很清楚,待我整個看過之後,到時候再來看看這個步驟是作什麼用的。
此時咱們進入Openfire的後臺,看看用戶在線的狀況:
誒?怎麼有鏈接,倒是離線呢?不着急,由於咱們雖然登錄了,可是咱們尚未「出席」。就像QQ你能夠設置不一樣的登陸狀態,有在線、不在電腦、忙、離線,這些狀態,因此咱們若是要在線,只須要發送出席請求就好了:
<presence id="ak014gz6x7"><status>Online</status><priority>1</priority></presence>
大家看,處處都要用到這個ID,固然,前面咱們作了綁定動做,此刻不用id屬性,換成from="jid"應該也是有效的。
此時咱們再看後臺狀態:
OK了。
So咱們還要下線呢,關閉鏈接,此時要用<open/>對應的標籤<close/>
<close xmlns="urn:ietf:params:xml:ns:xmpp-framing"/>
這樣就好了。
其實使用Websocket創建鏈接與XMPP協議在其餘的客戶端裏是沒有什麼太大的區別,可能就是<open/><close/>這兩個標籤的區別。咱們如今可以順利登陸了,基本上,就是有一個好的開始了。