-------------------------------------------------- (一) androidpn-server服务器端启动的理解和分析-----------------------------------------------
在Androidpn的底层主要采用的两大框架mina和openfire两大框架,其中mina主要为底层数据传输的Socket框架。下面简单的介绍一下Socket框架
Apache Mina Server 是一个网络通信应用框架,也就是说,它主要是对基于TCP/IP、UDP/IP协议栈的通信框架(也可以提供JAVA 对象的序列化服务、虚拟机管道通信服务等),Mina 同时提供了网络通信的Server 端、Client 端的封装,无论是哪端,Mina 在整个网通通信结构中都处于如下的位置:
可见Mina 的API 将真正的网络通信与我们的应用程序隔离开来,你只需要关心你要发送、接收的数据以及你的业务逻辑即可。同样的,无论是哪端,Mina 的执行流程如下所示:
(1). IoService:这个接口在一个线程上负责套接字的建立,拥有自己的Selector,监听是否有连接被建立。
(2). IoProcessor:这个接口在另一个线程上负责检查是否有数据在通道上读写,也就是说它也拥有自己的Selector,这是与我们使用JAVA NIO 编码时的一个不同之处,通常在JAVA NIO 编码中,我们都是使用一个Selector,也就是不区分IoService与IoProcessor 两个功能接口。另外,IoProcessor 负责调用注册在IoService 上的过滤器,并在过滤器链之后调用IoHandler。
(3). IoFilter:这个接口定义一组拦截器,这些拦截器可以包括日志输出、黑名单过滤、数据的编码(write 方向)与解码(read 方向)等功能,其中数据的encode 与decode是最为重要的、也是你在使用Mina 时最主要关注的地方。
(4). IoHandler:这个接口负责编写业务逻辑,也就是接收、发送数据的地方。
通过Mina的原理我们研究androidpn的运行流程不能看出,如下:
androidpn-server执行过程如下:
1.spring初始化并启动过程,调用NioSocketAcceptor。
2.NioSocketAcceptor开始执行调用IoProcessor.
3.IoProcessor开始调用FilterChain。FilterChain调用相关的IoFilter的。其中ProtocolCodecFilter的过滤器调用了org.androidpn.server.xmpp.codec.XmppCodecFactory进行编码。
4.XmppIoHandler实现自IoHanlder并调用通过openfire 的XMLLightweightParser解析相关的业务逻辑。
5.根据解析的信息调用xmpp并处理相关信息。
AndroidPN(Android Push Notification) 是一个基于 XMPP 协议的 Java 开源推送通知实现,它包含了完整的客户端和服务端。
AndroidPN基于Openfire下的一些开源项目构建。
AndroidPN服务器包含两个部分,
一个是侦听在5222端口上的XMPP服务,负责与客户端的XMPPConnection类进行通信,作用是用户注册和身份认证,并发送推送通知消息。
另外一部分是Web服务器,采用一个轻量级的HTTP服务器,负责接收用户的Web请求。
最上层包含四个组成部分,分别是SessionManager,Auth Manager,PresenceManager以及Notification Manager。
SessionManager负责管理客户端与服务器之间的会话。
Auth Manager负责客户端用户认证管理。
Presence Manager负责管理客户端用户的登录状态。
NotificationManager负责实现服务器向客户端推送消息功能。
IQHandler:消息处理器抽象类。
IQAuthHandler:权限协议的消息处理类,消息的类型为:jabber:iq:auth
IQRegisterHandler:用户注册的消息处理类,消息类型为: jabber:iq:register
IQRosterHandler:用户消息交互类,消息类型为:jabber:iq:roster
PresenceUpdateHandler:用户状态展现变化处理类。内部调用,不具有类型。
- public class NotificationManager {
- private static final String NOTIFICATION_NAMESPACE = "androidpn:iq:notification";
- private final Log log = LogFactory.getLog(getClass());
- private SessionManager sessionManager;
- /**
- * Constructor.
- */
- public NotificationManager() {
- sessionManager = SessionManager.getInstance();
- }
- /**
- * Broadcasts a newly created notification message to all connected users.
- *
- * @param apiKey the API key
- * @param title the title
- * @param message the message details
- * @param uri the uri
- */
- public void sendBroadcast(String apiKey, String title, String message,
- String uri) {
- log.debug("sendBroadcast()...");
- IQ notificationIQ = createNotificationIQ(apiKey, title, message, uri);
- for (ClientSession session : sessionManager.getSessions()) {
- if (session.getPresence().isAvailable()) {
- notificationIQ.setTo(session.getAddress());
- session.deliver(notificationIQ);
- }
- }
- }
- /**
- * Sends a newly created notification message to the specific user.
- *
- * @param apiKey the API key
- * @param title the title
- * @param message the message details
- * @param uri the uri
- */
- public void sendNotifcationToUser(String apiKey, String username,
- String title, String message, String uri) {
- log.debug("sendNotifcationToUser()...");
- IQ notificationIQ = createNotificationIQ(apiKey, title, message, uri);
- ClientSession session = sessionManager.getSession(username);
- if (session != null) {
- if (session.getPresence().isAvailable()) {
- notificationIQ.setTo(session.getAddress());
- session.deliver(notificationIQ);
- }
- }
- }
- /**
- * Creates a new notification IQ and returns it.
- */
- private IQ createNotificationIQ(String apiKey, String title,
- String message, String uri) {
- Random random = new Random();
- String id = Integer.toHexString(random.nextInt());
- // String id = String.valueOf(System.currentTimeMillis());
- Element notification = DocumentHelper.createElement(QName.get(
- "notification", NOTIFICATION_NAMESPACE));
- notification.addElement("id").setText(id);
- notification.addElement("apiKey").setText(apiKey);
- notification.addElement("title").setText(title);
- notification.addElement("message").setText(message);
- notification.addElement("uri").setText(uri);
- IQ iq = new IQ();
- iq.setType(IQ.Type.set);
- iq.setChildElement(notification);
- return iq;
- }
- }
其中:
发布订阅式的发送消息调用方法:
/**
* Broadcasts a newly created notification message to all connected users.
*
* @param apiKey the API key
* @param title the title
* @param message the message details
* @param uri the uri
*/
public void sendBroadcast(String apiKey, String title, String message,
String uri);
点对点的发送消息调用方法:
/**
* Sends a newly created notification message to the specific user.
*
* @param apiKey the API key
* @param title the title
* @param message the message details
* @param uri the uri
*/
public void sendNotifcationToUser(String apiKey, String username,
String title, String message, String uri);
创建发送消息的方法:
/**
* Creates a new notification IQ and returns it.
*/
private IQ createNotificationIQ(String apiKey, String title,
String message, String uri);
-------------------------------------------------- (三) androidpn-client客户端几个类的说明-----------------------------------------------
在androidpn的客户端几个重要的类:
ServiceManager:管理消息服务和加载相关的配置。
ConnectivityReceiver:处理网络状态的广播。
NotificationReceiver:处理服务端发送的推送消息。
NotificationService:后台服务用户响应服务端的消息。需要在AndroidManifest.xml.注册。
NotificationSettingsActivity:推送信息设置页面。
PersistentConnectionListener:监控连接关闭和重连事件的监听。
PhoneStateChangeListener:监听手机状态的事件监听类。
ReconnectionThread:重连的线程类。
Notifier:客户端发送通知的类。
NotificationIQ:消息的数据包。
ServiceManager中获取属性信息的方法:
- private Properties loadProperties() {
- // InputStream in = null;
- // Properties props = null;
- // try {
- // in = getClass().getResourceAsStream(
- // "/org/androidpn/client/client.properties");
- // if (in != null) {
- // props = new Properties();
- // props.load(in);
- // } else {
- // Log.e(LOGTAG, "Could not find the properties file.");
- // }
- // } catch (IOException e) {
- // Log.e(LOGTAG, "Could not find the properties file.", e);
- // } finally {
- // if (in != null)
- // try {
- // in.close();
- // } catch (Throwable ignore) {
- // }
- // }
- // return props;
- Properties props = new Properties();
- try {
- int id = context.getResources().getIdentifier("androidpn", "raw",
- context.getPackageName());
- props.load(context.getResources().openRawResource(id));
- } catch (Exception e) {
- Log.e(LOGTAG, "Could not find the properties file.", e);
- // e.printStackTrace();
- }
- return props;
- }
SharedPreferences的使用:
- sharedPrefs = context.getSharedPreferences(
- Constants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE);
- Editor editor = sharedPrefs.edit();
- editor.putString(Constants.API_KEY, apiKey);
- editor.putString(Constants.VERSION, version);
- editor.putString(Constants.XMPP_HOST, xmppHost);
- editor.putInt(Constants.XMPP_PORT, Integer.parseInt(xmppPort));
- editor.putString(Constants.CALLBACK_ACTIVITY_PACKAGE_NAME,
- callbackActivityPackageName);
- editor.putString(Constants.CALLBACK_ACTIVITY_CLASS_NAME,
- callbackActivityClassName);
- editor.commit();
获取手机的设备id:
- TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
- // wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
- // connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
- // Get deviceId
- deviceId = telephonyManager.getDeviceId();
Notifier中发送通知的方法:
- // Notification
- Notification notification = new Notification();
- notification.icon = getNotificationIcon();
- notification.defaults = Notification.DEFAULT_LIGHTS;
- if (isNotificationSoundEnabled()) {
- notification.defaults |= Notification.DEFAULT_SOUND;
- }
- if (isNotificationVibrateEnabled()) {
- notification.defaults |= Notification.DEFAULT_VIBRATE;
- }
- notification.flags |= Notification.FLAG_AUTO_CANCEL;
- notification.when = System.currentTimeMillis();
- notification.tickerText = message;
- // Intent intent;
- // if (uri != null
- // && uri.length() > 0
- // && (uri.startsWith("http:") || uri.startsWith("https:")
- // || uri.startsWith("tel:") || uri.startsWith("geo:"))) {
- // intent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
- // } else {
- // String callbackActivityPackageName = sharedPrefs.getString(
- // Constants.CALLBACK_ACTIVITY_PACKAGE_NAME, "");
- // String callbackActivityClassName = sharedPrefs.getString(
- // Constants.CALLBACK_ACTIVITY_CLASS_NAME, "");
- // intent = new Intent().setClassName(callbackActivityPackageName,
- // callbackActivityClassName);
- // intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- // intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
- // }
- Intent intent = new Intent(context,
- NotificationDetailsActivity.class);
- intent.putExtra(Constants.NOTIFICATION_ID, notificationId);
- intent.putExtra(Constants.NOTIFICATION_API_KEY, apiKey);
- intent.putExtra(Constants.NOTIFICATION_TITLE, title);
- intent.putExtra(Constants.NOTIFICATION_MESSAGE, message);
- intent.putExtra(Constants.NOTIFICATION_URI, uri);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
- intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- PendingIntent contentIntent = PendingIntent.getActivity(context, 0,
- intent, PendingIntent.FLAG_UPDATE_CURRENT);
- notification.setLatestEventInfo(context, title, message,
- contentIntent);
- notificationManager.notify(random.nextInt(), notification);
androidpn用户名和密码来源:
XmppManager的注册任务(RegisterTask)中run方法:
if (!xmppManager.isRegistered()) {
final String newUsername = newRandomUUID();
final String newPassword = newRandomUUID();
Registration registration = new Registration();
PacketFilter packetFilter = new AndFilter(new PacketIDFilter(
registration.getPacketID()), new PacketTypeFilter(
IQ.class));
Androidpn用的比较广泛,但Androidpn还不成熟,存在一些BUG。
目前比较困扰大家的BUG主要有:
1.当服务端连续发送多条通知时,客户端都是显示同一条通知内容。
2.服务端重启,客户端也需要重启建立连接。
最后还有一个问题当我们服务器端重启的时候,客户端就无法在连接到服务器了,除非把android后台的服务
关掉,然后重启才行.在XmmpManager中加上如下红色代码就可:
private void addTask(Runnable runnable) {
Log.d(LOGTAG, "addTask(runnable)...");
taskTracker.increase();
synchronized (taskList) {
if (taskList.isEmpty() && !running) {
running = true;
futureTask = taskSubmitter.submit(runnable);
if (futureTask == null) {
taskTracker.decrease();
}
} else {
//解决服务器端重启后,客户端不能成功连接androidpn服务器
runTask();
taskList.add(runnable);
}
}
Log.d(LOGTAG, "addTask(runnable)... done");
}
由于没有经验,有BUG也只能继续GOOGLE,最终发现 http://jclick.iteye.com/blog/1289383 这个TOMCAT版本比较符合项目的需求。在此还是要感谢这位大神,真是造福广大人民群众。在这个版本中主要修复了第二个问题,相信很多朋友都已经看过了这篇BLOG ,但是大神在回复中写错了类名XMPPWriter,所有导致大家并不知道问题是怎么解决的, 其实是修改了org.androidpn.client.XmppManager,org.jivesoftware.smack.PacketWriter(smack源码)这2个类,这个将他2个版本的代码比较一下就可以看出来了,废话了那么多,在此说一下大神解决此问题的方法。代码在
http://phonepush.sinaapp.com/forum.php?mod=viewthread&tid=5&extra=page%3D1 篇帖子中已经贴出来了。在org.androidpn.client.XmppManager的LoginTask方法中加了一行代码getConnection().startKeepAliveThread(xmppManager);跟踪进去发现是开启了一个线程发送心跳,当发送失败时捕获异常,客户端每隔一段时间进行重连。
org.jivesoftware.smack.PacketWriter的run方法
- catch (SocketException e) {
- Log.e("PacketReader", e.toString());
- connection.disconnect();
- xmppManager.startReconnectionThread();
- } catch (IOException e) {
- e.printStackTrace();
- }
getConnection().startKeepAliveThread(xmppManager); 编译就报错,那是因为他用的是第一个版本 ,所有请先下载第二个版本,第二个版本带大神精简过smack源码。 其实心跳机制在官方的asmack中就已经存在,并且在建立XmppConnection的时候就已经启动,但是遗憾的是asmack的开发人员并没有进行异常之后的重连
- catch (Exception e) {
- // Do nothing
- }
然后是第二个问题,我们刚才下载的这个版本并没有处理这个BUG,其实很简单,群里的高手经解决,就是将org.androidpn.client.Notifier中的notify方法的
- PendingIntent contentIntent = PendingIntent.getActivity(context, 0,intent, PendingIntent.FLAG_UPDATE_CURRENT);
- PendingIntent contentIntent = PendingIntent.getActivity(context, random.nextInt(),
- intent, PendingIntent.FLAG_UPDATE_CURRENT);
好了,这2个问题基本上就解决了,本人也只是在此将前辈们的经验写一下,方便大家集中修正BUG。其实在碰到框架BUG时,看看框架的源码还是有帮助,可能会有很多不解,但我觉得多多少少还是能看出一点东西来。以后大家碰到什么问题也可以提出来,大家一起研究讨论,集思广益,总比一个人瞎想的强,有什么好的想法也可以拿出来分享。再次谢谢各位前辈。
在网上androidpn上的BUG基本都解决了,也多亏牛人们顶力相助,灰常感谢啊。在这里要说的问题是手机锁屏后,客户端心跳包不再发送了。由于android也接触不是很久,对一些系统的机制不太了解,经过多次测试与分析,才发现了是由于锁屏后CPU处于睡眠状态,线程都被挂起,所以在服务器端设定的闲置时间内收不到心跳包,强制移除用户下线。
OK问题已经找到了就好办多了,既然是被挂起了我们就只有让心跳一直在跑了,不啰嗦了。既而在网上有找到两种方法,第一种是让系统不睡眠,第二种则是使用AlarmManager来做我们的操作,在这里我是用的第二种方案来解决我们的问题的。但这样可能有点费电,暂时只能这样解决了了,不知道大家有木有更好点的解决办法能说出来大家一起研究研究。
1).
- //申请设备电源锁
- public void acquireWakeLock()
- {
- if (null == mWakeLock)
- {
- PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "NotificationService");
- if (null != mWakeLock)
- {
- mWakeLock.acquire();
- }
- }
- }
- //释放设备电源锁
- public void releaseWakeLock()
- {
- if (null != mWakeLock)
- {
- mWakeLock.release();
- mWakeLock = null;
- }
- }
- //申请设备电源锁
- public void acquireWakeLock()
- {
- if (null == mWakeLock)
- {
- PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "NotificationService");
- if (null != mWakeLock)
- {
- mWakeLock.acquire();
- }
- }
- }
- //释放设备电源锁
- public void releaseWakeLock()
- {
- if (null != mWakeLock)
- {
- mWakeLock.release();
- mWakeLock = null;
- }
- }
2).
-------------------------------------------------- (五) androidpn-server编码和解码解析的过程-----------------------------------------------
在许多网络应用中可能针对传输的数据进行加密操作,接收到数据之后进行解码操作。
在mina中提供许多加密和解密的解析方式:
1.带一定前缀的字符串的解析方式。
2.序列化对象的字符串解析方式。
3.分隔符方式的字符串解析方式。
在mina中提供相关的filterchain支持相关的操作。
Mina的源代码如下:
- package org.apache.mina.filter.codec;
- import org.apache.mina.core.session.IoSession;
- /**
- * Provides {@link ProtocolEncoder} and {@link ProtocolDecoder} which translates
- * binary or protocol specific data into message object and vice versa.
- * <p>
- * Please refer to
- * <a href="../../../../../xref-examples/org/apache/mina/examples/reverser/ReverseProtocolProvider.html"><code>ReverserProtocolProvider</code></a>
- * example.
- *
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
- public interface ProtocolCodecFactory {
- /**
- * Returns a new (or reusable) instance of {@link ProtocolEncoder} which
- * encodes message objects into binary or protocol-specific data.
- */
- ProtocolEncoder getEncoder(IoSession session) throws Exception;
- /**
- * Returns a new (or reusable) instance of {@link ProtocolDecoder} which
- * decodes binary or protocol-specific data into message objects.
- */
- ProtocolDecoder getDecoder(IoSession session) throws Exception;
- }
加密处理类:
- package org.apache.mina.filter.codec;
- import org.apache.mina.core.buffer.IoBuffer;
- import org.apache.mina.core.session.IoSession;
- /**
- * Encodes higher-level message objects into binary or protocol-specific data.
- * MINA invokes {@link #encode(IoSession, Object, ProtocolEncoderOutput)}
- * method with message which is popped from the session write queue, and then
- * the encoder implementation puts encoded messages (typically {@link IoBuffer}s)
- * into {@link ProtocolEncoderOutput} by calling {@link ProtocolEncoderOutput#write(Object)}.
- * <p>
- * Please refer to
- * <a href="../../../../../xref-examples/org/apache/mina/examples/reverser/TextLineEncoder.html"><code>TextLineEncoder</code></a>
- * example.
- *
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- *
- * @see ProtocolEncoderException
- */
- public interface ProtocolEncoder {
- /**
- * Encodes higher-level message objects into binary or protocol-specific data.
- * MINA invokes {@link #encode(IoSession, Object, ProtocolEncoderOutput)}
- * method with message which is popped from the session write queue, and then
- * the encoder implementation puts encoded messages (typically {@link IoBuffer}s)
- * into {@link ProtocolEncoderOutput}.
- *
- * @throws Exception if the message violated protocol specification
- */
- void encode(IoSession session, Object message, ProtocolEncoderOutput out)
- throws Exception;
- /**
- * Releases all resources related with this encoder.
- *
- * @throws Exception if failed to dispose all resources
- */
- void dispose(IoSession session) throws Exception;
- }
解密类如下:
- package org.apache.mina.filter.codec;
- import org.apache.mina.core.buffer.IoBuffer;
- import org.apache.mina.core.session.IoSession;
- /**
- * Decodes binary or protocol-specific data into higher-level message objects.
- * MINA invokes {@link #decode(IoSession, IoBuffer, ProtocolDecoderOutput)}
- * method with read data, and then the decoder implementation puts decoded
- * messages into {@link ProtocolDecoderOutput} by calling
- * {@link ProtocolDecoderOutput#write(Object)}.
- * <p>
- * Please refer to
- * <a href="../../../../../xref-examples/org/apache/mina/examples/reverser/TextLineDecoder.html"><code>TextLineDecoder</code></a>
- * example.
- *
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- *
- * @see ProtocolDecoderException
- */
- public interface ProtocolDecoder {
- /**
- * Decodes binary or protocol-specific content into higher-level message objects.
- * MINA invokes {@link #decode(IoSession, IoBuffer, ProtocolDecoderOutput)}
- * method with read data, and then the decoder implementation puts decoded
- * messages into {@link ProtocolDecoderOutput}.
- *
- * @throws Exception if the read data violated protocol specification
- */
- void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out)
- throws Exception;
- /**
- * Invoked when the specified <tt>session</tt> is closed. This method is useful
- * when you deal with the protocol which doesn't specify the length of a message
- * such as HTTP response without <tt>content-length</tt> header. Implement this
- * method to process the remaining data that {@link #decode(IoSession, IoBuffer, ProtocolDecoderOutput)}
- * method didn't process completely.
- *
- * @throws Exception if the read data violated protocol specification
- */
- void finishDecode(IoSession session, ProtocolDecoderOutput out)
- throws Exception;
- /**
- * Releases all resources related with this decoder.
- *
- * @throws Exception if failed to dispose all resources
- */
- void dispose(IoSession session) throws Exception;
- }
在androidpn中的没有采取任何加密方式,但是提供相关的类org.androidpn.server.xmpp.codec,如果需要可以针对传输的数据进行加密和解密工作。
- package org.androidpn.server.xmpp.codec;
- import org.apache.mina.core.session.IoSession;
- import org.apache.mina.filter.codec.ProtocolCodecFactory;
- import org.apache.mina.filter.codec.ProtocolDecoder;
- import org.apache.mina.filter.codec.ProtocolEncoder;
- /**
- * Factory class that specifies the encode and decoder to use for parsing XMPP stanzas.
- *
- * @author Sehwan Noh ([email protected])
- */
- public class XmppCodecFactory implements ProtocolCodecFactory {
- private final XmppEncoder encoder;
- private final XmppDecoder decoder;
- /**
- * Constructor.
- */
- public XmppCodecFactory() {
- encoder = new XmppEncoder();
- decoder = new XmppDecoder();
- }
- /**
- * Returns a new (or reusable) instance of ProtocolEncoder.
- */
- public ProtocolEncoder getEncoder(IoSession session) throws Exception {
- return encoder;
- }
- /**
- * Returns a new (or reusable) instance of ProtocolDecoder.
- */
- public ProtocolDecoder getDecoder(IoSession session) throws Exception {
- return decoder;
- }
- }
androidpn的解密类:
- package org.androidpn.server.xmpp.codec;
- import org.androidpn.server.xmpp.net.XmppIoHandler;
- import org.apache.mina.core.buffer.IoBuffer;
- import org.apache.mina.core.session.IoSession;
- import org.apache.mina.filter.codec.CumulativeProtocolDecoder;
- import org.apache.mina.filter.codec.ProtocolDecoderOutput;
- import org.jivesoftware.openfire.nio.XMLLightweightParser;
- /**
- * Decoder class that parses ByteBuffers and generates XML stanzas.
- *
- * @author Sehwan Noh ([email protected])
- */
- public class XmppDecoder extends CumulativeProtocolDecoder {
- // private final Log log = LogFactory.getLog(XmppDecoder.class);
- @Override
- public boolean doDecode(IoSession session, IoBuffer in,
- ProtocolDecoderOutput out) throws Exception {
- // log.debug("doDecode(...)...");
- XMLLightweightParser parser = (XMLLightweightParser) session
- .getAttribute(XmppIoHandler.XML_PARSER);
- parser.read(in);
- if (parser.areThereMsgs()) {
- for (String stanza : parser.getMsgs()) {
- out.write(stanza);
- }
- }
- return !in.hasRemaining();
- }
- }
androidpn的加密类:
- package org.androidpn.server.xmpp.codec;
- import org.apache.mina.core.session.IoSession;
- import org.apache.mina.filter.codec.ProtocolEncoder;
- import org.apache.mina.filter.codec.ProtocolEncoderOutput;
- /**
- * Encoder class that does nothing (to the already encoded data).
- *
- * @author Sehwan Noh ([email protected])
- */
- public class XmppEncoder implements ProtocolEncoder {
- // private final Log log = LogFactory.getLog(XmppEncoder.class);
- public void encode(IoSession session, Object message,
- ProtocolEncoderOutput out) throws Exception {
- // log.debug("encode()...");
- }
- public void dispose(IoSession session) throws Exception {
- // log.debug("dispose()...");
- }
- }
最终将编码解析器转换为过滤器使用,具体配置如下:
- <bean class="org.apache.mina.filter.codec.ProtocolCodecFilter">
- <constructor-arg>
- <bean class="org.androidpn.server.xmpp.codec.XmppCodecFactory" />
- </constructor-arg>
- </bean>
-----------------(六)Androidpn-server的添加其他xmpp相关的协议(如查看好友列表等)--------------------------------
曾经有一个同学,在网上问我,如果想androidpn添加额外的xmpp协议的方法在怎么加呢?我当时很迷惑,后来经过一翻仔细研究androidpn发现,其实每一种处理xmpp协议方法,必须有一个Handler实现。具体可以参考org.androidpn.server.xmpp.handler中。
针对每一个Handler对应的xml拥有不同的命名空间,每一个命名空间在xmpp中都有定义,因为传输的xml的格式是一定的。
例如:
IQAuthHandler:命名空间 String NAMESPACE = "jabber:iq:auth";
IQRegisterHandler:命名空间 String NAMESPACE = "jabber:iq:register";
IQRosterHandler:命名空间 String NAMESPACE = "jabber:iq:roster";
同时我们从Handler的实现可以看到每一个handler最好实现对应IQHandler类,但是为辅助类型的Handler那么可以不用,例如androidpn的状态更新处理器类PresenceUpdateHandler,不必要发送相关的消息到客户端。
所以如果要实现xmpp中如查看好友,用户分组等通信协议,那么你可能要实现相关的Handler并使用xmpp协议规定的相关的命名空间。
在androidpn中主要的业务处理类XmppIoHandler可以看出最终消息解析之后分发到IQRouter中。IQRouter用于处理消息的响应的消息。
IQRouter的源代码如下:
- package org.androidpn.server.xmpp.router;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Map;
- import java.util.concurrent.ConcurrentHashMap;
- import org.androidpn.server.xmpp.handler.IQAuthHandler;
- import org.androidpn.server.xmpp.handler.IQHandler;
- import org.androidpn.server.xmpp.handler.IQRegisterHandler;
- import org.androidpn.server.xmpp.handler.IQRosterHandler;
- import org.androidpn.server.xmpp.session.ClientSession;
- import org.androidpn.server.xmpp.session.Session;
- import org.androidpn.server.xmpp.session.SessionManager;
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
- import org.dom4j.Element;
- import org.xmpp.packet.IQ;
- import org.xmpp.packet.JID;
- import org.xmpp.packet.PacketError;
- /**
- * This class is to route IQ packets to their corresponding handler.
- *
- * @author Sehwan Noh ([email protected])
- */
- public class IQRouter {
- private final Log log = LogFactory.getLog(getClass());
- private SessionManager sessionManager;
- private List<IQHandler> iqHandlers = new ArrayList<IQHandler>();
- private Map<String, IQHandler> namespace2Handlers = new ConcurrentHashMap<String, IQHandler>();
- /**
- * Constucts a packet router registering new IQ handlers.
- */
- public IQRouter() {
- sessionManager = SessionManager.getInstance();
- iqHandlers.add(new IQAuthHandler());
- iqHandlers.add(new IQRegisterHandler());
- iqHandlers.add(new IQRosterHandler());
- }
- /**
- * Routes the IQ packet based on its namespace.
- *
- * @param packet the packet to route
- */
- public void route(IQ packet) {
- if (packet == null) {
- throw new NullPointerException();
- }
- JID sender = packet.getFrom();
- ClientSession session = sessionManager.getSession(sender);
- if (session == null
- || session.getStatus() == Session.STATUS_AUTHENTICATED
- || ("jabber:iq:auth".equals(packet.getChildElement()
- .getNamespaceURI())
- || "jabber:iq:register".equals(packet.getChildElement()
- .getNamespaceURI()) || "urn:ietf:params:xml:ns:xmpp-bind"
- .equals(packet.getChildElement().getNamespaceURI()))) {
- handle(packet);
- } else {
- IQ reply = IQ.createResultIQ(packet);
- reply.setChildElement(packet.getChildElement().createCopy());
- reply.setError(PacketError.Condition.not_authorized);
- session.process(reply);
- }
- }
- private void handle(IQ packet) {
- try {
- Element childElement = packet.getChildElement();
- String namespace = null;
- if (childElement != null) {
- namespace = childElement.getNamespaceURI();
- }
- if (namespace == null) {
- if (packet.getType() != IQ.Type.result
- && packet.getType() != IQ.Type.error) {
- log.warn("Unknown packet " + packet);
- }
- } else {
- IQHandler handler = getHandler(namespace);
- if (handler == null) {
- sendErrorPacket(packet,
- PacketError.Condition.service_unavailable);
- } else {
- handler.process(packet);
- }
- }
- } catch (Exception e) {
- log.error("Could not route packet", e);
- Session session = sessionManager.getSession(packet.getFrom());
- if (session != null) {
- IQ reply = IQ.createResultIQ(packet);
- reply.setError(PacketError.Condition.internal_server_error);
- session.process(reply);
- }
- }
- }
- /**
- * Senda the error packet to the original sender
- */
- private void sendErrorPacket(IQ originalPacket,
- PacketError.Condition condition) {
- if (IQ.Type.error == originalPacket.getType()) {
- log.error("Cannot reply an IQ error to another IQ error: "
- + originalPacket);
- return;
- }
- IQ reply = IQ.createResultIQ(originalPacket);
- reply.setChildElement(originalPacket.getChildElement().createCopy());
- reply.setError(condition);
- try {
- PacketDeliverer.deliver(reply);
- } catch (Exception e) {
- // Ignore
- }
- }
- /**
- * Adds a new IQHandler to the list of registered handler.
- *
- * @param handler the IQHandler
- */
- public void addHandler(IQHandler handler) {
- if (iqHandlers.contains(handler)) {
- throw new IllegalArgumentException(
- "IQHandler already provided by the server");
- }
- namespace2Handlers.put(handler.getNamespace(), handler);
- }
- /**
- * Removes an IQHandler from the list of registered handler.
- *
- * @param handler the IQHandler
- */
- public void removeHandler(IQHandler handler) {
- if (iqHandlers.contains(handler)) {
- throw new IllegalArgumentException(
- "Cannot remove an IQHandler provided by the server");
- }
- namespace2Handlers.remove(handler.getNamespace());
- }
- /**
- * Returns an IQHandler with the given namespace.
- */
- private IQHandler getHandler(String namespace) {
- IQHandler handler = namespace2Handlers.get(namespace);
- if (handler == null) {
- for (IQHandler handlerCandidate : iqHandlers) {
- if (namespace.equalsIgnoreCase(handlerCandidate.getNamespace())) {
- handler = handlerCandidate;
- namespace2Handlers.put(namespace, handler);
- break;
- }
- }
- }
- return handler;
- }
- }
由以上的源代码可以看出,IQRouter在加载时候将各种处理器添加到回话管理器中,当消息分发到IQRouter中时候,根据命名空间的不同使用不同的处理处置即可。
在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;
- /**
- * Handles all I/O events fired by MINA.
- *
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- *
- * @see IoHandlerAdapter
- */
- public interface IoHandler {
- /**
- * Invoked from an I/O processor thread when a new connection has been created.
- * Because this method is supposed to be called from the same thread that
- * handles I/O of multiple sessions, please implement this method to perform
- * tasks that consumes minimal amount of time such as socket parameter
- * and user-defined session attribute initialization.
- */
- void sessionCreated(IoSession session) throws Exception;
- /**
- * Invoked when a connection has been opened. This method is invoked after
- * {@link #sessionCreated(IoSession)}. The biggest difference from
- * {@link #sessionCreated(IoSession)} is that it's invoked from other thread
- * than an I/O processor thread once thread model is configured properly.
- */
- void sessionOpened(IoSession session) throws Exception;
- /**
- * Invoked when a connection is closed.
- */
- void sessionClosed(IoSession session) throws Exception;
- /**
- * Invoked with the related {@link IdleStatus} when a connection becomes idle.
- * This method is not invoked if the transport type is UDP; it's a known bug,
- * and will be fixed in 2.0.
- */
- void sessionIdle(IoSession session, IdleStatus status) throws Exception;
- /**
- * Invoked when any exception is thrown by user {@link IoHandler}
- * implementation or by MINA. If <code>cause</code> is an instance of
- * {@link IOException}, MINA will close the connection automatically.
- */
- void exceptionCaught(IoSession session, Throwable cause) throws Exception;
- /**
- * Invoked when a message is received.
- */
- void messageReceived(IoSession session, Object message) throws Exception;
- /**
- * Invoked when a message written by {@link IoSession#write(Object)} is
- * sent out.
- */
- 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;
- /**
- * This class is to create new sessions, destroy sessions and deliver
- * received XML stanzas to the StanzaHandler.
- *
- * @author Sehwan Noh ([email protected])
- */
- 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);
- }
- }
- /**
- * Constructor. Set the server name from server instance.
- */
- protected XmppIoHandler() {
- serverName = XmppServer.getInstance().getServerName();
- }
- /**
- * Invoked from an I/O processor thread when a new connection has been created.
- */
- public void sessionCreated(IoSession session) throws Exception {
- log.debug("sessionCreated()...");
- }
- /**
- * Invoked when a connection has been opened.
- */
- public void sessionOpened(IoSession session) throws Exception {
- log.debug("sessionOpened()...");
- log.debug("remoteAddress=" + session.getRemoteAddress());
- // Create a new XML parser
- XMLLightweightParser parser = new XMLLightweightParser("UTF-8");
- session.setAttribute(XML_PARSER, parser);
- // Create a new connection
- Connection connection = new Connection(session);
- session.setAttribute(CONNECTION, connection);
- session.setAttribute(STANZA_HANDLER, new StanzaHandler(serverName,
- connection));
- }
- /**
- * Invoked when a connection is closed.
- */
- public void sessionClosed(IoSession session) throws Exception {
- log.debug("sessionClosed()...");
- Connection connection = (Connection) session.getAttribute(CONNECTION);
- connection.close();
- }
- /**
- * Invoked with the related IdleStatus when a connection becomes idle.
- */
- 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();
- }
- /**
- * Invoked when any exception is thrown.
- */
- public void exceptionCaught(IoSession session, Throwable cause)
- throws Exception {
- log.debug("exceptionCaught()...");
- log.error(cause);
- }
- /**
- * Invoked when a message is received.
- */
- public void messageReceived(IoSession session, Object message)
- throws Exception {
- log.debug("messageReceived()...");
- log.debug("RCVD: " + message);
- // Get the stanza handler
- StanzaHandler handler = (StanzaHandler) session
- .getAttribute(STANZA_HANDLER);
- // Get the XMPP packet parser
- int hashCode = Thread.currentThread().hashCode();
- XMPPPacketReader parser = parsers.get(hashCode);
- if (parser == null) {
- parser = new XMPPPacketReader();
- parser.setXPPFactory(factory);
- parsers.put(hashCode, parser);
- }
- // The stanza handler processes the message
- 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();
- }
- }
- /**
- * Invoked when a message written by IoSession.write(Object) is sent out.
- */
- 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的客户端。
-- |
-----------------(六)Androidpn-server的添加其他xmpp相关的协议(如查看好友列表等)--------------------------------