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= ‘lily@jabber.org/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));
}