posts - 93,  comments - 2,  trackbacks - 0

--------------------------------------------------(一)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-server服务器端几个类的说明-----------------------------------------------

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消息处理器的类:

     IQHandler:消息处理器抽象类。

     IQAuthHandler:权限协议的消息处理类,消息的类型为:jabber:iq:auth

     IQRegisterHandler:用户注册的消息处理类,消息类型为: jabber:iq:register

     IQRosterHandler:用户消息交互类,消息类型为:jabber:iq:roster

     PresenceUpdateHandler:用户状态展现变化处理类。内部调用,不具有类型。

NotificationManager源代码:
Java代码  收藏代码
  1. public class NotificationManager {
  2.  
  3.     private static final String NOTIFICATION_NAMESPACE = "androidpn:iq:notification"
  4.  
  5.     private final Log log = LogFactory.getLog(getClass()); 
  6.  
  7.     private SessionManager sessionManager; 
  8.  
  9.     /**
  10.      * Constructor.
  11.      */ 
  12.     public NotificationManager() { 
  13.         sessionManager = SessionManager.getInstance(); 
  14.     } 
  15.  
  16.     /**
  17.      * Broadcasts a newly created notification message to all connected users.
  18.      *
  19.      * @param apiKey the API key
  20.      * @param title the title
  21.      * @param message the message details
  22.      * @param uri the uri
  23.      */ 
  24.     public void sendBroadcast(String apiKey, String title, String message, 
  25.             String uri) { 
  26.         log.debug("sendBroadcast()..."); 
  27.         IQ notificationIQ = createNotificationIQ(apiKey, title, message, uri); 
  28.         for (ClientSession session : sessionManager.getSessions()) { 
  29.             if (session.getPresence().isAvailable()) { 
  30.                 notificationIQ.setTo(session.getAddress()); 
  31.                 session.deliver(notificationIQ); 
  32.             } 
  33.         } 
  34.     } 
  35.  
  36.     /**
  37.      * Sends a newly created notification message to the specific user.
  38.      *
  39.      * @param apiKey the API key
  40.      * @param title the title
  41.      * @param message the message details
  42.      * @param uri the uri
  43.      */ 
  44.     public void sendNotifcationToUser(String apiKey, String username, 
  45.             String title, String message, String uri) { 
  46.         log.debug("sendNotifcationToUser()..."); 
  47.         IQ notificationIQ = createNotificationIQ(apiKey, title, message, uri); 
  48.         ClientSession session = sessionManager.getSession(username); 
  49.         if (session != null) { 
  50.             if (session.getPresence().isAvailable()) { 
  51.                 notificationIQ.setTo(session.getAddress()); 
  52.                 session.deliver(notificationIQ); 
  53.             } 
  54.         } 
  55.     } 
  56.  
  57.     /**
  58.      * Creates a new notification IQ and returns it.
  59.      */ 
  60.     private IQ createNotificationIQ(String apiKey, String title, 
  61.             String message, String uri) { 
  62.         Random random = new Random(); 
  63.         String id = Integer.toHexString(random.nextInt()); 
  64.         // String id = String.valueOf(System.currentTimeMillis()); 
  65.  
  66.         Element notification = DocumentHelper.createElement(QName.get( 
  67.                 "notification", NOTIFICATION_NAMESPACE)); 
  68.         notification.addElement("id").setText(id); 
  69.         notification.addElement("apiKey").setText(apiKey); 
  70.         notification.addElement("title").setText(title); 
  71.         notification.addElement("message").setText(message); 
  72.         notification.addElement("uri").setText(uri); 
  73.  
  74.         IQ iq = new IQ(); 
  75.         iq.setType(IQ.Type.set); 
  76.         iq.setChildElement(notification); 
  77.  
  78.         return iq; 
  79.     } 

其中:

    发布订阅式的发送消息调用方法:

    /**
     * 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中获取属性信息的方法:
Java代码  收藏代码
  1. private Properties loadProperties() { 
  2.     //        InputStream in = null; 
  3.     //        Properties props = null; 
  4.     //        try { 
  5.     //            in = getClass().getResourceAsStream( 
  6.     //                    "/org/androidpn/client/client.properties"); 
  7.     //            if (in != null) { 
  8.     //                props = new Properties(); 
  9.     //                props.load(in); 
  10.     //            } else { 
  11.     //                Log.e(LOGTAG, "Could not find the properties file."); 
  12.     //            } 
  13.     //        } catch (IOException e) { 
  14.     //            Log.e(LOGTAG, "Could not find the properties file.", e); 
  15.     //        } finally { 
  16.     //            if (in != null) 
  17.     //                try { 
  18.     //                    in.close(); 
  19.     //                } catch (Throwable ignore) { 
  20.     //                } 
  21.     //        } 
  22.     //        return props; 
  23.  
  24.     Properties props = new Properties(); 
  25.     try
  26.         int id = context.getResources().getIdentifier("androidpn", "raw"
  27.                 context.getPackageName()); 
  28.         props.load(context.getResources().openRawResource(id)); 
  29.     } catch (Exception e) { 
  30.         Log.e(LOGTAG, "Could not find the properties file.", e); 
  31.         // e.printStackTrace(); 
  32.     } 
  33.     return props; 

SharedPreferences的使用:

Java代码  收藏代码
  1. sharedPrefs = context.getSharedPreferences( 
  2.         Constants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); 
  3. Editor editor = sharedPrefs.edit(); 
  4. editor.putString(Constants.API_KEY, apiKey); 
  5. editor.putString(Constants.VERSION, version); 
  6. editor.putString(Constants.XMPP_HOST, xmppHost); 
  7. editor.putInt(Constants.XMPP_PORT, Integer.parseInt(xmppPort)); 
  8. editor.putString(Constants.CALLBACK_ACTIVITY_PACKAGE_NAME, 
  9.         callbackActivityPackageName); 
  10. editor.putString(Constants.CALLBACK_ACTIVITY_CLASS_NAME, 
  11.         callbackActivityClassName); 
  12. editor.commit(); 

获取手机的设备id:

Java代码  收藏代码
  1. TelephonyManager       telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); 
  2.         // wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); 
  3.         // connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); 
  4.  
  5.         // Get deviceId 
  6.         deviceId = telephonyManager.getDeviceId(); 

Notifier中发送通知的方法:

Java代码  收藏代码
  1. // Notification 
  2. Notification notification = new Notification(); 
  3. notification.icon = getNotificationIcon(); 
  4. notification.defaults = Notification.DEFAULT_LIGHTS; 
  5. if (isNotificationSoundEnabled()) { 
  6.     notification.defaults |= Notification.DEFAULT_SOUND; 
  7. if (isNotificationVibrateEnabled()) { 
  8.     notification.defaults |= Notification.DEFAULT_VIBRATE; 
  9. notification.flags |= Notification.FLAG_AUTO_CANCEL; 
  10. notification.when = System.currentTimeMillis(); 
  11. notification.tickerText = message; 
  12.  
  13. //            Intent intent; 
  14. //            if (uri != null 
  15. //                    && uri.length() > 0 
  16. //                    && (uri.startsWith("http:") || uri.startsWith("https:") 
  17. //                            || uri.startsWith("tel:") || uri.startsWith("geo:"))) { 
  18. //                intent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri)); 
  19. //            } else { 
  20. //                String callbackActivityPackageName = sharedPrefs.getString( 
  21. //                        Constants.CALLBACK_ACTIVITY_PACKAGE_NAME, ""); 
  22. //                String callbackActivityClassName = sharedPrefs.getString( 
  23. //                        Constants.CALLBACK_ACTIVITY_CLASS_NAME, ""); 
  24. //                intent = new Intent().setClassName(callbackActivityPackageName, 
  25. //                        callbackActivityClassName); 
  26. //                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 
  27. //                intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); 
  28. //            } 
  29.  
  30. Intent intent = new Intent(context, 
  31.         NotificationDetailsActivity.class); 
  32. intent.putExtra(Constants.NOTIFICATION_ID, notificationId); 
  33. intent.putExtra(Constants.NOTIFICATION_API_KEY, apiKey); 
  34. intent.putExtra(Constants.NOTIFICATION_TITLE, title); 
  35. intent.putExtra(Constants.NOTIFICATION_MESSAGE, message); 
  36. intent.putExtra(Constants.NOTIFICATION_URI, uri); 
  37. intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 
  38. intent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 
  39. intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); 
  40. intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); 
  41. intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 
  42.  
  43. PendingIntent contentIntent = PendingIntent.getActivity(context, 0
  44.         intent, PendingIntent.FLAG_UPDATE_CURRENT); 
  45.  
  46. notification.setLatestEventInfo(context, title, message, 
  47.         contentIntent); 
  48. 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-client常见的bug-----------------------------------------------

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方法
Java代码
  1. catch (SocketException e) {
  2. Log.e("PacketReader", e.toString());
  3. connection.disconnect();
  4. xmppManager.startReconnectionThread();
  5. } catch (IOException e) {
  6. e.printStackTrace();
  7. }
这样就达到了当与服务端失去连接时,客户端能够进行重新连接的效果。后来群里有朋友说在LoginTask方法中加入
getConnection().startKeepAliveThread(xmppManager); 编译就报错,那是因为他用的是第一个版本 ,所有请先下载第二个版本,第二个版本带大神精简过smack源码。 其实心跳机制在官方的asmack中就已经存在,并且在建立XmppConnection的时候就已经启动,但是遗憾的是asmack的开发人员并没有进行异常之后的重连

Java代码
  1. catch (Exception e) {
  2. // Do nothing
  3. }
所有才出现这个困扰大家的问题。

       然后是第二个问题,我们刚才下载的这个版本并没有处理这个BUG,其实很简单,群里的高手经解决,就是将org.androidpn.client.Notifier中的notify方法的

  1. PendingIntent contentIntent = PendingIntent.getActivity(context, 0,intent, PendingIntent.FLAG_UPDATE_CURRENT);
复制代码
改成
  1. PendingIntent contentIntent = PendingIntent.getActivity(context, random.nextInt(),
  2.                     intent, PendingIntent.FLAG_UPDATE_CURRENT);
复制代码

       好了,这2个问题基本上就解决了,本人也只是在此将前辈们的经验写一下,方便大家集中修正BUG。其实在碰到框架BUG时,看看框架的源码还是有帮助,可能会有很多不解,但我觉得多多少少还是能看出一点东西来。以后大家碰到什么问题也可以提出来,大家一起研究讨论,集思广益,总比一个人瞎想的强,有什么好的想法也可以拿出来分享。再次谢谢各位前辈。


在网上androidpn上的BUG基本都解决了,也多亏牛人们顶力相助,灰常感谢啊。在这里要说的问题是手机锁屏后,客户端心跳包不再发送了。由于android也接触不是很久,对一些系统的机制不太了解,经过多次测试与分析,才发现了是由于锁屏后CPU处于睡眠状态,线程都被挂起,所以在服务器端设定的闲置时间内收不到心跳包,强制移除用户下线。

      OK问题已经找到了就好办多了,既然是被挂起了我们就只有让心跳一直在跑了,不啰嗦了。既而在网上有找到两种方法,第一种是让系统不睡眠,第二种则是使用AlarmManager来做我们的操作,在这里我是用的第二种方案来解决我们的问题的。但这样可能有点费电,暂时只能这样解决了了,不知道大家有木有更好点的解决办法能说出来大家一起研究研究。

1).

  1. //申请设备电源锁  
  2.     public void acquireWakeLock() 
  3.     { 
  4.         if (null == mWakeLock) 
  5.         { 
  6.             PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); 
  7.             mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "NotificationService"); 
  8.             if (null != mWakeLock) 
  9.             { 
  10.                 mWakeLock.acquire(); 
  11.             } 
  12.         } 
  13.     } 
  14.      
  15.     //释放设备电源锁  
  16.     public void releaseWakeLock() 
  17.     { 
  18.         if (null != mWakeLock) 
  19.         { 
  20.             mWakeLock.release(); 
  21.             mWakeLock = null
  22.         } 
  23.     } 
Java代码  收藏代码
  1. //申请设备电源锁 
  2.     public void acquireWakeLock() 
  3.     { 
  4.         if (null == mWakeLock) 
  5.         { 
  6.             PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); 
  7.             mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "NotificationService"); 
  8.             if (null != mWakeLock) 
  9.             { 
  10.                 mWakeLock.acquire(); 
  11.             } 
  12.         } 
  13.     } 
  14.      
  15.     //释放设备电源锁 
  16.     public void releaseWakeLock() 
  17.     { 
  18.         if (null != mWakeLock) 
  19.         { 
  20.             mWakeLock.release(); 
  21.             mWakeLock = null
  22.         } 
  23.     } 


2).

 

Java代码 收藏代码
  1. public void registerAlarmManager(){
  2. am = (AlarmManager)getSystemService(ALARM_SERVICE);
  3. pi = PendingIntent.getBroadcast(this, 0, new Intent(this, HeartActionBroadCast.class), Intent.FLAG_ACTIVITY_NEW_TASK);
  4. long now = System.currentTimeMillis();
  5. am.setInexactRepeating(AlarmManager.RTC_WAKEUP, now, 20000, pi);
  6. }

 


--------------------------------------------------(五)androidpn-server编码和解码解析的过程-----------------------------------------------

在许多网络应用中可能针对传输的数据进行加密操作,接收到数据之后进行解码操作。

在mina中提供许多加密和解密的解析方式:

1.带一定前缀的字符串的解析方式。

2.序列化对象的字符串解析方式。

3.分隔符方式的字符串解析方式。

在mina中提供相关的filterchain支持相关的操作。

Mina的源代码如下:

Java代码  收藏代码
  1. package org.apache.mina.filter.codec; 
  2.  
  3. import org.apache.mina.core.session.IoSession; 
  4.  
  5. /**
  6. * Provides {@link ProtocolEncoder} and {@link ProtocolDecoder} which translates
  7. * binary or protocol specific data into message object and vice versa.
  8. * <p>
  9. * Please refer to
  10. * <a href="../../../../../xref-examples/org/apache/mina/examples/reverser/ReverseProtocolProvider.html"><code>ReverserProtocolProvider</code></a>
  11. * example.
  12. *
  13. * @author <a href="http://mina.apache.org">Apache MINA Project</a>
  14. */ 
  15. public interface ProtocolCodecFactory { 
  16.     /**
  17.      * Returns a new (or reusable) instance of {@link ProtocolEncoder} which
  18.      * encodes message objects into binary or protocol-specific data.
  19.      */ 
  20.     ProtocolEncoder getEncoder(IoSession session) throws Exception; 
  21.  
  22.     /**
  23.      * Returns a new (or reusable) instance of {@link ProtocolDecoder} which
  24.      * decodes binary or protocol-specific data into message objects.
  25.      */ 
  26.     ProtocolDecoder getDecoder(IoSession session) throws Exception; 

加密处理类:

Java代码  收藏代码
  1. package org.apache.mina.filter.codec; 
  2.  
  3. import org.apache.mina.core.buffer.IoBuffer; 
  4. import org.apache.mina.core.session.IoSession; 
  5.  
  6. /**
  7. * Encodes higher-level message objects into binary or protocol-specific data.
  8. * MINA invokes {@link #encode(IoSession, Object, ProtocolEncoderOutput)}
  9. * method with message which is popped from the session write queue, and then
  10. * the encoder implementation puts encoded messages (typically {@link IoBuffer}s)
  11. * into {@link ProtocolEncoderOutput} by calling {@link ProtocolEncoderOutput#write(Object)}.
  12. * <p>
  13. * Please refer to
  14. * <a href="../../../../../xref-examples/org/apache/mina/examples/reverser/TextLineEncoder.html"><code>TextLineEncoder</code></a>
  15. * example.
  16. *
  17. * @author <a href="http://mina.apache.org">Apache MINA Project</a>
  18. *
  19. * @see ProtocolEncoderException
  20. */ 
  21. public interface ProtocolEncoder { 
  22.  
  23.     /**
  24.      * Encodes higher-level message objects into binary or protocol-specific data.
  25.      * MINA invokes {@link #encode(IoSession, Object, ProtocolEncoderOutput)}
  26.      * method with message which is popped from the session write queue, and then
  27.      * the encoder implementation puts encoded messages (typically {@link IoBuffer}s)
  28.      * into {@link ProtocolEncoderOutput}.
  29.      *
  30.      * @throws Exception if the message violated protocol specification
  31.      */ 
  32.     void encode(IoSession session, Object message, ProtocolEncoderOutput out) 
  33.             throws Exception; 
  34.  
  35.     /**
  36.      * Releases all resources related with this encoder.
  37.      *
  38.      * @throws Exception if failed to dispose all resources
  39.      */ 
  40.     void dispose(IoSession session) throws Exception; 

解密类如下:

Java代码  收藏代码
  1. package org.apache.mina.filter.codec; 
  2.  
  3. import org.apache.mina.core.buffer.IoBuffer; 
  4. import org.apache.mina.core.session.IoSession; 
  5.  
  6. /**
  7. * Decodes binary or protocol-specific data into higher-level message objects.
  8. * MINA invokes {@link #decode(IoSession, IoBuffer, ProtocolDecoderOutput)}
  9. * method with read data, and then the decoder implementation puts decoded
  10. * messages into {@link ProtocolDecoderOutput} by calling
  11. * {@link ProtocolDecoderOutput#write(Object)}.
  12. * <p>
  13. * Please refer to
  14. * <a href="../../../../../xref-examples/org/apache/mina/examples/reverser/TextLineDecoder.html"><code>TextLineDecoder</code></a>
  15. * example.
  16. *
  17. * @author <a href="http://mina.apache.org">Apache MINA Project</a>
  18. *
  19. * @see ProtocolDecoderException
  20. */ 
  21. public interface ProtocolDecoder { 
  22.     /**
  23.      * Decodes binary or protocol-specific content into higher-level message objects.
  24.      * MINA invokes {@link #decode(IoSession, IoBuffer, ProtocolDecoderOutput)}
  25.      * method with read data, and then the decoder implementation puts decoded
  26.      * messages into {@link ProtocolDecoderOutput}.
  27.      *
  28.      * @throws Exception if the read data violated protocol specification
  29.      */ 
  30.     void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) 
  31.             throws Exception; 
  32.  
  33.     /**
  34.      * Invoked when the specified <tt>session</tt> is closed.  This method is useful
  35.      * when you deal with the protocol which doesn't specify the length of a message
  36.      * such as HTTP response without <tt>content-length</tt> header. Implement this
  37.      * method to process the remaining data that {@link #decode(IoSession, IoBuffer, ProtocolDecoderOutput)}
  38.      * method didn't process completely.
  39.      *
  40.      * @throws Exception if the read data violated protocol specification
  41.      */ 
  42.     void finishDecode(IoSession session, ProtocolDecoderOutput out) 
  43.             throws Exception; 
  44.  
  45.     /**
  46.      * Releases all resources related with this decoder.
  47.      *
  48.      * @throws Exception if failed to dispose all resources
  49.      */ 
  50.     void dispose(IoSession session) throws Exception; 

在androidpn中的没有采取任何加密方式,但是提供相关的类org.androidpn.server.xmpp.codec,如果需要可以针对传输的数据进行加密和解密工作。

Java代码  收藏代码
  1. package org.androidpn.server.xmpp.codec; 
  2.  
  3. import org.apache.mina.core.session.IoSession; 
  4. import org.apache.mina.filter.codec.ProtocolCodecFactory; 
  5. import org.apache.mina.filter.codec.ProtocolDecoder; 
  6. import org.apache.mina.filter.codec.ProtocolEncoder; 
  7.  
  8. /**
  9. * Factory class that specifies the encode and decoder to use for parsing XMPP stanzas.
  10. *
  11. * @author Sehwan Noh (devnoh@gmail.com)
  12. */ 
  13. public class XmppCodecFactory implements ProtocolCodecFactory { 
  14.  
  15.     private final XmppEncoder encoder; 
  16.  
  17.     private final XmppDecoder decoder; 
  18.  
  19.     /**
  20.      * Constructor.
  21.      */ 
  22.     public XmppCodecFactory() { 
  23.         encoder = new XmppEncoder(); 
  24.         decoder = new XmppDecoder(); 
  25.     } 
  26.  
  27.     /**
  28.      * Returns a new (or reusable) instance of ProtocolEncoder.
  29.      */ 
  30.     public ProtocolEncoder getEncoder(IoSession session) throws Exception { 
  31.         return encoder; 
  32.     } 
  33.  
  34.     /**
  35.      * Returns a new (or reusable) instance of ProtocolDecoder.
  36.      */ 
  37.     public ProtocolDecoder getDecoder(IoSession session) throws Exception { 
  38.         return decoder; 
  39.     } 
  40.  

androidpn的解密类:

Java代码  收藏代码
  1. package org.androidpn.server.xmpp.codec; 
  2.  
  3. import org.androidpn.server.xmpp.net.XmppIoHandler; 
  4. import org.apache.mina.core.buffer.IoBuffer; 
  5. import org.apache.mina.core.session.IoSession; 
  6. import org.apache.mina.filter.codec.CumulativeProtocolDecoder; 
  7. import org.apache.mina.filter.codec.ProtocolDecoderOutput; 
  8. import org.jivesoftware.openfire.nio.XMLLightweightParser; 
  9.  
  10. /**
  11. * Decoder class that parses ByteBuffers and generates XML stanzas.
  12. *
  13. * @author Sehwan Noh (devnoh@gmail.com)
  14. */ 
  15. public class XmppDecoder extends CumulativeProtocolDecoder { 
  16.  
  17.     // private final Log log = LogFactory.getLog(XmppDecoder.class); 
  18.  
  19.     @Override 
  20.     public boolean doDecode(IoSession session, IoBuffer in, 
  21.             ProtocolDecoderOutput out) throws Exception { 
  22.         // log.debug("doDecode(...)..."); 
  23.  
  24.         XMLLightweightParser parser = (XMLLightweightParser) session 
  25.                 .getAttribute(XmppIoHandler.XML_PARSER); 
  26.         parser.read(in); 
  27.  
  28.         if (parser.areThereMsgs()) { 
  29.             for (String stanza : parser.getMsgs()) { 
  30.                 out.write(stanza); 
  31.             } 
  32.         } 
  33.         return !in.hasRemaining(); 
  34.     } 
  35.  

androidpn的加密类:

Java代码  收藏代码
  1. package org.androidpn.server.xmpp.codec; 
  2.  
  3. import org.apache.mina.core.session.IoSession; 
  4. import org.apache.mina.filter.codec.ProtocolEncoder; 
  5. import org.apache.mina.filter.codec.ProtocolEncoderOutput; 
  6.  
  7. /**
  8. *  Encoder class that does nothing (to the already encoded data).
  9. *
  10. * @author Sehwan Noh (devnoh@gmail.com)
  11. */ 
  12. public class XmppEncoder implements ProtocolEncoder { 
  13.  
  14.     // private final Log log = LogFactory.getLog(XmppEncoder.class); 
  15.  
  16.     public void encode(IoSession session, Object message, 
  17.             ProtocolEncoderOutput out) throws Exception { 
  18.         // log.debug("encode()..."); 
  19.     } 
  20.  
  21.     public void dispose(IoSession session) throws Exception { 
  22.         // log.debug("dispose()..."); 
  23.     } 
  24.  

   最终将编码解析器转换为过滤器使用,具体配置如下:

Xml代码  收藏代码
  1. <bean class="org.apache.mina.filter.codec.ProtocolCodecFilter"> 
  2.     <constructor-arg> 
  3.         <bean class="org.androidpn.server.xmpp.codec.XmppCodecFactory" /> 
  4.     </constructor-arg> 
  5. </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的源代码如下:

Java代码  收藏代码
  1. package org.androidpn.server.xmpp.router; 
  2.  
  3. import java.util.ArrayList; 
  4. import java.util.List; 
  5. import java.util.Map; 
  6. import java.util.concurrent.ConcurrentHashMap; 
  7.  
  8. import org.androidpn.server.xmpp.handler.IQAuthHandler; 
  9. import org.androidpn.server.xmpp.handler.IQHandler; 
  10. import org.androidpn.server.xmpp.handler.IQRegisterHandler; 
  11. import org.androidpn.server.xmpp.handler.IQRosterHandler; 
  12. import org.androidpn.server.xmpp.session.ClientSession; 
  13. import org.androidpn.server.xmpp.session.Session; 
  14. import org.androidpn.server.xmpp.session.SessionManager; 
  15. import org.apache.commons.logging.Log; 
  16. import org.apache.commons.logging.LogFactory; 
  17. import org.dom4j.Element; 
  18. import org.xmpp.packet.IQ; 
  19. import org.xmpp.packet.JID; 
  20. import org.xmpp.packet.PacketError; 
  21.  
  22. /**
  23. * This class is to route IQ packets to their corresponding handler.
  24. *
  25. * @author Sehwan Noh (devnoh@gmail.com)
  26. */ 
  27. public class IQRouter { 
  28.  
  29.     private final Log log = LogFactory.getLog(getClass()); 
  30.  
  31.     private SessionManager sessionManager; 
  32.  
  33.     private List<IQHandler> iqHandlers = new ArrayList<IQHandler>(); 
  34.  
  35.     private Map<String, IQHandler> namespace2Handlers = new ConcurrentHashMap<String, IQHandler>(); 
  36.  
  37.     /**
  38.      * Constucts a packet router registering new IQ handlers.
  39.      */ 
  40.     public IQRouter() { 
  41.         sessionManager = SessionManager.getInstance(); 
  42.         iqHandlers.add(new IQAuthHandler()); 
  43.         iqHandlers.add(new IQRegisterHandler()); 
  44.         iqHandlers.add(new IQRosterHandler()); 
  45.     } 
  46.  
  47.     /**
  48.      * Routes the IQ packet based on its namespace.
  49.      *
  50.      * @param packet the packet to route
  51.      */ 
  52.     public void route(IQ packet) { 
  53.         if (packet == null) { 
  54.             throw new NullPointerException(); 
  55.         } 
  56.         JID sender = packet.getFrom(); 
  57.         ClientSession session = sessionManager.getSession(sender); 
  58.  
  59.         if (session == null 
  60.                 || session.getStatus() == Session.STATUS_AUTHENTICATED 
  61.                 || ("jabber:iq:auth".equals(packet.getChildElement() 
  62.                         .getNamespaceURI()) 
  63.                         || "jabber:iq:register".equals(packet.getChildElement() 
  64.                                 .getNamespaceURI()) || "urn:ietf:params:xml:ns:xmpp-bind" 
  65.                         .equals(packet.getChildElement().getNamespaceURI()))) { 
  66.             handle(packet); 
  67.         } else
  68.             IQ reply = IQ.createResultIQ(packet); 
  69.             reply.setChildElement(packet.getChildElement().createCopy()); 
  70.             reply.setError(PacketError.Condition.not_authorized); 
  71.             session.process(reply); 
  72.         } 
  73.     } 
  74.  
  75.     private void handle(IQ packet) { 
  76.         try
  77.             Element childElement = packet.getChildElement(); 
  78.             String namespace = null
  79.             if (childElement != null) { 
  80.                 namespace = childElement.getNamespaceURI(); 
  81.             } 
  82.             if (namespace == null) { 
  83.                 if (packet.getType() != IQ.Type.result 
  84.                         && packet.getType() != IQ.Type.error) { 
  85.                     log.warn("Unknown packet " + packet); 
  86.                 } 
  87.             } else
  88.                 IQHandler handler = getHandler(namespace); 
  89.                 if (handler == null) { 
  90.                     sendErrorPacket(packet, 
  91.                             PacketError.Condition.service_unavailable); 
  92.                 } else
  93.                     handler.process(packet); 
  94.                 } 
  95.             } 
  96.  
  97.         } catch (Exception e) { 
  98.             log.error("Could not route packet", e); 
  99.             Session session = sessionManager.getSession(packet.getFrom()); 
  100.             if (session != null) { 
  101.                 IQ reply = IQ.createResultIQ(packet); 
  102.                 reply.setError(PacketError.Condition.internal_server_error); 
  103.                 session.process(reply); 
  104.             } 
  105.         } 
  106.     } 
  107.  
  108.     /**
  109.      * Senda the error packet to the original sender
  110.      */ 
  111.     private void sendErrorPacket(IQ originalPacket, 
  112.             PacketError.Condition condition) { 
  113.         if (IQ.Type.error == originalPacket.getType()) { 
  114.             log.error("Cannot reply an IQ error to another IQ error: " 
  115.                     + originalPacket); 
  116.             return
  117.         } 
  118.         IQ reply = IQ.createResultIQ(originalPacket); 
  119.         reply.setChildElement(originalPacket.getChildElement().createCopy()); 
  120.         reply.setError(condition); 
  121.         try
  122.             PacketDeliverer.deliver(reply); 
  123.         } catch (Exception e) { 
  124.             // Ignore 
  125.         } 
  126.     } 
  127.  
  128.     /**
  129.      * Adds a new IQHandler to the list of registered handler.
  130.      *
  131.      * @param handler the IQHandler
  132.      */ 
  133.     public void addHandler(IQHandler handler) { 
  134.         if (iqHandlers.contains(handler)) { 
  135.             throw new IllegalArgumentException( 
  136.                     "IQHandler already provided by the server"); 
  137.         } 
  138.         namespace2Handlers.put(handler.getNamespace(), handler); 
  139.     } 
  140.  
  141.     /**
  142.      * Removes an IQHandler from the list of registered handler.
  143.      *
  144.      * @param handler the IQHandler
  145.      */ 
  146.     public void removeHandler(IQHandler handler) { 
  147.         if (iqHandlers.contains(handler)) { 
  148.             throw new IllegalArgumentException( 
  149.                     "Cannot remove an IQHandler provided by the server"); 
  150.         } 
  151.         namespace2Handlers.remove(handler.getNamespace()); 
  152.     } 
  153.  
  154.     /**
  155.      * Returns an IQHandler with the given namespace.
  156.      */ 
  157.     private IQHandler getHandler(String namespace) { 
  158.         IQHandler handler = namespace2Handlers.get(namespace); 
  159.         if (handler == null) { 
  160.             for (IQHandler handlerCandidate : iqHandlers) { 
  161.                 if (namespace.equalsIgnoreCase(handlerCandidate.getNamespace())) { 
  162.                     handler = handlerCandidate; 
  163.                     namespace2Handlers.put(namespace, handler); 
  164.                     break
  165.                 } 
  166.             } 
  167.         } 
  168.         return handler; 
  169.     } 
  170.  

       由以上的源代码可以看出,IQRouter在加载时候将各种处理器添加到回话管理器中,当消息分发到IQRouter中时候,根据命名空间的不同使用不同的处理处置即可。

------------------------------(七)Androidpn中业务类xmppIoHandler实现分析--------------------------------------
在androidpn中主要采用Mina进行网络通讯,其中Mina中IoHandler用来处理主要的业务逻辑。

Mina 中源代码如下:

Java代码  收藏代码
  1. package org.apache.mina.core.service; 
  2.  
  3. import java.io.IOException; 
  4.  
  5. import org.apache.mina.core.session.IdleStatus; 
  6. import org.apache.mina.core.session.IoSession; 
  7.  
  8. /**
  9. * Handles all I/O events fired by MINA.
  10. *
  11. * @author <a href="http://mina.apache.org">Apache MINA Project</a>
  12. *
  13. * @see IoHandlerAdapter
  14. */ 
  15. public interface IoHandler { 
  16.     /**
  17.      * Invoked from an I/O processor thread when a new connection has been created.
  18.      * Because this method is supposed to be called from the same thread that
  19.      * handles I/O of multiple sessions, please implement this method to perform
  20.      * tasks that consumes minimal amount of time such as socket parameter
  21.      * and user-defined session attribute initialization.
  22.      */ 
  23.     void sessionCreated(IoSession session) throws Exception; 
  24.  
  25.     /**
  26.      * Invoked when a connection has been opened.  This method is invoked after
  27.      * {@link #sessionCreated(IoSession)}.  The biggest difference from
  28.      * {@link #sessionCreated(IoSession)} is that it's invoked from other thread
  29.      * than an I/O processor thread once thread model is configured properly.
  30.      */ 
  31.     void sessionOpened(IoSession session) throws Exception; 
  32.  
  33.     /**
  34.      * Invoked when a connection is closed.
  35.      */ 
  36.     void sessionClosed(IoSession session) throws Exception; 
  37.  
  38.     /**
  39.      * Invoked with the related {@link IdleStatus} when a connection becomes idle.
  40.      * This method is not invoked if the transport type is UDP; it's a known bug,
  41.      * and will be fixed in 2.0.
  42.      */ 
  43.     void sessionIdle(IoSession session, IdleStatus status) throws Exception; 
  44.  
  45.     /**
  46.      * Invoked when any exception is thrown by user {@link IoHandler}
  47.      * implementation or by MINA.  If <code>cause</code> is an instance of
  48.      * {@link IOException}, MINA will close the connection automatically.
  49.      */ 
  50.     void exceptionCaught(IoSession session, Throwable cause) throws Exception; 
  51.  
  52.     /**
  53.      * Invoked when a message is received.
  54.      */ 
  55.     void messageReceived(IoSession session, Object message) throws Exception; 
  56.  
  57.     /**
  58.      * Invoked when a message written by {@link IoSession#write(Object)} is
  59.      * sent out.
  60.      */ 
  61.     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源代码:

Java代码  收藏代码
  1. package org.androidpn.server.xmpp.net; 
  2.  
  3. import java.util.Map; 
  4. import java.util.concurrent.ConcurrentHashMap; 
  5.  
  6. import org.androidpn.server.xmpp.XmppServer; 
  7. import org.apache.commons.logging.Log; 
  8. import org.apache.commons.logging.LogFactory; 
  9. import org.apache.mina.core.service.IoHandler; 
  10. import org.apache.mina.core.session.IdleStatus; 
  11. import org.apache.mina.core.session.IoSession; 
  12. import org.dom4j.io.XMPPPacketReader; 
  13. import org.jivesoftware.openfire.net.MXParser; 
  14. import org.jivesoftware.openfire.nio.XMLLightweightParser; 
  15. import org.xmlpull.v1.XmlPullParserException; 
  16. import org.xmlpull.v1.XmlPullParserFactory; 
  17.  
  18. /**
  19. * This class is to create new sessions, destroy sessions and deliver
  20. * received XML stanzas to the StanzaHandler.
  21. *
  22. * @author Sehwan Noh (devnoh@gmail.com)
  23. */ 
  24. public class XmppIoHandler implements IoHandler { 
  25.  
  26.     private static final Log log = LogFactory.getLog(XmppIoHandler.class); 
  27.  
  28.     public static final String XML_PARSER = "XML_PARSER"
  29.  
  30.     private static final String CONNECTION = "CONNECTION"
  31.  
  32.     private static final String STANZA_HANDLER = "STANZA_HANDLER"
  33.  
  34.     private String serverName; 
  35.  
  36.     private static Map<Integer, XMPPPacketReader> parsers = new ConcurrentHashMap<Integer, XMPPPacketReader>(); 
  37.  
  38.     private static XmlPullParserFactory factory = null
  39.  
  40.     static
  41.         try
  42.             factory = XmlPullParserFactory.newInstance( 
  43.                     MXParser.class.getName(), null); 
  44.             factory.setNamespaceAware(true); 
  45.         } catch (XmlPullParserException e) { 
  46.             log.error("Error creating a parser factory", e); 
  47.         } 
  48.     } 
  49.  
  50.     /**
  51.      * Constructor. Set the server name from server instance.
  52.      */ 
  53.     protected XmppIoHandler() { 
  54.         serverName = XmppServer.getInstance().getServerName(); 
  55.     } 
  56.  
  57.     /**
  58.      * Invoked from an I/O processor thread when a new connection has been created.
  59.      */ 
  60.     public void sessionCreated(IoSession session) throws Exception { 
  61.         log.debug("sessionCreated()..."); 
  62.     } 
  63.  
  64.     /**
  65.      * Invoked when a connection has been opened.
  66.      */ 
  67.     public void sessionOpened(IoSession session) throws Exception { 
  68.         log.debug("sessionOpened()..."); 
  69.         log.debug("remoteAddress=" + session.getRemoteAddress()); 
  70.         // Create a new XML parser 
  71.         XMLLightweightParser parser = new XMLLightweightParser("UTF-8"); 
  72.         session.setAttribute(XML_PARSER, parser); 
  73.         // Create a new connection 
  74.         Connection connection = new Connection(session); 
  75.         session.setAttribute(CONNECTION, connection); 
  76.         session.setAttribute(STANZA_HANDLER, new StanzaHandler(serverName, 
  77.                 connection)); 
  78.     } 
  79.  
  80.     /**
  81.      * Invoked when a connection is closed.
  82.      */ 
  83.     public void sessionClosed(IoSession session) throws Exception { 
  84.         log.debug("sessionClosed()..."); 
  85.         Connection connection = (Connection) session.getAttribute(CONNECTION); 
  86.         connection.close(); 
  87.     } 
  88.  
  89.     /**
  90.      * Invoked with the related IdleStatus when a connection becomes idle.
  91.      */ 
  92.     public void sessionIdle(IoSession session, IdleStatus status) 
  93.             throws Exception { 
  94.         log.debug("sessionIdle()..."); 
  95.         Connection connection = (Connection) session.getAttribute(CONNECTION); 
  96.         if (log.isDebugEnabled()) { 
  97.             log.debug("Closing connection that has been idle: " + connection); 
  98.         } 
  99.         connection.close(); 
  100.     } 
  101.  
  102.     /**
  103.      * Invoked when any exception is thrown.
  104.      */ 
  105.     public void exceptionCaught(IoSession session, Throwable cause) 
  106.             throws Exception { 
  107.         log.debug("exceptionCaught()..."); 
  108.         log.error(cause); 
  109.     } 
  110.  
  111.     /**
  112.      * Invoked when a message is received.
  113.      */ 
  114.     public void messageReceived(IoSession session, Object message) 
  115.             throws Exception { 
  116.         log.debug("messageReceived()..."); 
  117.         log.debug("RCVD: " + message); 
  118.  
  119.         // Get the stanza handler 
  120.         StanzaHandler handler = (StanzaHandler) session 
  121.                 .getAttribute(STANZA_HANDLER); 
  122.  
  123.         // Get the XMPP packet parser 
  124.         int hashCode = Thread.currentThread().hashCode(); 
  125.         XMPPPacketReader parser = parsers.get(hashCode); 
  126.         if (parser == null) { 
  127.             parser = new XMPPPacketReader(); 
  128.             parser.setXPPFactory(factory); 
  129.             parsers.put(hashCode, parser); 
  130.         } 
  131.  
  132.         // The stanza handler processes the message 
  133.         try
  134.             handler.process((String) message, parser); 
  135.         } catch (Exception e) { 
  136.             log.error( 
  137.                     "Closing connection due to error while processing message: " 
  138.                             + message, e); 
  139.             Connection connection = (Connection) session 
  140.                     .getAttribute(CONNECTION); 
  141.             connection.close(); 
  142.         } 
  143.     } 
  144.  
  145.     /**
  146.      * Invoked when a message written by IoSession.write(Object) is sent out.
  147.      */ 
  148.     public void messageSent(IoSession session, Object message) throws Exception { 
  149.         log.debug("messageSent()..."); 
  150.     } 
  151.  

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(自定义消息路由器)
Image
     2.在PackRouter的构造函数里初始化XXXRouter
Image(1)
     3.根据不同的消息类型调用相应的路由器
Image(2)
     4.在PacketRouter中添加XXXRouter的路由方法route(XXX xxx)
Image(3)
注: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一致,才能正确的收到。
以上红色部分,是自己在看这篇文章时,所添加上去的,算是对这篇文章的一点补充…如果不对的地方,请大家指正。


--

-----------------(六)Androidpn-server的添加其他xmpp相关的协议(如查看好友列表等)--------------------------------

posted on 2013-05-24 17:05 Terry Zou 阅读(5029) 评论(1)  编辑  收藏 所属分类: Androidpn

FeedBack:
# re: androidpn(本文服务器为tomcat)
2013-10-17 20:12 | Deepak Singh
Hi Dear,

I have implemented AndroidPN inside my app. I need to send push Notification every 1 hrs interval. Could you pl guide me to achieve this.


I will be very grateful to you.



Regards,
Deepak Singh  回复  更多评论
  

只有注册用户登录后才能发表评论。


网站导航:
 
<2013年10月>
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

常用链接

留言簿(2)

随笔分类

随笔档案

文章分类

文章档案

相册

收藏夹

Java

搜索

  •  

最新随笔

最新评论

阅读排行榜

评论排行榜