xmppmini 專案詳解:一步一步從原理跟我學實用 xmpp 技術開發 2.登入的實現
第二章登入的實現
金庸《倚天屠龍記》
張三丰緩緩搖頭,說道:“少林派累積千年,方得達成這等絕技,決非一蹴而至,就算是絕頂聰明之人,也無法自創。”他頓了一頓,又道:“我當年在少林寺中住過,只是未蒙傳授武功,直到此時,也不明白尋常血肉之軀如何能練到這般指力。”
前言
大家肯定不知道,要說其實 xmpp 裡最關鍵、開發中也最不好解決的其實是登入過程你信麼?
我要說我只用兩句話就能完成 xmpp 登入你信麼?
2.1 登入的原理
前面的第一章寫得稍顯囉嗦,下面我們來實現具體的功能。首先要實現的就是我們這一節要說的登入功能。
說來大家也許不相信,其實 xmpp 實現中最難的部分其實就是第一步:登入。過了這一關之後,其實後面的部分幾乎沒有什麼難度可言,就是些正常的客戶機/伺服器應答而已。
登入部分難就難在其協議的複雜性和相容性上。和電子郵件一樣,其實登入過程是有多種實現可選擇的,不過這實際上並沒有什麼必要全部實現(對於個人開發者來說也不太可能有精力全部實現),就象電子郵件客戶端一樣,我們實現其中一種最常見的就可以了,我們在文章中會解釋為何選擇這種登入方式。
xmpp 和我們前面介紹的 smtp/pop3 一樣是以字串應答為基礎的,所以也可以將其互動過程以對話的形式進行描述(當然會有不一樣的地方,用到時我們會提到)。具體的與伺服器的對話方法我們在《一步一步從原理跟我學郵件收取及傳送》中已經介紹過了,不清楚的同學可以移步過去先學習一下,這裡我們就不贅述了。
2.2 建立測試環境
和電子郵件一樣,要測試我們的程式和想法需要有一臺可用的伺服器,大家可以直接註冊我們的 newbt.net 免費郵箱,會自帶 xmpp 功能。xmpp 的完整功能需要您升級為 1 元包年使用者,不過我們目前提到的功能在免費使用者級別就可以完成了(其實我們的 newbt 免費郵箱和 xmppmini 專案大體上是個眾籌專案,大家覺得有用就支援吧,不會強制收費)。希望自己掌握伺服器的同學也可以使用 openfire 伺服器搭建,理論上也可以使用 ejabberd ,不過在開發中筆者發現 ejabberd 的相容性要比 openfire 差很多,所以推薦 openfire 。不過對於將來想自己開發伺服器的同學不推薦你們在 openfire 上二次開發,原因我們後面會講到(文中如有提到ejabberd 則為ejabberd-19.08-windows)。
我們 xmppmini 專案的賬號直接在 http://www.newbt.net:8888 上註冊即可(但願您看到這篇文章的時候我們專案還健在)。Openfire 需要在http://www.igniterealtime.org/projects/openfire/index.jsp 下載,筆者測試的版本是openfire-4.1.4-1 因為igniterealtime 家的向前相容性並不怎麼好,所以不能保證與最新版本的 openfire 相容。Openfire 似乎不支援多域名,所以在安裝時如果是在區域網中測試,一定要先固定自己的 ip 否則下次開機後就可能無法連線了。
2.3 登入協議與必須要注意的坑
如前所述我們只介紹其中一種最常用的登入協議,原因為其他的登入方式我們得說上一週而且並不怎麼實用。綜合考慮推薦大家都使用這種登入方式。在 xmpp 協議中,這種登入方式稱之為“PLAIN”。
通常 xmpp 連線的是伺服器的 5222 埠,雙方對話過程如下:
1. 客戶端首先發送資訊。
類似於:
<?xml version="1.0"?> <stream:stream xmlns:stream="http://etherx.jabber.org/streams" version="1.0" xmlns="jabber:client" to="newbt.net" xml:lang="en" xmlns:xml="http://www.w3.org/XML/1998/namespace">
伺服器的回覆類似於:
<?xml version='1.0'?> <stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' from='xumatomacbook-pro.local' id='675c6847-c13d-4710-9844-d9339e4df087' version='1.0' xml:lang='en'>
就是這麼簡單的第一句對話,其中也有很容易出錯的相容性問題。
對於 openfire 和 newbt.net 伺服器,to 的部分可以隨便寫。對於 ejabberd-19.08-windows 則一定要寫上正確的對應域名(區域網測試的話則是 IP)
否則會報
<stream:error> <host-unknown xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
另外,現在 ejabberd 的版本似乎一定要 ssl 連線
可修改 C:/ProgramData/ejabberd/conf/ejabberd.yml 中的 starttls_required 為 false 否則沒法用明文對話進行測試。
可以看到 xmpp 中收發的字串並不是標準的完整 xml 格式,其實嚴格來說它只是 xml 格式的節點片段。其中的<?xml version='1.0'?> 並不是必須的。瞭解這些在解碼 xmpp 的資訊時是非常重要的。
2.伺服器迴應,並且告知自己的可用登入方式。
這第一句話的事情還沒完,實際上真實的伺服器並不會只回應一條語句,而是會再發送一條語句說明自己支援的功能。類似於:
<stream:features><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>PLAIN</mechanism></mechanisms></stream:features>
所以客戶端傳送第一條命令後要有準備,自己要接收兩條迴應資訊,如果加上 xml 的資訊頭就有可能是要接收三條迴應,這和 smtp/pop3/ftp 這樣的傳統網路協議是不同的。
3. 客戶端選擇最簡單的 plaint 方式傳送相應的使用者名稱和密碼。
類似於:
<auth mechanism="PLAIN" xmlns="urn:ietf:params:xml:ns:xmpp-sasl">AGNjY0BuZXdidC5uZXQAYWFhYWFh</auth>
這裡有個問題。就是現在對網路攔截非常重視,理論上有可能會被攔截偷窺到密碼。解決的辦法 xmpp 給出了很多,不用那麼麻煩,我們選擇最簡單的,直接連線 5223 埠就可以了,除了使用的是 ssl/tls 的通訊方式外其他的不用作任何更改。
傳送的訊息格式中,紅色以外的部分是固定的,而紅色部分是一個 base64 後的字串,它的原文為:”\0使用者名稱\0密碼” 偽碼如下:
Base64Encode("" + '\0' + user + '\0' + pass)
這裡有個超級大坑,在標準協議中這裡其實應該是 “登入id+字元0+使用者名稱+字元0+密碼”,遺憾的是對這部分協議的理解各家有各家的看法(這種理解上的歧義在 smtp/pop3 中也是非常常見的,也是造成網路協議實現間不相容的主要原因之一)。目前相容比較好的方式是將“登入id”直接設定為空字串,另外在“使用者名稱”這裡實際上要帶上它的域名,即“使用者名稱+@+host”,例如“[email protected]”而不是直接寫上 “clq”。關於這個問題我仔細對比過很多篇同行們的文章教程(rfc文件更不用說了),大多數作者都沒有解釋這部分內容,但最後他們的給出的程式碼中格式大多都是採用留空登入 id 的方式。也歡迎大家回覆探討這一問題,特別是對 ejabberd 比較熟悉的使用者。目前的現狀是如果寫了登入 id 的話很可能是過不了 ejabberd 的登入校驗的。
4.伺服器迴應是否成功
類似於:
<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>
如果登入失敗的話則類似於:
<failure xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><not-authorized></not-authorized></failure>
5.客戶端接收響應,解析出是否成功。
客戶端只要判斷迴應的訊息中包含的是success 還是failure 就可以了,連 xml 解碼都用不著。
所以綜上所述,其實最簡單的 xmpp 登入其實客戶端只要傳送兩句話就可以了,而且換用安全套接字後就可以成為實用的登入方式。
這其中的技術難點其實只有一個,那就是伺服器迴應訊息的解碼,在下一章節中我們會介紹不使用第三方庫的簡單又實用的方式。
----------------------------------------------------------------
從本文中大家可以看出,本系列文章假定大家已經看過《一步一步從原理跟我學郵件收取及傳送》系列文章,對於一些通用的操作一筆帶過了(象base64的編碼過程、如何用telnet等工具傳送網路命令列、ssl/tls這些)。也節省了不少篇幅。不過這對於初學者有些不太公平,只有建議這部分讀者先去認真的跟著《一步一步從原理跟我學郵件收取及傳送》文章自己動動手先熟悉下網路程式的常用操作。
&n