在androidpn中的没有采取任何加密方式,但是提供相关的类org.androidpn.server.xmpp.codec,如果需要可以针对传输的数据进行加密和解密工作。
------------------------------(七)Androidpn中业务类xmppIoHandler实现分析--------------------------------------
在androidpn中主要采用Mina进行网络通讯,其中Mina中IoHandler用来处理主要的业务逻辑。Mina 中源代码如下:
- package org.apache.mina.core.service;
-
- import java.io.IOException;
-
- import org.apache.mina.core.session.IdleStatus;
- import org.apache.mina.core.session.IoSession;
-
-
-
-
-
-
-
-
- public interface IoHandler {
-
-
-
-
-
-
-
- void sessionCreated(IoSession session) throws Exception;
-
-
-
-
-
-
-
- void sessionOpened(IoSession session) throws Exception;
-
-
-
-
- void sessionClosed(IoSession session) throws Exception;
-
-
-
-
-
-
- void sessionIdle(IoSession session, IdleStatus status) throws Exception;
-
-
-
-
-
-
- void exceptionCaught(IoSession session, Throwable cause) throws Exception;
-
-
-
-
- void messageReceived(IoSession session, Object message) throws Exception;
-
-
-
-
-
- void messageSent(IoSession session, Object message) throws Exception;
- }
Mina中IoHandler可供处理的事件回调
sessionCreate(IoSession)
IoSession对象被创建时的回调,一般用于进行会话初始化操作。注意:与sessionOpened(IoSession)不同,IoSession对象的创建并不意味着对应底层TCP连接的建立,而仅仅代表字面意思:一个IoSession对象被创建出来了。
sessionOpened(IoSession)
IoSession对象被打开时回调。在TCP中,该事件是在TCP连接建立时触发,一般可用于发起连接建立的握手、认证等操作。
sessionIdle(IoSession,IdleStatus)
IoSession对象超时时回调。当一个IoSession对象在指定的超时时常内没有读写事件发生,就会触发该事件,一般可用于通知服务器断开长时间闲置的连接等处理。具体的超时设置可由 IoService.setWriteIdleTime(int) ,IoService.setReadIdleTime(int) ,IoService.setBothIdleTime(int)设置。
messageReceived(IoSession,Object)
当接收到IoSession对Client发送的数据时回调。
messageSent(IoSession,Object)
当发送给IoSession对Client的数据发送成功时回调。
exceptionCaught(IoSession,Throwable)
当会话过程中出现异常时回调,通常用于错误处理。
session.write(Object)方法是一个异步方法,对该方法的调用并不会阻塞,而是向Mina投递一个异步的写操作,并返回一个可用于对已投递异步写操作进行控制的WriteFuture对象。例如:调用WriteFuture的await()或awaitUninterruptibly(),可由同步等待该异步操作的完成。
在I/O处理器中实现业务逻辑的时候,对于简单的情况,一般只需要在messageReceived中对传入的消息进行处理。如果需要写回数据到对等体,用IoSession.write()即可。
另外的情况,client和server的通信协议比较复杂,client是有状态变迁的,这时可用Mina提供的状态机实现,可使用IO处理器的实现更加简单。
androidpn中XmppIoHandler源代码:
- package org.androidpn.server.xmpp.net;
-
- import java.util.Map;
- import java.util.concurrent.ConcurrentHashMap;
-
- import org.androidpn.server.xmpp.XmppServer;
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
- import org.apache.mina.core.service.IoHandler;
- import org.apache.mina.core.session.IdleStatus;
- import org.apache.mina.core.session.IoSession;
- import org.dom4j.io.XMPPPacketReader;
- import org.jivesoftware.openfire.net.MXParser;
- import org.jivesoftware.openfire.nio.XMLLightweightParser;
- import org.xmlpull.v1.XmlPullParserException;
- import org.xmlpull.v1.XmlPullParserFactory;
-
-
-
-
-
-
-
- public class XmppIoHandler implements IoHandler {
-
- private static final Log log = LogFactory.getLog(XmppIoHandler.class);
-
- public static final String XML_PARSER = "XML_PARSER";
-
- private static final String CONNECTION = "CONNECTION";
-
- private static final String STANZA_HANDLER = "STANZA_HANDLER";
-
- private String serverName;
-
- private static Map<Integer, XMPPPacketReader> parsers = new ConcurrentHashMap<Integer, XMPPPacketReader>();
-
- private static XmlPullParserFactory factory = null;
-
- static {
- try {
- factory = XmlPullParserFactory.newInstance(
- MXParser.class.getName(), null);
- factory.setNamespaceAware(true);
- } catch (XmlPullParserException e) {
- log.error("Error creating a parser factory", e);
- }
- }
-
-
-
-
- protected XmppIoHandler() {
- serverName = XmppServer.getInstance().getServerName();
- }
-
-
-
-
- public void sessionCreated(IoSession session) throws Exception {
- log.debug("sessionCreated()...");
- }
-
-
-
-
- public void sessionOpened(IoSession session) throws Exception {
- log.debug("sessionOpened()...");
- log.debug("remoteAddress=" + session.getRemoteAddress());
-
- XMLLightweightParser parser = new XMLLightweightParser("UTF-8");
- session.setAttribute(XML_PARSER, parser);
-
- Connection connection = new Connection(session);
- session.setAttribute(CONNECTION, connection);
- session.setAttribute(STANZA_HANDLER, new StanzaHandler(serverName,
- connection));
- }
-
-
-
-
- public void sessionClosed(IoSession session) throws Exception {
- log.debug("sessionClosed()...");
- Connection connection = (Connection) session.getAttribute(CONNECTION);
- connection.close();
- }
-
-
-
-
- public void sessionIdle(IoSession session, IdleStatus status)
- throws Exception {
- log.debug("sessionIdle()...");
- Connection connection = (Connection) session.getAttribute(CONNECTION);
- if (log.isDebugEnabled()) {
- log.debug("Closing connection that has been idle: " + connection);
- }
- connection.close();
- }
-
-
-
-
- public void exceptionCaught(IoSession session, Throwable cause)
- throws Exception {
- log.debug("exceptionCaught()...");
- log.error(cause);
- }
-
-
-
-
- public void messageReceived(IoSession session, Object message)
- throws Exception {
- log.debug("messageReceived()...");
- log.debug("RCVD: " + message);
-
-
- StanzaHandler handler = (StanzaHandler) session
- .getAttribute(STANZA_HANDLER);
-
-
- int hashCode = Thread.currentThread().hashCode();
- XMPPPacketReader parser = parsers.get(hashCode);
- if (parser == null) {
- parser = new XMPPPacketReader();
- parser.setXPPFactory(factory);
- parsers.put(hashCode, parser);
- }
-
-
- try {
- handler.process((String) message, parser);
- } catch (Exception e) {
- log.error(
- "Closing connection due to error while processing message: "
- + message, e);
- Connection connection = (Connection) session
- .getAttribute(CONNECTION);
- connection.close();
- }
- }
-
-
-
-
- public void messageSent(IoSession session, Object message) throws Exception {
- log.debug("messageSent()...");
- }
-
- }
XmppIoHandler在加载的时候创建相关的xml解析工厂。
sessionOpened:在连接打开时候创建相关的xml的解析器和Handler处理器。
sessionClosed:关闭相关的连接。
sessionIdle:关闭相关的连接。
messageReceived:获取相关的xml解析器和handler处理器处理相关的消息。
最近正在做一个项目,要用到Android的Push技术。目前对于Android的推送技术,用的比较多的还是AndroidPn。由于要对Push的服务器端,进行压力测试。当然,不可能真找几千台手机来测试。所以只能通过PC端模拟AndroidPN的用户端,每个线程代表一个AndroidPN的客户端。
闲话少说,要想在PC端模拟AndroidPN的客户端,不了解
源码是不行的。
Google一下,大致可以找到相关源码的解析。本文是在相关基础上,添加些自己的见解。
Androidpn包含有server和client两个包,server部分可以作为服务器单独运行,也可以嵌入到web项目的servlet中,在tomcat环境中与web项目的其他部分交互。
Server部分的主要包结构如下:
其中org.androidpn.server.dao,org.androidpn.server.model和org.androidpn.server.service为使用hibernate链接
数据库并实现简单的用户登录认证,开发中可以用我们自己的认证模块替换。剩下的包就是推送的主体实现。
接下来逐个包来看:
1.util包中的类用来加载resources中的配置文件,在配置文件中可指定监听端口和ssl证书目录等属性。
2.org.androidpn.server.xmpp包里面定义了一些异常类型,主要是包含有入口类XmppServer,这个类用来启动和停止server程序。
3.org.androidpn.server.xmpp.auth包里面是认证的一些类,我们自己的认证模块可以在这里与androidpn进行结合。
4.org.androidpn.server.xmpp.codec是XMPP协议的XML文件解析包,server收到和发送的消息都要通过这个包来进行xmpp协议编码和解码。
5.org.androidpn.server.xmpp.handler包主要是对消息的处理,我们可以针对不同的消息类型定义自己的handler,
6.org.androidpn.server.xmpp.net包负责维护与client之间的持久连接,并实现了一些传输方式供发送xmpp消息时使用。
7.org.androidpn.server.xmpp.presence里面只包含PresenceManager类,用来维护client的在线状态。
8.org.androidpn.server.xmpp.push包里面的NotificationManager类包含有向client发送消息的接口。
9.org.androidpn.server.xmpp.router包负责将收到的信息包发送到相应的handler进行处理,是一个路由包。
10.org.androidpn.server.xmpp.session包定义了用来表示持久链接的session,每个session包含一条连接的状态信息。
11.org.androidpn.server.xmpp.ssl是对连接进行ssl认证的工具包。(目前服务器使用的是NonSASLAuthentication认证)
跟XMPP协议有关的类:
IQ Presence Message 分别表示XMPP中的<iq>节,<presence>节,<message>节
Packet表示XMPP节的抽象类,IQ Presence Message均是Packet的子类
Element表示XML节中的元素
如果对XMPP协议不太了解,可以先大致了解下XMPP协议中各种xml节的含义。
server发送消息的整个流程主要是:
1. NotificationManager的push接口被调用。
2.使用SessionManager在当前session集合中查找相应的client链接。
3.定义自己的XMPP消息格式并组装。
4.通过相应session,向client发送消息。
在这个流程中我们需要修改的是步骤3,也就是需要定义和组装自己的xmpp消息,以便于将适当的信息传到客户端并便于客户端解析。一个简单的消息组装例子如下:
1: private IQ createCustomizeIQ(String apiKey, String title,
2: String message, String uri) {
3: Random random = new Random();
4: String id = Integer.toHexString(random.nextInt());
5: // String id = String.valueOf(System.currentTimeMillis());
6:
7: Element notification = DocumentHelper.createElement(QName.get(
8: "notification", NOTIFICATION_NAMESPACE));
9: notification.addElement("id").setText(id);
10: notification.addElement("title").setText(title);
11: notification.addElement("message").setText(message);
12: notification.addElement("uri").setText(uri);
13: //自定义IQ的属性
14: notification.addElement("属性名").setText(属性);
15: IQ iq = new IQ();
16: iq.setType(IQ.Type.set);
17: iq.setChildElement(notification);
18:
19: return iq;
20: }
要注意的是在创建element的时候,传入的namespace要和client解析使用的namespace相匹配。
server端接收和处理消息的流程是:
1.connection收到packet,使用tsc.push.server.xmpp.codec解码。
2.router根据packet的namespace等信息,将packet路由到相应的handler。
3.handler进行处理。
相应的router和handler类在androidpn中都有例子可以参考,这里就不贴代码了。开发中只要根据client发送消息的格式,定义自己的router和handler类,然后在PacketRouter中注册router,在IQHandler中注册handler即可。
补充:
PacketRouter注册router的具体步骤
1.即在PacketRouter中添加成员变量,类型为自己定义的XXXRouter(自定义消息
路由器)
2.在PackRouter的构造函数里初始化XXXRouter
3.根据不同的消息类型调用相应的路由器
4.在PacketRouter中添加XXXRouter的路由方法route(XXX xxx)
注:XXX(自定义XMPP节)是Packet的子类
IQHandler注册handler的具体步骤
IQHandler处理信息<iq>节的抽象类,注册IQHandler就是继承IQHandler,重写其中的handleIQ(IQ)方法返回
应答的<iq>节.
IQHandler的process(IQ)即是处理各种IQ,在实际过程中是IQHandler handler = new IQXXXHandler()。在调用
handler.process()就会调用子类的handleIQ(IQ)方法
Client部分的主要包结构如下:
Client这边包含有消息的收发,解析以及持久连接的发起,重连等功能呢,十分强大,我们开发时完全不用管底层的连接,也不用担心断线,可以专注于业务部分的开发。
同时,代码结构也很简单。去除android的Service和BroadCast类以及一些工具类和常量类不谈:
1.NotificationIQ,NotificationIQProvider,NotificationPacketListener三个类负责对收到的Notification格式的消息进行解析和处理,
2.XmppManager是主控制器,NotificationService通过这个类,在后台维护androidpn连接。
3.PersistentConnectionListener,PhoneStateChangeListener,ReconnectionThread.java三个类则负责监听手机的状态并进行断线重连。
我们自定义消息时需要定义3个类:在***IQ中定义消息的实体,在***IQProvider中将消息转化为***IQ实体,在***PacketListener中对实体进行处理,具体的实现可参考NotificationIQ,NotificationIQProvider,NotificationPacketListener三个类。在定义这些类之后,还需要在XmppManager中将这3个类中注册到connection中,代码如下:
//ConnectTask类在XmppManager类里
1: Log.i(LOGTAG, "XMPP connected successfully");
2: // packet provider
3: ProviderManager.getInstance().addIQProvider("notification",
4: "androidpn:iq:notification",new NotificationIQProvider());
//LoginTask类在XmppManager类里
1: // packet filter
2: PacketFilter packetFilter = new PacketTypeFilter(NotificationIQ.class);
3: // packet listener
4: PacketListener packetListener = xmppManager.getNotificationPacketListener();
5: connection.addPacketListener(packetListener, packetFilter);
需要注意的是,注册***IQProvider时,传入的namespace需要和服务端组装消息时使用的namespace一致,才能正确的收到。
以上红色部分,是自己在看这篇文章时,所添加上去的,算是对这篇文章的一点补充…如果不对的地方,请大家指正。