引言
目前互聯網產品使用的即時通信協議有這幾種:即時信息和空間協議(IMPP)、空間和即時信息協議(PRIM)、針對即時通訊和空間平衡擴充的進程開始協議SIP(SIMPLE)以及XMPP。PRIM與XMPP、SIMPLE類似,但已經不再使用了。
本次要講的是XMPP,由Openfire實現.
1、Openfire與XMPP
Openfire是開源的實時協作服務器(RTC),它是基於公開協議XMPP(RFC-3920),並在此基礎上實現了XMPP-IM(RFC-3921),擴展了IM功能,對實施協作的各種場景做較全面的考慮,如用戶在線狀態切換、消息訂閱、通知等等,因此可以用來搭建即時通信服務器,其搭建的方法也很簡易。
RFC-3920與RFC-3921的說明:
(1)RFC-3920:XMPP的核心。定義了XMPP協議框架下應用的網絡架構,引入了XMLStream(XML流)與XMLStanza(XML節),並規定XMPP協議在通信過程中使用的XML標簽。使用XML標簽從根本上說是協議開放性與擴展性的需要。此外,在通信的安全方面,把TLS 安全傳輸機制與SASL 認證機制引入到內核,與XMPP進行無縫的連接,為協議的安全性、可靠性奠定了基礎。Core文檔還規定了錯誤的定義及處理、XML 的使用規范、JID(Jabber Identifier,Jabber 標識符)的定義、命名規范等等。所以這是所有基於XMPP協議的應用都必需支持的文檔。
(2)RFC-3921:用戶成功登陸到服務器之后,發布更新自己的在線好友管理、發送即時聊天消息等業務。所有的這些業務都是通過三種基本的XML 節來完成的:IQ Stanza(IQ 節), Presence Stanza(Presence 節), Message Stanza(Message 節)。RFC3921 還對阻塞策略進行了定義,定義是多種阻塞方式。可以說,RFC3921 是RFC3920的充分補充。兩個文檔結合起來,就形成了一個基本的即時通信協議平台,在這個平台上可以開發出各種各樣的應用。
2、Openfire的特點:
(1)超強的擴展能力
XMPP協議,繼承了在XML靈活的擴展性,通過擴展發送擴展信息、或者在原有的信息中增加擴展節點來處理用戶需求。另外,Openfire本身也支持插件開發,開發者可以根據需求,以插件的形式添加所需要的功能,例如好友列表、群成員列表等,而不需要去修改核心的源代碼。
(2)並發能力
Openfire的通信處理基於Apache MINA框架實現,單機可支持上萬的並發,同時也支持集群。
(3)安全性:
XMPP在C2S通信,和S2S通信中都使用TLS協議作為通信通道的加密方法,保證通信的安全
(3)對Web的支持:
Openfire采用內置的jetty作web服務器,可以方便的在上面增加WEB功能。jetty服務器是隨AdminConsolePlugin插件時啟動,通過調用startup()方法。9090為其明文端口,9091為其加密端口。
3、通信機制
1、帳號體系
(1)XMPP服務器的帳號基礎,是域(Domain),例如:org.example.com,它在服務器配置時的時候設置,也是服務器能被訪問到的域名或IP地址。客戶端連接的時候,用這個域去尋找服務器。
(2)JID:XMPP中,任何一個可能進行通信的實體,包括一個用戶、或者一個聊天室,都需要一個JID。
用戶的JID格式為:usre@domain,如:abc@org.example.com
聊天室的JID為:room@conference.domain, 如ABC@conference.org.example.com
一般情況,JID后面還會附帶一個資源名(resource),指代這個客戶端的來源,比如:abc@org.example.com/pc-abc,表示這個JID在一台名為pc-abc的設備上登錄。這是同一個帳號能夠在多個設備中登錄的基礎。
2、通信端口
C-S連接的端口是5222 ,S-S連接的端口為5269,這些端口已經在IANA注冊。
3、通信機制
當客戶端連接上XMPP服務器創建會話時,首先是建立一個TCP長連接,並在這個連接上收發XML流進行協商,協商通過后,服務端與客戶端,可以通過Message、Presence、IQ這三種格式進行數據交換。
這三種數據交換格式,下面逐個做介紹:
(1)Message:基本的消息發送,不要求得到響應,用於即時通信、群組、通知等
結構如下:
<message from='1002@zy-fordestiny' to='1001@zy-fordestiny' xml:lang='en'> <body>Are you OK?</body> </message>
其中:
To : 消息接收方JID。
from : 消息發送方JID
body: 所要發送的消息。
(2)Presence:用來表明用戶的狀態,當用戶離線或改變自己的狀態時,就會在stream的上下文中插入一個Presence元素,來表明自身的狀態.
結構如下:
<presence from='1001@zy-fordestiny' to='1002@zy-fordestiny'> <status> Online </status> </presence>
(3)IQ:一種請求/響應機制,類似於Http的get請求。
結構如下:(以客戶端請求服務器綁定資源為例)
<iq type='set' id='bind_1'> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/> </iq>
iq消息中,type是主要屬性,值包括:
Get :獲取當前域值。
Set :設置或替換get查詢的值。
Result :說明成功的響應了先前的查詢。
Error: 查詢和響應中出現的錯誤。
4、通信示例
下面以一個客戶端登錄IM服務器的例子,說明客戶端與服務的交互過程。其中C-S表示客戶端往服務端發消息,S-C表示服務端往客戶端發消息。
1、初始stream
C-S:
<stream:stream xmlns='jabber:client' to='zy-fordestiny' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' from='10023@zy-fordestiny' xml:lang='en'>
S-C:
<?xml version='1.0' encoding='UTF-8'?> <stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="zy-fordestiny" id="34hpvqh6zz" xml:lang="en" version="1.0">
S-C:通知客戶端STL認證
<stream:features> <starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"></starttls> <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"> <mechanism>PLAIN</mechanism> <mechanism>ANONYMOUS</mechanism> <mechanism>SCRAM-SHA-1</mechanism> <mechanism>CRAM-MD5</mechanism> <mechanism>DIGEST-MD5</mechanism> <mechanism>JIVE-SHAREDSECRET</mechanism> </mechanisms> <compression xmlns="http://jabber.org/features/compress"> <method>zlib</method> </compression> <auth xmlns="http://jabber.org/features/iq-auth"/> <register xmlns="http://jabber.org/features/iq-register"/> </stream:features>
2、TLS協商
C-S:
<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'></starttls>
S-C:通知客戶端可以進行STL協商
<proceed xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>
3、客戶端和服務器嘗試通過已有的TCP連接完成 TLS 握手
4、如果 TLS 握手成功,客戶端初始化一個新的流給服務器
C-S:
<stream:stream xmlns='jabber:client' to='zy-fordestiny' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' from='10023@zy-fordestiny' xml:lang='en'>
S-C:
<?xml version='1.0' encoding='UTF-8'?> <stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="zy-fordestiny" id="34hpvqh6zz" xml:lang="en" version="1.0"> <stream:features> <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"> <mechanism>PLAIN</mechanism> <mechanism>ANONYMOUS</mechanism> <mechanism>SCRAM-SHA-1</mechanism> <mechanism>CRAM-MD5</mechanism> <mechanism>DIGEST-MD5</mechanism> <mechanism>JIVE-SHAREDSECRET</mechanism> </mechanisms> <compression xmlns="http://jabber.org/features/compress"> <method>zlib</method> </compression> <auth xmlns="http://jabber.org/features/iq-auth"/> <register xmlns="http://jabber.org/features/iq-register"/> </stream:features>
4 開始SASL握手
C-S:客戶端選擇一種驗證機制
<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='SCRAM-SHA-1'>biwsbj0xMDAyMyxyPUpkYjNr Vn5mLzYwYH05N0tdfmk2M3Q8Wj14cmdnIy1S</auth>
S-C:
<challenge xmlns="urn:ietf:params:xml:ns:xmpp-sasl"> cj1KZGIza1Z+Zi82MGB9OTdLXX5pNjN0PFo9eHJnZyMtUmQ0ODQ3MGFjLWU4YjAtNG Q3NS05ZDAzLWI4NjE3MTI0ODlhYSxzPU5aQjJ2bzljcjlqNjFINEE5L1dLMWtOMVFjVnVJVFlkLGk9NDA5Ng== </challenge>
C-S:
<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> Yz1iaXdzLHI9SmRiM2tWfmYvNjBgfTk3S11+aTYzdDxaPXhyZ2cjLVJkNDg0NzBh Yy1lOGIwLTRkNzUtOWQwMy1iODYxNzEyNDg5YWEscD11QStXMFBWQnFKWnNWQjVZd2pNRlVESGFGQjQ9 </response>
S-C:通知客戶端驗證成功
<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"> dj02a1l6bkFDVHRLaGNEdVlVR1BlY1FWZ283RzQ9 </success>
5、stream
C-S:客戶端發起一個新的流
<stream:stream xmlns='jabber:client' to='zy-fordestiny' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' from='10023@zy-fordestiny' id='34hpvqh6zz' xml:lang='en'>
S-C:服務器發送一個流頭信息回應客戶端,並附上任何可用的特性,注意,多了session
<?xml version='1.0' encoding='UTF-8'?> <stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="zy-fordestiny" id="34hpvqh6zz" xml:lang="en" version="1.0"> <stream:features> <compression xmlns="http://jabber.org/features/compress"> <method>zlib</method> </compression> <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/> <session xmlns="urn:ietf:params:xml:ns:xmpp-session"> <optional/> </session> <sm xmlns='urn:xmpp:sm:2'/><sm xmlns='urn:xmpp:sm:3'/> </stream:features>
6. 資源綁定,重要!!
C-S:
<iq id='h1q24-3' type='set'> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'> <resource>ZY-fordestiny</resource> </bind> </iq>
S-C:
<iq type="result" id="h1q24-3" to="zy-fordestiny/34hpvqh6zz"> <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"> <jid>10023@zy-fordestiny/ZY-fordestiny</jid> </bind> </iq>
7、獲取花名冊
C-S:
<iq id='h1q24-5' type='get'> <query xmlns='jabber:iq:roster'></query> </iq>
S-C:
<iq type="result" id="h1q24-5" to="10023@zy-fordestiny/ZY-fordestiny"> <query xmlns="jabber:iq:roster"/> </iq>
8、發送disco#items,查詢server所支持的components
C-S:
<iq to='zy-fordestiny' id='h1q24-6' type='get'> <query xmlns='http://jabber.org/protocol/disco#items'></query> </iq>
S-C:
<iq type="result" id="h1q24-6" from="zy-fordestiny" to="10023@zy-fordestiny/ZY-fordestiny"> <query xmlns="http://jabber.org/protocol/disco#items"> <item jid="pubsub.zy-fordestiny" name="Publish-Subscribe service"/> <item jid="conference.zy-fordestiny" name="公共房間"/> <item jid="broadcast.zy-fordestiny" name="Broadcast service"/> <item jid="proxy.zy-fordestiny" name="Socks 5 Bytestreams Proxy"/> </query> </iq>
9、獲取名片,群組
C-S:
<iq id='h1q24-23' type='get'> <vCard xmlns='vcard-temp'/> </iq>
S-C:
<iq type="result" id="h1q24-23" to="10023@zy-fordestiny/ZY-fordestiny"> <vCard xmlns="vcard-temp"/> </iq>
C-S:
<iq id='h1q24-24' type='get'> <sharedgroup xmlns='http://www.jivesoftware.org/protocol/sharedgroup'> </sharedgroup> </iq>
S-C:
<iq type="result" id="h1q24-24" to="10023@zy-fordestiny/ZY-fordestiny"> <sharedgroup xmlns="http://www.jivesoftware.org/protocol/sharedgroup"/> </iq>
10、提交在線狀態
C-S:
<presence id='h1q24-10'> <status>在線</status> <priority>1</priority> <c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://www.igniterealtime.org/projects/smack' ver='njE08O0d+gOu+3R5iJiJSheFRMw='/> </presence>
可以看到,完整的登錄過程,需要經過多次的信息交換。
關於XMPP的其他信息,可以網上查閱資源了解,這里不再贅述。
后面的章節,將一步步的從Openfire的源代碼入手,分析整套系統的運行機制,望對讀者有幫助。
over!