1.XMPP協議介紹
用持久連接的方式來進行推送。現在比較成熟的及時消息傳遞協議共有四種:可擴展消息與存在協議(XMPP)、即時信息和空間協議(IMPP)、空間和即時信息協議(PRIM)、針對即時通訊和空間平衡擴充的進程開始協議SIP(SIMPLE),而無疑最爲主流就是XMPP協議,它是一種基於XML的傳遞協議,具有很強的靈活性和可擴展性。它的特點是將複雜性從客戶端轉移到了服務器端。XMPP 是一種很類似於http協議的一種數據傳輸協議,它的過程就如同“包裝--〉解包裝”的過程,用戶只需要明白它接受的類型,並理解它返回的類型,就可以很好的利用XMPP來進行數據通訊。
XMPP主要顯著的優點主要有以下幾個方面:
1、 分佈式 任何人都可以運行自己的XMPP服務器,它沒有主服務器
2、 安全性很高。使用SASL及TLS等技術的可靠安全性
3、 開發性 它是開源的,易於進行學習和了解
4、 跨平臺 毋庸置疑,使用的XML進行傳輸的
基於XMPP協議的java開發有一個開源框架,那就是smack,它主要封裝了一些XMPP的實現。而如果把它直接用在Android上是不行的,因爲android缺少了一些java的類庫,於是一個改進版的asmack誕生了,它是專門爲android而改進的android smack。
2.XMPP的工作原理
所有從一個客戶端到另外一個客戶端的數據都必須通過XMPP服務器,不過我們目前做推送的話沒有從一個客戶端到另外一個客戶端的信息,只有客戶端和服務器的交互。交互的過程如下:
1、 客戶端連接到服務器
2、 服務器端利用本地目錄系統的證書對其認證
3、 連接認證之後相互交互
整個服務器端和客戶端的通信是基於一個session(會話)過程,會話開始,首先會指定服務器的端口號,然後把上述提到的信息發送到服務器端,怎麼發送消息的呢?以<stream>根節點的方式開始傳遞,只有在服務器和客戶端關閉的時候纔會發送它的結束標記</stream>。客戶端通過XMPP協議只用做的就是接收消息,而所有其它的操作都交給服務器,比如管理連接、消息保存等等,這樣就很大程度的減輕了客戶端的負擔。那麼客戶端和服務器端的消息迴應是如何實現的?如要通過一個ID來標識,具體細節可以去查看XMPP協議。
在服務器端的源碼中一個org.androidpn.server.xmpp.net.Connection類,主要是代表一個服務器上的XMPP連接,注意只是一個,它可以確保在服務器關閉的時候,發送一個</stream>標記到客戶端,告知連接斷開,需重新連接。
org.androidpn.server.xmpp.session.SessionManager主要用戶管理所有會話,比如連接斷開,刪除session以及建立連接,添加session等等。
而在管理Socket連接的時候,服務器端採用了MINA框架來進行管理,MINA的優點就是改變了我們傳統的管理socket的方式,比如沒建立一個socket開一個線程,而MINA可以實現多個線程管理N多個用戶。在處理高併發的推送上無疑是有巨大的好處的。
3.XMPP的消息格式
XMPP中定義了, 3個頂層XML元素: Message、Presence、IQ,下面針對這三種元素進行介紹。
<Message>
用於在兩個jabber用戶之間發送信息。Jsm(jabber會話管理器)負責滿足所有的消息,不管目標用戶的狀態如何。如果用戶在線jsm立即提交;否則jsm就存儲。
To :標識消息的接收方。
from : 指發送方的名字或標示(id)o
Text: 此元素包含了要提交給目標用戶的信息。
結構如下所示:
<message to= ‘[email protected]/contact’ type =’chat’>
<body> 你好,在忙嗎</body>
</message>
<Presence>
用來表明用戶的狀態,如:online、away、dnd(請勿打擾)等。當用戶離線或改變自己的狀態時,就會在stream的上下文中插入一個Presence元素,來表明自身的狀態.結構如下所示:
<presence>
From =‘lily @ jabber.com/contact’
To = ‘yaoman @ jabber.com/contact'
<status> Online </status>
</presence>
<presence>元素可以取下面幾種值:
Probe :用於向接受消息方法發送特殊的請求
subscribe:當接受方狀態改變時,自動向發送方發送presence信息。
< IQ >
一種請求/響應機制,從一個實體從發送請求,另外一個實體接受請求,並進行響應.例如,client在stream的上下文中插入一個元素,向Server請求得到自己的好友列表,Server返回一個,裏面是請求的結果.
<iq > 主要的屬性是type。包括:
Get :獲取當前域值。
Set :設置或替換get查詢的值。
Result :說明成功的響應了先前的查詢。
Error: 查詢和響應中出現的錯誤。
結構如下所示:
<iq from =‘lily @ jabber.com/contact’id=’1364564666’Type=’result’>
4.asmack源碼解析
1)幾個關鍵類
在asmack中有幾個非常重要的對象XMPPConnection、PacketReader和PacketWriter,XMPPConnection這個類用來連接XMPP服務,從類名上看,Packet+ (Reader/Wirter),而TCP/IP傳輸的數據,叫做Packet(包),asmack使用的是XMPP協議,XMPP簡單講就是使用TCP/IP協議 + XML流協議的組合。所以這個了對象的作用從字面上看應該是,寫包與讀包,作用爲從服務端讀寫數據。
(1).XMPPConnection類
可以使用connect()方法建立與服務器的連接。disconnect()方法斷開與服務器的連接。
public voidconnect() throws XMPPException {
//Stablishes the connection, readers and writers
connectUsingConfiguration(config);
//Automatically makes the login if the user was previouslly connectedsuccessfully
// to theserver and the connection was terminated abruptly
if(connected && wasAuthenticated) {
//Make the login
try {
if(isAnonymous()) {
// Make the anonymous login
loginAnonymously();
}
else {
login(config.getUsername(), config.getPassword(),
config.getResource());
}
packetReader.notifyReconnection();
}
catch(XMPPException e) {
e.printStackTrace();
}
}
}
public void disconnect(PresenceunavailablePresence) {
// If notconnected, ignore this request.
if(packetReader == null || packetWriter == null) {
return;
}
shutdown(unavailablePresence);
if (roster!= null) {
roster.cleanup();
roster= null;
}
wasAuthenticated = false;
packetWriter.cleanup();
packetWriter = null;
packetReader.cleanup();
packetReader = null;
}
(2).PacketWriter類
protected PacketWriter(XMPPConnection connection) {
this.queue = newArrayBlockingQueue<Packet>(500, true);
this.connection =connection;
init();
}
這裏傳入的XMPPConnection對象,對象中包含了需要發送的數據,然後調用PacketWriter中的sendPacket方法往服務器端發送數據。
(3).PacketReader類
protected PacketReader(finalXMPPConnection connection) {
this.connection =connection;
this.init();
}
PacketReader所有的核心邏輯都在一個線程中完成的。
readerThread = new Thread() {
public void run(){
parsePackets(this);
}
};
調用方法parsePackets去解析從服務器端接收的數據,這裏用的是android自帶的XmlPullParser類來實現解析xml數據。
if (eventType == XmlPullParser.START_TAG) {
if(parser.getName().equals("message")) {
processPacket(PacketParserUtils.parseMessage(parser));
}
else if (parser.getName().equals("iq")){
processPacket(PacketParserUtils.parseIQ(parser, connection));
}
else if(parser.getName().equals("presence")) {
processPacket(PacketParserUtils.parsePresence(parser));
}
// We found an openingstream. Record information about it, then notify
// the connectionID lockso that the packet reader startup can finish.
else if(parser.getName().equals("stream"))
(4).PacketParserUtils類
這個是數據包解析工具類,解析客戶端與服務器的各種往來消息數據,比如上面提到的Message、Presence、IQ三種消息。
如
processPacket(PacketParserUtils.parseIQ(parser,connection));
processPacket(PacketParserUtils.parseMessage(parser));
processPacket(PacketParserUtils.parsePresence(parser));
2)建立連接,接收消息的流程
(1).建立連接
connection = new XMPPConnection();
XMPPConnection在這構造函數裏面主要配置ip地址和端口(super(newConnectionConfiguration("169.254.141.109", 9991));)
(2).註冊監聽,開始初始化連接。
connection.addPacketListener(packetListener, packetFilter);
connection.connect();
PacketListener packetListener = new PacketListener() {
@Override
public voidprocessPacket(Packet packet) {
System.out
.println("Activity----processPacket" + packet.toXML());
}
};
PacketFilterpacketFilter = new PacketFilter() {
@Override
public booleanaccept(Packet packet) {
System.out.println("Activity----accept" + packet.toXML());
return true;
}
};
創建包的監聽以及包的過濾,當有消息到時就會廣播到所有註冊的監聽,當然前提是要通過packetFilter的過濾。
public void connect() {
// Stablishes theconnection, readers and writers
connectUsingConfiguration(config);
}
private void connectUsingConfiguration(ConnectionConfigurationconfig) {
String host = config.getHost();
int port =config.getPort();
try {
this.socket = newSocket(host, port);
} catch(UnknownHostException e) {
e.printStackTrace();
} catch (IOExceptione) {
e.printStackTrace();
}
initConnection();
}
通過之前設置的ip和端口,建立socket對象
(3).初始化packetWriter,packetReader對象
對象初始化
packetWriter = newPacketWriter(this);
packetReader = newPacketReader(this);
啓動packetWriter,packetReader線程
// Start the packet writer.This will open a XMPP stream to the server
packetWriter.startup();
// Start the packet reader.The startup() method will block until we
// get an opening streampacket back from server.
packetReader.startup();
(4).接收消息
PacketReader在一個while loop中線程中循環不停的解析、刷新reader對象、同時發送解析過後的各種Packet,解析完成後根據listener的filter分配給不同的listener處理
readerThread = newThread() {
public void run(){
parsePackets(this);
}
};
private void parsePackets(Thread thread) {
try {
int eventType =parser.getEventType();
do {
if (eventType== XmlPullParser.START_TAG) {
if(parser.getName().equals("message")) {
processPacket(PacketParserUtils.parseMessage(parser));
}
else if(parser.getName().equals("iq")) {
processPacket(PacketParserUtils.parseIQ(parser, connection));
}
else if(parser.getName().equals("presence")) {
processPacket(PacketParserUtils.parsePresence(parser));
}
private voidprocessPacket(Packet packet) {
if (packet == null) {
return;
}
// Loop through allcollectors and notify the appropriate ones.
for (PacketCollectorcollector: connection.getPacketCollectors()) {
collector.processPacket(packet);
}
// Deliver the incoming packet to listeners.
listenerExecutor.submit(new ListenerNotification(packet));
}