IM
XMPP協議的前身是Jabber協議,Jabber項目的初始目標是建立一個開放的即時消息平臺。它的核心提供了網絡上從一地方到另一個地方的快速路由信息的能力。
舉例
我們假定suke從skh.whu.edu.cn服務器上的賬戶給在skh.whu.edu.cn服務器端的妹妹發送一個消息。
- 她的客戶端通過client-to-server XML流推送消息節到skh.whu.edu.cn服務器。
- skh.whu.edu.cn服務器在節上貼上from地址的郵戳,并為了得到節應該如何被處理去檢查to地址。得知這個消息節被綁定到skh.whu.edu.cn服務器,
- skh.whu.edu.cn服務器立即通過server-to-server XML流路由消息到skh.whu.edu.cn(無中間服務器跳躍)。
- 基于接收到的消息節,skh.whu.edu.cn服務器檢查suke的妹妹是否在線;如果在線,服務器立即通過server-to-client XML流傳遞消息到她的在線設備中的一個或多個。
- 消息很快就從suke傳遞到了她的妹妹。
例子分析
從例子中我們可以得出:
- 客戶端和服務器是按事件驅動的,并在任何他們接收到一個進來的節的時候,采取適當的行為。
- XMPP服務器不會存儲一個消息,并等待客戶去輪詢消息;相反,他們一接收到消息就把它傳遞出去。
- 所有實體(特別是服務器)應該是能意識到出席的,因為在線使接收者服務器和接收者設備之間快速傳遞成為可能。
- 快速并且精確的對DNS查找、域名解析、長時間TCP連接、連接超時和網絡沖突的處理對整個系統的成功運行是至關重要的。
出席消息類型
出席幾種類型的XMPP消息,基本上是通過type屬性的值來進行區分的:
normal
這種消息類型立即被傳遞或被服務器離線存儲,并被客戶端以任何聊天或群聊對話外的獨立消息來處理。這個類型是默認值。
chat
chat消息類型在“聊天會話”中通常在相對短的時間內以突發消息發送。即時消息客戶端在一對一的對話界面中顯示這些信息。
groupchat
XMPP服務器通常將路由類型為groupchat的消息路由到一個擁有多個聊天室的專門組件或模塊,并且這個組件產生一個向外的消息給房間的每一個人。
headline
headline消息通常不被離線存儲,因為他們是臨時性的。另外,XMPP服務器經常傳遞headline類型的消息到所有和賬戶關聯的在線設備(至少priority值為非負的)。
error
error類型的消息是為了應答先前發送的消息而被發送出去的,以指示和先前消息有關的錯誤發送(接收人不出席,此時不能發送消息等)。
出席消息類型總結
chat和normal消息被接收人的服務器以一種特定的方式處理:如果消息是投遞到賬戶的bare JID(user@domain.tld),服務器立即把消息傳遞給當前與賬戶關聯的優先級最高的資源。如果僅有一個在線資源,決定就容易,但是如果有多個在線資源,接收人的服務器傳遞消息給出席優先級最大值的那個資源。
盡管XMPP技術能在實時數據傳遞中占據優勢,但是幾乎所有的XMPP服務器都支持“離線消息”,當服務器接收傳給這個JabberID的一個normal或chat消息,而目的接收人不在線。當用戶下次登錄的時候,這些消息會自動的發送給接收人的客戶端。當接收人的服務器傳遞出了離線的消息,它也將使用定義在延遲傳遞[XEP-0203]中的協議擴展添加一個小的擴展通知。這可以使接收客戶端準確的對在用戶界面中接收到的消息排序。
聊天會話
XMPP的聊天會話沒有進行正式談判,而是自然地進行。發起對話的實體給應答者的bare JID發送一個消息,并且消息被發起者的服務器使用發起者的full JID貼上郵戳。當應答者發送一個回復時,接收人的服務器也會將應答者的full JID貼到回復的消息上。在這時,發起者知道了應答者的full JID,并且應答者知道了發起者的full JID,然后將相互的XMPP資源標識進行“鎖定”。當發送接下來的消息時,兩者都相互發送節到full JID,除非收到另外一個人的出席變化為止(這可能觸發重新發送一個消息到bare JID)。
聊天狀態通告
在聊天中加入不知道聊天對象的狀態會讓人誤以為對方已經離線或不想搭理人,未免讓人心中有所失落。比如,你和你的女友聊天,你問:“親愛的,今天都干啥了”,你的女友很興奮,她有很多話要告訴你,由于打字的時間太久讓你覺得她不愛你....好嘛,這樣的聊天是不是很虐心....
然后,辦法還是有的,你需要在你的IM系統中得到聊天狀態的通告,定義聊天狀態通告[XEP-0085]
聊天狀態
starting
某人開始一個對話,但是你還沒有參與進來
active
你正參與在對話中,當前你沒有組織你的消息,而是在關注
composing
你正在組織一個消息
paused
你開始組織一個消息,但由于某個原因而停止組織消息
inactive
你一段時間里沒有參與這個對話
gone
你參與的這個對話已經結束
對話期間狀態變化
在composing狀態組織完一個消息后,狀態變為active,等待消息的回復。
消息狀態變化代碼實現
母親-女兒對話
<message from="suke@skh.whu.edu.cn"
to="daughter@skh.whu.edu.cn"
type="chat">
<body>Hi honey!</body>
<active xmlns="http://jabber.org/protocol/chatstates"/>
</message>
通過添加<active/>元素到你的消息中,指示了你在對話中處于活躍狀態。女兒開始進行回復,這樣她的客戶端發送給你一個聊天狀態更新,通過添加一個<composing/>元素到一個空的消息里:
<message from="daughter@skh.whu.edu.cn"
to="suke@skh.whu.edu.cn"
type="chat">
<composing xmlns="http://jabber.org/protocol/chatstates"/>
</message>
通告之后不久,實際的消息到達,再次使她成為對話中的活躍參與者:
<message from="daughter@skh.whu.edu.cn"
to="suke@skh.whu.edu.cn"
type="chat">
<body>Hi</body>
<active xmlns="http://jabber.org/protocol/chatstates"/>
</message>
這個對話持續一會兒,直到你問她關于奶奶的事:
<message from="suke@skh.whu.edu.cn"
to="daughter@skh.whu.edu.cn"
type="chat">
<body>Did you visit grandma this afternoon? What did she tell you?</body>
<active xmlns="http://jabber.org/protocol/chatstates"/>
</message>
<message from="daughter@skh.whu.edu.cn"
to="suke@skh.whu.edu.cn"
type="chat">
<composing xmlns="http://jabber.org/protocol/chatstates"/>
</message>
這里她突然停下碼字去接電話,幾秒鐘后,她的客戶端給你發送一個<paused/>的通告:
<message from="daughter@skh.whu.edu.cn"
to="suke@skh.whu.edu.cn"
type="chat">
<paused xmlns="http://jabber.org/protocol/chatstates"/>
</message>
一會兒后,她又恢復回答:
<message from="daughter@skh.whu.edu.cn"
to="suke@skh.whu.edu.cn"
type="chat">
<composing xmlns="http://jabber.org/protocol/chatstates"/>
</message>
最后,跳到對話的結尾,她發送再見并關閉了聊天窗口:
<message from="daughter@skh.whu.edu.cn"
to="suke@skh.whu.edu.cn"
type="chat">
<body>Everything was fine. I have to go do my homework now.</body>
<active xmlns="http://jabber.org/protocol/chatstates"/>
</message>
<message from="daughter@skh.whu.edu.cn"
to="suke@skh.whu.edu.cn"
type="chat">
<gone xmlns="http://jabber.org/protocol/chatstates"/>
</message>
你可以配置你的客戶端,使它只能發送基本的聊天狀態信息(例如,你是是否是active或composing),并且不發送關于更多狀態的更多信息,例如paused、inactive或gone。這些基本信息只會顯露你是否正在組織回答,并且留下一跡象表明你是否已經離開你的IM客戶端或重新考慮談話并關閉對話。
格式化消息
格式化消息是指XMPP能夠讓你自定義消息的外觀或表達,如字體大小,顏色,強調等定義在XHTML-IM[XEP-0071]
<message from="suke@skh.whu.edu.cn"
to="beta@skh.whu.edu.cn"
type="chat">
<body>I love this movie I saw last night, it's awesome!</body>
<html xmlns="http://jabber.org/protocol/xhtml-im">
<body xmlns="http://www.w3.org/1999/xhtml">
<p>
I <em>love</em>, this new movie I saw last night,
it's <strong>awesome</strong>!
</p>
</body>
</html>
</message>
你也可以用CSS格式文本。這樣讓你能夠包括許多流行的風格格式,包括顏色、字體、文字大小、字體粗細(例如,粗體)和字體風格(例如,斜體)、字體邊緣、文本對齊(例如,居中)、和文本裝飾(例如,下劃線)。
XHTML-IM子集也提供了對核心HTML表達的特征的一些支持,包括編號和無序列表、超鏈接和圖像
名單中缺少的是一些更高級的HTML要素如表格和多媒體對象、以及在HTML文檔中以<HEAD>標簽的任何事物如腳本。這是有意為之的,因為有些惡意代碼可能會使用這些要素(XMPP的設計者總是絞盡腦汁的思考安全性?。?。相反,XHTML-IM關注的是HTML要素的一個簡單子集,可以用在rapid-fire聊天對話的內容中并進行輕量級的表達。即使這樣,XMPP客戶端仍然要對來自未知實體的XHTML格式消息倍加小心,因為即使圖像引用也可能引入安全漏洞。一種組織措施就是只接收來自你名冊中的人的XHTML-IM格式消息。
vCard
vCard標準(最初是出版在vCard MIME Directory Profile[RFC 2426]中)定義了許多你想要標榜的數據字段,包括你的名字、昵稱、地址、電話和傳真號、所屬公司、電子郵件地址、生日、個人網址、你的頭像、甚至還有你的PGP密鑰。你可以不用發布任何你不想發布的信息,但是這樣做能讓人們找出更多關于你的信息,這樣可以加快對話。
我們假定在skh.whu.edu.cn的suke發送一個不請自來的消息給beta:
<message from="suke@skh.whu.edu.cn"
to="beta@skh.whu.edu.cn">
<body>O Beta do you know the way out of this pool?</body>
</message>
在回復之前,beta也許會通過發送一個IQ-get到JabberID檢查suke的vCard:
<iq from="beta@skh.whu.edu.cn"
id="pw91nf84"
to="suke@skh.whu.edu.cn"
type="get">
<vCard xmlns="vcard-temp"/>
</iq>
由于這個請求是發送到suke的bare JID,suke的服務器代表她進行回復:
<iq from="suke@skh.whu.edu.cn"
id="pw91nf84"
to="beta@skh.whu.edu.cn"
type="result">
<vCard xmlns="vcard-temp">
<N>
<GIVEN>suke</GIVEN>
</N>
<URL>http://sku.whu.edu.cn/~suke/</URL>
<PHOTO>
<EXTVAL>http://www.cs.whu.edu/~rgs/suke03a.gif</EXTVAL>
</PHOTO>
</vCard>
</iq>
因此,beta至少可以在進行聊天之前,瀏覽suke的個人網址并查看她的圖片。自然地,在vCard中的所有數據可能是假的,所以可能會為得到的vCard結果付出代價。但是,在許多情況下,有總比沒有好!
為了更新你的vCard,發送一個IQ-set到你的服務器。這里suke添加一個郵件地址并上傳整個vCard到她的服務器(是的,不可能只上傳變化了的,因為vCard-temp規格沒有提供那個特征):
<iq from="suke@skh.whu.edu.cn"
id="w0s1nd97"
to="skh.whu.edu.cn"
type="set">
<vCard xmlns="vcard-temp">
<N>
<GIVEN>suke</GIVEN>
</N>
<URL>http://skh.whu.edu.cn/~suke/</URL>
<PHOTO>
<EXTVAL>http://www.cs.whu.edu/~rgs/suke03a.gif</EXTVAL>
</PHOTO>
<EMAIL><USERID>suke@whu.edu.cn</USERID></EMAIL>
</vCard>
</iq>
阻止和過濾通訊
簡單阻止和過濾通訊
我們假定你想阻止來自BigCompany.com的之前的上司的通訊。如果你的服務器支持簡單通訊阻止,就很容易做到這點,只需要發送一個適當的IQ-set:
<iq from="suke@skh.whu.edu.cn/Psi"
id="yu4er81v"
to="suke@skh.whu.edu.cn"
type="set">
<block xmlns="urn:XMPP:blocking">
<item jid="gmz@skh.whu.edu.cn"/>
</block>
</iq>
現在,阻止gmz@skh.whu.edu.cn確切意味著什么呢?
首先,你需要對你的之前的上司表示成離線。當你對那個JabberID添加了阻止規則,你的服務器發送出一個不可用的出席包,以便你的老板以為你是離線。從那時起,任何時候你更新你的出席(例如,恢復在線),相關的出席節不會被發送到gmz@skh.whu.edu.cn(似乎你永遠不再登陸)。
其次,你的服務器需要確認你之前的上司不能用任何方式找出你在線。這意味著你的服務器對進來的IQ-get或IQ-set使用<service-unavailable/>錯誤應答,忽略進來的任何<message/>消息(或再次返回一個<service-unavailable/>錯誤),并且放棄任何進來的<presence/>節。
最后,您的服務器需要防止你做一些愚蠢的事情,比如向你之前的上司發送一個消息或IQ請求,這樣它會對任何發送給gmz@skh.whu.edu.cn向外的節回復一個<not-acceptable/>錯誤。
你也可以組織完整的域名。我們假定你已經開始接收到XMPP網絡上來自一個流氓服務器的不請自來的消息(也許是spammers.lit)。你可以通過設置規則組織那個域中的任何JabberID的消息:
<iq from="suke@skh.whu.edu.cn"
id="i3s91xc3"
to="skh.whu.edu.cn"
type="set">
<block xmlns="urn:XMPP:blocking">
<item jid="spammers.lit"/>
</block>
</iq>
現在當你恢復你的“阻止列表”,你將看到兩個條目:
<iq from="suke@skh.whu.edu.cn/Psi"
id="92h1nv8f"
to="suke@skh.whu.edu.cn"
type="get">
<blocklist xmlns="urn:XMPP:blocking"/>
</iq>
<iq from="suke@skh.whu.edu.cn"
id="92h1nv8f"
to="suke@skh.whu.edu.cn/Psi"
type="result">
<blocklist xmlns="urn:XMPP:blocking">
<item jid="gmz@skh.whu.edu.cn"/>
<item jid="spammers.lit"/>
</blocklist>
</iq>
在簡單的通訊阻止中,它也很簡單解除封鎖的人。簡單地發送一個包含在一個<unblock/>元素中的JabberID的IQ-set代替<block/>元素:
<iq from="suke@skh.whu.edu.cn/Psi"
id="ng23h57w"
to="suke@skh.whu.edu.cn"
type="set">
<unblock xmlns="urn:XMPP:blocking">
<item jid="gmz@skh.whu.edu.cn"/>
</unblock>
</iq>
高級阻止和過濾
有時候你想通過過濾和阻止擁有更多的控制,相對于簡單的通訊阻止。例如,當你正在使用移動電話登錄到你的IM服務器時,你不想要接收到你200個同事的狀態更新,因為這樣會占據你非常有限的帶寬。在另一方面,你又確實想要接收他們發送給你的偶然消息。而且,你也不想組織所有進來的出席包,因為你想知道你家里人哪一個在線,因此在你開始一個海外旅行之前可以和他們進行聊天。另外,你需要一個finer-grained協議來控制你的交通過濾規則。
XMPP再次發揮出它的作用。然后簡單通訊阻止使用一個基本的阻止列表,full-featured隱私協議使用一種更高級的隱私列表。一個隱私列表是一個針對所有交通匹配的規則列表,包括進來的和出去的。如果一個規則匹配一個出去的包,與規則相關的行為將作用于包。例如,考慮如下的隱私列表:
<list name="mylist">
<item type="jid" value="gmz@skh.whu.edu.cn" action="deny" order="1">
<iq/>
<message/>
<presence-out/>
</item>
<item type="group" value="C207" action="deny" order="2">
<presence-in/>
</item>
<item action="allow" order="3"/>
</list>
讓我們來看看如何解析這個成我們的語言:
自gmz@skh.whu.edu.cn的消息和第一條規則匹配。因此,如果你的服務器接收到一個來自你的之前的上司的IQ或消息節,它會忽略這個節或返回一個錯誤。
但是,如果你的服務器接收到一個來自你之前的上司的出席節,并且這個節與第一個隱私規則不匹配,那么你的服務器繼續讓它與第二條規則匹配。由于你不再和你之前的上司一起工作了,所以他不在你名冊里的“工作”組。因此,你的服務器繼續下一條規則(這個例子中的最后一個規則)。你瞧,進來的出席節匹配最后一個規則,因此你的服務器允許這個節通過。現在你可以看到你之前的上司是否在線,但是他不能和你進行通訊!
特定隱私規則的結合為允許和阻止通訊提供了一個強大的工具,因為你的隱私列表可以包含無限數量的按照任何順序的隱私規則(通過item元素標識)。每個給定的規則的行為要么是allow,要么是deny,并且處理節的規則類型是基于一個特定的JabberID、名冊分組名或者出席訂閱狀態。最后,節之間要進行匹配,基于他們是否是消息,是否是進來的出席通告(例如,不包括訂閱相關的出席節),是否是出去的出席通告、是否是IQ或所有的節(包括訂閱相關的節)。在實踐中,這些更高級的阻止和允許的方法提供基本的過濾以代替那些簡單的阻止(盡管付出更大的復雜度代價)。
更多消息拓展機制
這章提供在XMPP中各種與消息相關的擴展的概覽。但這不是全部。下面對一些擴展進行快速瀏覽。請參考所有細節規格,并且確保在你最喜歡的客戶端,服務器或庫檢查支持,因為其中一些尚未得到廣泛實施:
- 可擴展節地址[XEP-0033]讓你在沒有使用聊天室的情況下,同時發送單個消息給多個接收者。
- 高級消息處理[XEP-0079]提供一種控制消息傳遞的方式;例子包括消息終止和阻止消息因為延遲傳遞而被脫機存儲。
- 消息回執[XEP-0184] 基于標題做你所期望做的:它提供一個端到端的機制來決定是否目的接收人真正的接收到了這個消息。
- 消息歸檔[XEP-0136]定義了一種在服務器上存儲消息的技術,而不是將他們存儲到你的本地機子上。這在許多情況下是有用的:也許你正在使用的web客戶端沒有本地存儲,你正在使用的-設備有有限的存儲空間,或你在不同設備之間經常移動,而你想要所有的歷史消息都在一個地方
參考資料:
xmpp系列筆記
xmpp-權威指南(圖書)