Markdown版本筆記 | 個人GitHub首頁 | 個人博客 | 個人微信 | 個人郵箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
Openfire XMPP Smack RTC IM 即時通信 聊天 MDhtml
Demo地址:https://github.com/baiqiantao/OpenFireTest.git
官網
官方文檔
OpenFire下載
java
實時協做服務器
real time collaboration (RTC)
。它使用惟一普遍採用的即時消息開放協議XMPP(Jabber)
。 Openfire很是容易設置和管理,但提供堅如磐石的安全性和性能。功能豐富
的即時消息和跨平臺實時協做服務器
,使用XMPP協議
提供全面的羣聊和即時消息服務
。簡單說,OpenFire 是服務器,XMPP 是協議,Smack 是類庫,Spark 是客戶端。node
GitHub
Flowdalic/asmackandroid
基於 XMPP 協議的 Java 實現
,提供一套可擴展的API
,與 OpenFire 進行通訊。XMPP 客戶端類庫
,能夠實現即時通信和聊天。優勢:git
缺點是其API並不是爲大量併發用戶設計,每一個客戶要1個線程,佔用資源大。github
<message from="admin@myopenfire.com" to="bqt@myopenfire.com">消息內容</message>
Jabber Identifier
或JID
,它用來標示XMPP網絡中的各個XMPP實體。聯繫到用戶
所需的全部信息。網關或者服務器
實體
(好比一個客戶端),固然它也可以表示其餘的實體
(好比在多用戶聊天系統中的一個房間)。jid = [ localpart "@" ] domainpart [ "/" resourcepart ]
,例如:
服務器jabber.org
上的用戶stpeter
。聊天室
的名字,service 是多用戶聊天服務的主機名
。暱稱
。Extensible Messaging and Presence Protocol,可擴展通信和表示協議數據庫
XMPP是一種基於標準通用標記語言的子集XML
的協議,它繼承了在XML環境中靈活的發展性。所以,基於XMPP的應用具備超強的可擴展性
。通過擴展之後的XMPP能夠經過發送擴展的信息來處理用戶的需求,以及在XMPP的頂端創建如內容發佈系統和基於地址的服務等應用程序。並且,XMPP包含了針對服務器端的軟件協議,使之能與另外一個進行通話,這使得開發者更容易創建客戶應用程序或給一個配好系統添加功能。編程
優勢:開放、可擴展、標準、證明可用、分散、安全
缺點 :數據負載太重,沒有二進制傳輸 c#
基本網絡結構緩存
客戶端,服務器,網關
,通訊可以在這三者的任意兩個之間雙向發生。服務器
同時承擔了客戶端信息記錄,鏈接管理和信息的路由功能。網關
承擔着與異構即時通訊系統的互聯互通,異構系統能夠包括SMS,MSN,ICQ等。單客戶端
經過TCP/IP
鏈接到單服務器,而後在之上傳輸XML。XMPP 工做流程
XMPP核心協議通訊的基本模式就是先創建一個stream
,而後協商一堆安全
之類的東西,中間通訊過程就是客戶端發送XML Stanza(節點)
,一個接一個的。服務器根據客戶端發送的信息以及程序的邏輯,發送XML Stanza
給客戶端。可是這個過程並非一問一答的,任什麼時候候都有可能從一方發信給另一方。通訊的最後階段是</stream>
關閉流,關閉TCP/IP
鏈接。
傳輸的內容
傳輸的是與即時通信相關的指令
。在之前這些命令要麼用2進制
的形式發送(好比QQ),要麼用純文本指令加空格加參數加換行
符的方式發送(好比MSN)。而XMPP傳輸的即時通信指令的邏輯與以往相仿,只是協議的形式變成了XML格式
的純文本。這不但使得解析容易了,人也容易閱讀了,方便了開發和查錯。
XMPP 的核心部分就是一個在網絡上分片斷髮送 XML 的流協議
。這個流協議是 XMPP 的即時通信指令的傳遞基礎,能夠說 XMPP 用 TCP 傳的是 XML 流。
真實通信案例
Xmpp協議是創建在xml的基礎上的,因此,看起來,xmpp協議就像一個xml。
客戶端 8049a646c63e65e8 發出去的消息:
<message from='8049a646c63e65e8@oatest.dgcb.com.cn/phone' id='5U6Mk-5' to='903e652d2334628a@oatest.dgcb.com.cn' type='chat'> <body>{"fromId":"8049a646c63e65e8","fromName":"韓大東","messageType":1,"secret":false,"textContent":"你好","toName":"鄭西風","toUserID":"903e652d2334628a"}</body> <request xmlns='urn:xmpp:receipts'/> </message>
客戶端 8049a646c63e65e8 接收到的消息:
<message from="903e652d2334628a@oatest.dgcb.com.cn/phone" id="Bw4c9-4" to="8049a646c63e65e8@oatest.dgcb.com.cn" type="chat"> <body>{"fromId":"903e652d2334628a","fromName":"鄭西風","messageType":1,"secret":false,"textContent":"你好"}</body> <request xmlns="urn:xmpp:receipts"/> <send time="2018-10-19 16:08:21:999" xmlns="icitic:msg:single"/> </message>
其實 XMPP 是一種很相似於http協議的一種數據傳輸協議
,用戶只須要明白它接收的類型,並理解它返回的類型,就能夠很好的利用xmpp來進行數據通信。
目前很多IM應用系統如Google公司的Google Talk
以及Jive Messenger
等開源應用,都是遵循XMPP協議集而設計實現的,這些應用具備很好的互通性。
安裝時除了修改一下安裝路徑,其餘一路Next就Ok了。
安裝完畢後會自動啓動Openfire服務並自動打開 配置頁面 (可能須要手動刷新一下)。也能夠經過雙擊 \Openfire\bin\openfire.exe
或 \Openfire\bin\openfired.exe
啓動Openfire服務後手動打開配置頁面。
而後按照指引設置 Openfire 服務器:
配置服務器域名【127.0.0.1】
選擇數據庫
選擇特性配置,默認便可
設置管理員賬戶【0909082401@163.com】【123456a】
提示安裝完成,點擊登陸管理員控制檯頁面【admin】【123456a】
進入後能夠看到服務器名稱等信息【127.0.0.1】
建立用戶【admin】【baiqiantao】【bqt】【test】
安裝spark客戶端,這個spark僅僅是拿來測試用的。
至此代碼之外的環境已經配置好了。
Xml是由節點構成的,而基於xml的xmpp協議中與通訊有關三個最核心的節(Stanza)是:<message>、<presence>、<iq>
,能夠經過組織不一樣的節來達到各式各樣不一樣的通信目的。接下來就對這些Stanza作一個大體的瞭解。
每一個節都有其屬性,雖然不一樣的節其屬性各有不一樣,可是一些基本的屬性是這些全部的節所共同的如下這些是他們的共同屬性。
from
表示Stanza的發送方,在發送Stanza時,通常來講不推薦設定,服務器會自動設定正確的值
,若是你設定了不正確的值,服務器將會拒收你的Stanza信息。
to
表示Stanza的接收方。這個節點通常是本身設置的,若到達服務器的數據中沒收設置該屬性,則服務器會認爲這條信息是發送給本身的。
type
指定Stanza的類型。
這個節與前兩個不一樣,設置的值不能夠統一而論,不一樣的節有不一樣的設定值,每種Stanza都有固定的幾種可能的設定值。
雖然不一樣節點的type屬性各有不一樣,可是都有一個error類型,表示這是一條錯誤信息,服務器接收到這種類型的信息的時候不須要做出任何的迴應。
id
用於標誌惟一的一條特定信息,表示一個特定的請求。
在
Presence Stanza 用來控制和表示實體的在線狀態,能夠展現離線、在線、離開、不能打擾等複雜狀態,另外,還能被用來創建和結束在線狀態的訂閱。
除了類型信息外,Presence還包含其餘一些可選的屬性:
自定義文本
,例如:外出吃飯優先級
的非負數案例
<presence/>
設定用戶狀態爲在線
<presence type="unavailable"/>
設定用戶狀態爲離線
<presence> <show>away</show> <status>at the ball</status> </presence>
用於顯示用戶狀態的詳細信息。上面的例子代表用戶由於at the ball
在離開狀態。
<show>
標籤在presence節點中最多出現一次,取值能夠爲Presence.Mode
中的某一個。<status>
標籤用於顯示額外信息<presence> <status>touring the countryside</status> <priority>10</priority> </presence>
在這個節中,出現了一個<priority>
標籤,表示如今鏈接的優先級。每一個鏈接能夠設置從-128到127
的優先級,默認是設置爲0
,用戶能夠在這個標籤裏修改相應的優先級。
在線狀態預約
首先咱們來看一個例子:
<presence from="william_duan@jabber.org" to="test_account@jabber.org" type="subscribe"/>
<presence from="test_account@jabber.org" to="william_duan@jabber.org" type="subscribed"/>
經過上述交互,william_duan 就能看到 test_account 的在線狀態,並能接收到 test_account 的在線狀態通知了(例如上線提醒功能)。
Presence.Type
package org.jivesoftware.smack.packet; public enum Presence.Type { available, //【在線,可接收消息】The user is available to receive messages (default). unavailable,//【離線,不可接收消息】The user is unavailable to receive messages. subscribe,//【申請添加對方爲好友】Request subscription to recipient's presence. subscribed, //【贊成對方添加本身爲好友】Grant subscription to sender's presence. unsubscribe, //【刪除好友的申請】Request removal of subscription to sender's presence. unsubscribed,//【拒絕添加對方爲好友】Grant removal of subscription to sender's presence. error,//【錯誤】The presence stanza(/packet) contains an error message. probe,;//【帳號是否存在】A presence probe as defined in section 4.3 of RFC 6121 public static Type fromString(String string) { return Type.valueOf(string.toLowerCase(Locale.US)); } }
Presence.Mode
package org.jivesoftware.smack.packet; public enum Presence.Mode { chat, //【交談中】,Free to chat. available, //【在線】Available (the default). away, //【離開】,Away. xa, //【離開一段時間】,Away for an extended period of time. dnd; //【請勿打擾】,Do not disturb. public static Presence.Mode fromString(String string) { return Presence.Mode.valueOf(string.toLowerCase(Locale.US)); } }
用於在用戶之間傳遞信息,這消息能夠是單純的聊天信息,也能夠某種格式化的信息。
message節點信息是傳遞以後就被忘記的。當消息被送出以後,發送者是無論這個消息是否已經送出或者何時被接收到。可是經過擴展協議,能夠改變這樣一種情況。
案例
私人聊天信息:
<message from="william_duan@jabber.org" to="test_account@jabber.org" type="chat"> <body>Come on</body> <thread>23sdfewtr234weasdf</thread> </message>
多人聊天信息:
<message from="test_account@jabber.org" to="william_duan@jabber.org" type="groupchat"> <body>welcome</body> </message>
上面的兩個例子都包含了一個<type>
標籤,這個標籤代表了消息的類型,能夠取 Message.Type
中的任一值。
<body>
標籤裏面是具體的消息內容。
Message.Type
package org.jivesoftware.smack.packet; public enum Message.Type { normal,//【廣播】(Default) a normal text message used in email like interface. chat,//【單聊】Typically short text message used in line-by-line chat interfaces. groupchat,//【羣聊】Chat message sent to a groupchat server for group chats. headline,//【通知,不須要回應】Text message to be displayed in scrolling marquee滾動選框 displays. error;//【錯誤】indicates a messaging error. error消息爲系統自動發送的,每每是因爲錯誤發送消息 public static Type fromString(String string) { return Type.valueOf(string.toLowerCase(Locale.US)); } }
Info/Query
模式的消息請求,他和Http
協議比較類似。須要有迴應
。get
以及set
請求,就如同http中的GET
以及POST
。result
以及error
兩種迴應。案例
william_duan 請求本身的聯繫人列表:
<iq from="william_duan@jabber.org/study" id="roster1" type="get"> <query xmlns="jabber:iq:roster"/> </iq>
請求發生錯誤:
<iq id="roster1" to="william_duan@jabber.org/study" type="error"> <query xmlns="jabber:iq:roster"/> <error type="cancel"> <feature-not-implemented xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/> </error> </iq>
請求成功,返回 william_duan 的聯繫人列表。每個<item>
標籤表明了一個聯繫人信息:
<iq id="roster1" to="william_duan@jabber.org/study" type="error"> <query xmlns="jabber:iq:roster"/> <item name="one" jid="account_one@jabber.org"/> <item name="two" jid="account_two@jabber.org"/> </iq>
IQ.Type
public enum IQ.Type { get, //【請求消息】The IQ stanza requests information, inquires about what data is needed in order to complete further operations, etc. set, //【設置消息】The IQ stanza provides data that is needed for an operation to be completed, sets new values, replaces existing values, etc. result, //【成功】The IQ stanza is a response to a successful get or set request. error,; //【失敗】The IQ stanza reports an error that has occurred regarding processing or delivery of a get or set request. public static IQ.Type fromString(String string) { return IQ.Type.valueOf(string.toLowerCase(Locale.US)); } }
Demo地址:https://github.com/baiqiantao/OpenFireTest.git
XMPPConnection的鏈接須要首先經過XMPPTCPConnectionConfiguration.builder()
配置你在Openfire設置的配置,而後根據配置構造一個 XMPPTCPConnection ,之後全部操做基本都須要用到這個 XMPPTCPConnection 。
connection = new XMPPTCPConnection(configuration);
經過了上面的配置後,我們能夠登陸Openfire系統了,至關簡單:
XMPPUtils.getConnection().login(username, password);
下面咱們重點分析下登陸過程的報文內容以及一些最經常使用的API。
在創建了Socket後,client會向服務器發出一條xml:
<stream:stream xmlns:stream='http://etherx.jabber.org/streams' from='8049a646c63e65e8@oatest.dgcb.com.cn' to='oatest.dgcb.com.cn' version='1.0' xmlns='jabber:client' xml:lang='en'>
服務器解析到上面的指令後,會返回用於告訴client可選的SASL方式
<?xml version='1.0' encoding='UTF-8'?> <stream:stream xmlns:stream="http://etherx.jabber.org/streams" from="oatest.dgcb.com.cn" id="36ebm4blnf" version="1.0" xmlns="jabber:client" xml:lang="en"> <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>SCRAM-SHA-1</mechanism> <mechanism>CRAM-MD5</mechanism> <mechanism>DIGEST-MD5</mechanism> </mechanisms> <compression xmlns="http://jabber.org/features/compress"> <method>zlib</method> </compression> <ver xmlns="urn:xmpp:features:rosterver"/> <register xmlns="http://jabber.org/features/iq-register"/> </stream:features>
至此,connect 算是完成了,此時會回調 ConnectionListener
的 connected
方法。
XMPPUtils.getConnection().login(username, password);
一、客戶端選擇PLAIN認證方式
<auth mechanism='PLAIN' xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>ADgwNDlhNjQ2YzYzZTY1ZTgAQkRFNEM3QzBGMzdENEZGRTlENDlGNDcwMTdFNUJCRjc= </auth>
服務器經過計算加密後的密碼後,服務器將返回
<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>
二、當客戶端收到以上命令後,將首次發起鏈接的id發送到服務器
<stream:stream xmlns:stream='http://etherx.jabber.org/streams' from='8049a646c63e65e8@oatest.dgcb.com.cn' id='36ebm4blnf' to='oatest.dgcb.com.cn' version='1.0' xmlns='jabber:client' xml:lang='en'>
這時服務器會返回以下內容說明此時已經成功綁定了當前的Socket
<?xml version='1.0' encoding='UTF-8'?> <stream:stream xmlns:stream="http://etherx.jabber.org/streams" from="oatest.dgcb.com.cn" id="36ebm4blnf" version="1.0" xmlns="jabber:client" xml:lang="en"> <stream:features> <compression xmlns="http://jabber.org/features/compress"> <method>zlib</method> </compression> <ver xmlns="urn:xmpp:features:rosterver"/> <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>
三、壓縮
3.一、客戶端在接收到如上的內容後會告訴服務器開啓壓縮
項目中沒有使用壓縮,因此下面的過程不存在,如下爲參考別人的案例
<compress xmlns='http://jabber.org/protocol/compress'><method>zlib</method></compress>
服務器返回
<compressed xmlns='http://jabber.org/protocol/compress'/>
3.二、客戶端收到服務器的響應命令後,從新創建一個Socket,發送指令
<stream:stream xmlns='jabber:client' to='server domain' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' from='username@server domain' id='c997c3a8' xml:lang='en'>
服務器將返回,不知道你有沒有發現,這裏的id仍是那個id
<?xml version='1.0' encoding='UTF-8'?> <stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="im" id="c997c3a8" xml:lang="en" version="1.0"> <stream:features> <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"> <mechanism>PLAIN</mechanism> <mechanism>ANONYMOUS</mechanism> <mechanism>JIVE-SHAREDSECRET</mechanism> </mechanisms> <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/> <session xmlns="urn:ietf:params:xml:ns:xmpp-session"/> </stream:features>
四、客戶端發送綁定Socket的指令:
<iq id='SG6jR-3' type='set'> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'> <resource>phone</resource> </bind> </iq>
服務器返回綁定了具備指定 JID 的客戶端
<iq id="SG6jR-3" to="oatest.dgcb.com.cn/36ebm4blnf" type="result"> <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"> <jid>8049a646c63e65e8@oatest.dgcb.com.cn/phone</jid> </bind> </iq>
五、開啓一個session
項目中沒有開啓一個session的邏輯,因此下面的過程不存在,如下爲參考別人的案例
<iq id='b86j8-6' type='set'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>
這時服務器返回
<iq type="result" id="b86j8-6" to="c997c3a8@im/c997c3a8"/>
六、認證
由於項目中沒有開啓認證,因此這裏沒有報文通信,只有以下日誌:
至此,客戶端的登陸過程算是完成了。
注意,connect 和 login 都是同步操做,因此在
login(username, password)
方法調用之後,若是沒有報異常,就是登錄成功了。
登錄之後接着會自動發送一條獲取通信錄的指令,並會將通信錄緩存起來,因此之後再獲取通信錄時,並不須要訪問網絡。
<iq id='gZYnq-5' type='get'> <query xmlns='jabber:iq:roster'></query> </iq>
服務器將返回
<iq id="SG6jR-5" to="8049a646c63e65e8@oatest.dgcb.com.cn/phone" type="result"> <query ver="-491295515" xmlns="jabber:iq:roster"> <item name="李**" jid="0347a8a25e9074b0@oatest.dgcb.com.cn" subscription="to"/> <item jid="903e652d2334628a@oatest.dgcb.com.cn" subscription="from"/> <item ask="subscribe" jid="28af56d053cbbf3e@oatest.dgcb.com.cn" subscription="none"/> </query> </iq>
此過程完成之後會回調 RosterListener
的 entriesAdded
方法。
雖然已經登陸了,可是還須要告訴服務器本身的狀態,不然服務器不會認爲你是在線狀態,這時你可能就收不到其餘好友發來的消息(咱們咱們項目中有集成離線推送功能,若是沒有告訴服務器你在笑,服務器會走離線消息推送的邏輯。)
XMPPUtils.getConnection().sendStanza(presence);
客戶端發送 presence 消息告訴服務器本身在線:
<presence from='8049a646c63e65e8@oatest.dgcb.com.cn/phone' id='91kqC-27'> <status>IchatMM</status> <priority>0</priority> <c hash='sha-1' node='http://www.igniterealtime.org/projects/smack' ver='NfJ3flI83zSdUDzCEICtbypursw=' xmlns='http://jabber.org/protocol/caps'/> </presence>
服務器響應:
<presence from="c53706e24ce32f72@oatest.dgcb.com.cn/pc" to="8049a646c63e65e8@oatest.dgcb.com.cn/phone"> <priority>0</priority> <c hash="sha-1" node="http://camaya.net/gloox" ver="9ZtEa+bYQasYo2pVBGT9ShIT+Yc=" xmlns="http://jabber.org/protocol/caps"></c> </presence>
收到響應後,會回調 RosterListener
的 presenceChanged
方法,此後,就能夠愉快的玩耍了。
服務器會定時(默認3分鐘)主動發送一條 ping 消息,以肯定客戶端是否在線:
PingManager.getInstanceFor(connection).setPingInterval(60);//ping消息間隔
<iq from="oatest.dgcb.com.cn" id="553-595" to="8049a646c63e65e8@oatest.dgcb.com.cn/phone" type="get"> <ping xmlns="urn:xmpp:ping"/> </iq>
客戶端響應:
<iq id='553-595' to='oatest.dgcb.com.cn' type='result'></iq>
到此,整個登陸流程已經成功了,接下來能夠作一些用戶信息的獲取等操做。
//發送方式一,簡單的發送文本消息 ChatManager.getInstanceFor(XMPPUtils.getConnection()).createChat(to).sendMessage(text); //發送方式二,發送一個Message對象,可包含一些信息,通常使用這種方式 XMPPUtils.getConnection().sendStanza(msg);
除去消息內容後的日誌:
14:51:02.365 客戶端A I/bqt: 【chatCreated】 14:51:02.366 客戶端A D/SMACK: SENT (0) 14:51:02.399 客戶端A D/SMACK: RECV (0) 14:51:02.400 客戶端A I/bqt: 【processPacket】 14:51:02.402 客戶端A I/bqt: 【processMessage】 14:51:02.404 客戶端A D/SMACK: RECV (0) 14:51:02.404 客戶端B D/SMACK: RECV (0) 14:51:02.407 客戶端A I/bqt: 【processPacket】 14:51:02.407 客戶端A I/bqt: 【processMessage】 14:51:02.409 客戶端B I/bqt: 【processPacket】 14:51:02.410 客戶端B I/bqt: 【chatCreated】 14:51:02.411 客戶端B I/bqt: 【processMessage】 14:51:02.412 客戶端B I/bqt: 消息類型:chat
一、客戶端A發送消息:
<message from='8049a646c63e65e8@oatest.dgcb.com.cn/phone' id='nCRIE-44' to='903e652d2334628a@oatest.dgcb.com.cn/phone' type='chat'> <body>你好,我是包青天</body> <thread>6828a752-cfae-4149-9d4d-c8fb83a17175</thread> </message>
客戶端收到服務器的回執(msgId相同):
<message from="903e652d2334628a@oatest.dgcb.com.cn/phone" to="8049a646c63e65e8@oatest.dgcb.com.cn/phone"> <received msgId="nCRIE-44" status="1" time="2018-10-20 14:50:16:566" xmlns="urn:xmpp:receipts"/> </message>
二、而後,客戶端B會收到客戶端A發送的消息(id相同):
<message from="8049a646c63e65e8@oatest.dgcb.com.cn/phone" id="nCRIE-44" to="903e652d2334628a@oatest.dgcb.com.cn/phone" type="chat"> <body>你好,我是包青天</body> <thread>6828a752-cfae-4149-9d4d-c8fb83a17175</thread> <send time="2018-10-20 14:50:16:572" xmlns="icitic:msg:single"/> </message>
客戶端A也會收到的回執消息(id後面拼接了mutisingle):
<message from="8049a646c63e65e8@oatest.dgcb.com.cn" id="nCRIE-44mutisingle" to="8049a646c63e65e8@oatest.dgcb.com.cn" type="chat"> <subject>903e652d2334628a@oatest.dgcb.com.cn</subject> <body>你好,我是包青天</body> <send time="2018-10-20 14:50:16:571" xmlns="icitic:msg:single"/> </message>
implementation 'org.igniterealtime.smack:smack-android:4.1.4' implementation 'org.igniterealtime.smack:smack-tcp:4.1.4' implementation 'org.igniterealtime.smack:smack-im:4.1.4' implementation 'org.igniterealtime.smack:smack-extensions:4.1.4'
public class MainActivity extends ListActivity { private boolean switchUser = false; private EditText etAccount, etPassword, etChat; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String[] array = {"初始化", "登陸", "發送在線狀態消息", "發消息", "獲取好友信息", "建立聊天室", "加入聊天室", "邀請好友進入聊天室", "註銷登陸", "",}; setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, Arrays.asList(array))); etAccount = new EditText(this); etPassword = new EditText(this); etChat = new EditText(this); etAccount.setText(switchUser ? "8049a646c63e65e8" : "903e652d2334628a"); etPassword.setText(switchUser ? "1E6210BB50614D978F4758B2DC9D76C9" : "40C61DE3492C41B1846281833434D997"); etChat.setText(switchUser ? "903e652d2334628a@oatest.dgcb.com.cn/phone" : "8049a646c63e65e8@oatest.dgcb.com.cn/phone"); getListView().addFooterView(etAccount); getListView().addFooterView(etPassword); getListView().addFooterView(etChat);//要聊天的用戶的ID } @Override protected void onListItemClick(ListView l, View v, int position, long id) { String account = etAccount.getText().toString(); String password = etPassword.getText().toString(); String jid = etChat.getText().toString(); new Thread(() -> testApi(position, account, password, jid)).start(); } private void testApi(int position, String account, String password, String jid) { switch (position) { case 0: XMPPUtils.init(account, password);//初始化 break; case 1: XMPPUtils.login(account, password);//登陸 break; case 2: XMPPUtils.setOnLineStatus();//在線 break; case 3: XMPPUtils.sendMessage(account + "@oatest.dgcb.com.cn/phone", jid, "你好,我是包青天");//發消息 break; case 4: XMPPUtils.getMyFriends();//獲取好友信息 break; case 5: XMPPUtils.createMucRoom(jid, "包青天");//建立聊天室 break; case 6: XMPPUtils.joinChatRoom(jid, account);//加入聊天室 break; case 7: XMPPUtils.inviteToTalkRoom(jid, account, password, "快來參加第二十八屆英雄大會");//邀請好友進入聊天室 break; case 8: XMPPUtils.logout();//註銷登陸 break; default: break; } } }
public class XMPPUtils { private static XMPPTCPConnection connection; /** * 初始化 */ public static synchronized void init(CharSequence username, String password) { if (connection == null) { //初始化XMPPTCPConnection相關配置 XMPPTCPConnectionConfiguration configuration = XMPPTCPConnectionConfiguration.builder() .setUsernameAndPassword(username, password)//設置登陸openfire的用戶名和密碼 .setServiceName("oatest.dgcb.com.cn")//設置服務器名稱 .setHost("oatest.dgcb.com.cn")//設置主機地址 .setPort(25222)//設置端口號 .setResource("phone") //默認爲Smack .setDebuggerEnabled(true)//是否查看debug日誌 //********************************************** 如下爲進階配置 ************************************************* .setConnectTimeout(10 * 1000)//設置鏈接超時的最大時間 .setSecurityMode(ConnectionConfiguration.SecurityMode.disabled)//設置安全模式,關閉安全模式 .setCompressionEnabled(false) //開啓通信壓縮,開啓後傳輸的流量將節省90% .setSendPresence(false) .setCustomSSLContext(getSSLContext()) //自定義的TLS登陸 .setHostnameVerifier((hostname, session) -> true) .build(); connection = new XMPPTCPConnection(configuration); connection.setFromMode(XMPPConnection.FromMode.USER); connection.addConnectionListener(new MyConnectionListener()); //監聽connect狀態 connection.addAsyncStanzaListener(new MyStanzaListener(), StanzaTypeFilter.MESSAGE);// 註冊包的監聽器 PingManager.getInstanceFor(connection).setPingInterval(60);//ping消息間隔 //SASL認證 SASLAuthentication.blacklistSASLMechanism("SCRAM-SHA-1"); SASLAuthentication.blacklistSASLMechanism(SASLPlainMechanism.DIGESTMD5); SASLAuthentication.registerSASLMechanism(new SASLPlainMechanism()); Roster.getInstanceFor(connection).addRosterListener(new MyRosterListener()); ChatManager.getInstanceFor(connection).addChatListener(new MyChatManagerListener()); //監聽與聊天相關的事件 MultiUserChatManager.getInstanceFor(connection).addInvitationListener(new MyInvitationListener()); //被邀請監聽 } } private static SSLContext getSSLContext() { SSLContext context = null; try { context = SSLContext.getInstance("TLS"); context.init(null, new TrustManager[]{new TLSUtils.AcceptAllTrustManager()}, new SecureRandom()); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (KeyManagementException e) { e.printStackTrace(); } return context; } public static XMPPTCPConnection getConnection() { return connection; } /** * 登陸 */ public static void login(CharSequence username, String password) { try { if (!XMPPUtils.getConnection().isConnected()) { XMPPUtils.getConnection().connect(); } if (XMPPUtils.getConnection().isConnected()) { Log.i("bqt", "開始登陸"); XMPPUtils.getConnection().login(username, password); Log.i("bqt", "登陸成功"); } else { Log.i("bqt", "登陸失敗"); } } catch (SmackException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (XMPPException e) { e.printStackTrace(); } } /** * 告訴服務器登陸狀態 */ public static void setOnLineStatus() { if (XMPPUtils.getConnection().isAuthenticated()) { try { Presence presence = new Presence(Presence.Type.available); presence.setStatus("IchatMM"); //顯示額外信息,內容根據需求可隨意定製 presence.setPriority(0); //鏈接的優先級 XMPPUtils.getConnection().sendStanza(presence); } catch (SmackException.NotConnectedException e) { e.printStackTrace(); } } } /** * 註銷登陸 */ public static void logout() { if (!XMPPUtils.getConnection().isConnected()) { XMPPUtils.getConnection().disconnect(); } } /** * 發消息 */ public static void sendMessage(String from, String to, String text) { try { ChatManager.getInstanceFor(XMPPUtils.getConnection()).createChat(to).sendMessage(text);//直接發送一條文本 /*Message msg = new Message(to, Message.Type.chat); msg.setStanzaId(System.currentTimeMillis() + ""); msg.setFrom(from); msg.setBody(text); XMPPUtils.getConnection().sendStanza(msg);//發送一個Message對象,可包含一些信息,通常使用後者*/ } catch (SmackException.NotConnectedException e) { e.printStackTrace(); } } /** * 獲取好友信息 */ public static void getMyFriends() { //並不須要訪問網絡,由於在登陸後已經拿到用戶的通信錄了,這裏是直接從緩存中讀取的 Set<RosterEntry> set = Roster.getInstanceFor(XMPPUtils.getConnection()).getEntries(); for (RosterEntry entry : set) { Log.i("bqt", "JID:" + entry.getUser() + ",Name:" + entry.getName()); } } /** * 建立聊天室 */ public static void createMucRoom(String jid, String nickname) { try { MultiUserChat muc = MultiUserChatManager.getInstanceFor(XMPPUtils.getConnection()).getMultiUserChat(jid); muc.create(nickname);//暱稱 Form form = muc.getConfigurationForm(); Form submitForm = form.createAnswerForm(); for (FormField field : form.getFields()) { if (!FormField.Type.hidden.equals(field.getType()) && field.getVariable() != null) { submitForm.setDefaultAnswer(field.getVariable()); } } List<String> list = new ArrayList<>(); list.add("20"); List<String> owners = new ArrayList<>(); owners.add("guochen@192.168.0.245"); submitForm.setAnswer("muc#roomconfig_roomowners", owners); submitForm.setAnswer("muc#roomconfig_maxusers", list); submitForm.setAnswer("muc#roomconfig_roomname", "room01"); submitForm.setAnswer("muc#roomconfig_persistentroom", true); submitForm.setAnswer("muc#roomconfig_membersonly", false); submitForm.setAnswer("muc#roomconfig_allowinvites", true); submitForm.setAnswer("muc#roomconfig_enablelogging", true); submitForm.setAnswer("x-muc#roomconfig_reservednick", true); submitForm.setAnswer("x-muc#roomconfig_canchangenick", false); submitForm.setAnswer("x-muc#roomconfig_registration", false); muc.sendConfigurationForm(submitForm); } catch (XMPPException.XMPPErrorException e) { e.printStackTrace(); } catch (SmackException e) { e.printStackTrace(); } } /** * 加入聊天室 */ public static void joinChatRoom(String jid, String nickname) { try { MultiUserChat muc = MultiUserChatManager.getInstanceFor(XMPPUtils.getConnection()).getMultiUserChat(jid); muc.join(nickname); } catch (SmackException.NoResponseException e) { e.printStackTrace(); } catch (XMPPException.XMPPErrorException e) { e.printStackTrace(); } catch (SmackException.NotConnectedException e) { e.printStackTrace(); } } /** * 邀請好友進入聊天室 */ public static void inviteToTalkRoom(String jid, String nickname, String user, String reason) { try { MultiUserChat muc = MultiUserChatManager.getInstanceFor(XMPPUtils.getConnection()).getMultiUserChat(jid); muc.addInvitationRejectionListener((invitee, rejectReason) -> Log.i("bqt", "拒絕了," + invitee + "," + rejectReason)); muc.join(nickname); muc.invite(user, reason); } catch (SmackException.NotConnectedException e) { e.printStackTrace(); } catch (SmackException.NoResponseException e) { e.printStackTrace(); } catch (XMPPException.XMPPErrorException e) { e.printStackTrace(); } } }
2018-10-19