zoukankan      html  css  js  c++  java
  • AndroidPn源码分析(一)

    好了,开始研究源码了。目前对androidpn,只限于使用过它,跑了一下demo。现在开始研究一下源码。

    (一)入口

    当服务器端启动的时候,控制台会打印一些log,除了spring和hibernate,mina,在最后的几行,就是androidpn的代码了,第一个是XmppServer类。

    在XmppServer中,加载spring的配置文件。这貌似把spring加载配置文件给略了,反正也没有web.xml中提到的application*.xml文件。

    (二)web流程

    启动的时候,也加载了配置中的一些action(springmvc里面,不知道是不是也是这么叫),在页面中,点击各个连接,会执行这些action信息。最主要的是send的那个方法。

        public ModelAndView send(HttpServletRequest request,
                HttpServletResponse response) throws Exception {
            String broadcast = ServletRequestUtils.getStringParameter(request,
                    "broadcast", "Y");
            String username = ServletRequestUtils.getStringParameter(request,
                    "username");
            String title = ServletRequestUtils.getStringParameter(request, "title");
            String message = ServletRequestUtils.getStringParameter(request,
                    "message");
            String uri = ServletRequestUtils.getStringParameter(request, "uri");
    
            String apiKey = Config.getString("apiKey", "");
            logger.debug("apiKey=" + apiKey);
    
            if (broadcast.equalsIgnoreCase("Y")) {
                notificationManager.sendBroadcast(apiKey, title, message, uri);
            } else {
                notificationManager.sendNotifcationToUser(apiKey, username, title,
                        message, uri);
            }
    
            ModelAndView mav = new ModelAndView();
            mav.setViewName("redirect:notification.do");
            return mav;
        }

    上面红色的字体,即是执行的方法,现在开始进入主题了。

    (三)服务流程

    广播的代码:

        public void sendBroadcast(String apiKey, String title, String message,
                String uri) {
            log.debug("sendBroadcast()...");
            log.debug("message content:"+message); //2014.8.7 
            IQ notificationIQ = createNotificationIQ(apiKey, title, message, uri);
            for (ClientSession session : sessionManager.getSessions()) {
                if (session.getPresence().isAvailable()) {
                    notificationIQ.setTo(session.getAddress());
                    session.deliver(notificationIQ);
                }
            }
        }

    在这里,首先是构造一个IQ类型的对象,基于xmpp协议的。

    然后从服务器检查,是否有客户端存在,如果有客户端信息存在,就根据客户端的信息,将IQ发送到客户端。然后就看看客户端是怎么产生的。

    (四)客户端连接

    如果不知道该从哪里看起,那么可以用真机或者模拟器测试一下,看客户端连接的时候,服务器log里打印出了什么东西。

    首先连接的时候,有这么一条首要记录:

    <org.androidpn.server.xmpp.net.XmppIoHandler> : sessionCreated()...

    好了,客户端连接的入口就是这个XmppIoHandler类了。

    其实这个东西,是有配置的。在XmppServer启动的时候,加载spring-config.xml文件,在这个里面,有两个配置:

        <bean id="xmppHandler" class="org.androidpn.server.xmpp.net.XmppIoHandler" />
    
        <bean id="ioAcceptor" class="org.apache.mina.transport.socket.nio.NioSocketAcceptor"
            init-method="bind" destroy-method="unbind">
            <property name="defaultLocalAddress" value=":5222" />
            <property name="handler" ref="xmppHandler" />
            <property name="filterChainBuilder" ref="filterChainBuilder" />
            <property name="reuseAddress" value="true" />
        </bean>

    这个是mina的,这个东西之前没有接触过。但是客户端和服务器之间的通讯连接,是基于这个框架的。百度百科有很精简的一段介绍。

    XmppIoHandler这个类,实现IOHandler接口中的几个方法。关于这几个方法,需要说明一下,不然真的是一头雾水,这里引用了一下网上的资料:

    Handler用来处理MINA触发的I/O事件。IoHandler是一个核心接口,它定义了Filter链末端需要的所有行为。IoHandler接口包含以下方法:
    
        sessionCreated  sessionOpened sessionClosed  sessionIdle  exceptionCaught  messageReceived   messageSent
    
    sessionCreated事件
    一个新的connection被创建时,会触发SessionCreated事件。对于TCP来说,这个事件代表连接的建立;对于UDP来说,它代 表收到了一个UDP数据包。这个方法可以用作初始化session的各种属性,
    也可以用来在一个新建的connection上触发一些一次性的行为。 I
    /O processor线程会调用这个方法,所以在实现该方法时,只加入一些耗时较少的操作,因为I/O processor线程是用来处理多会话的。
    sessionOpened事件 当一个connection打开时会触发sessionOpened事件,这个事件永远在sessionCreated之后触发。如果配置了线程模式,那么这个方法会被非I
    /O processor线程调用。
    sessionClosed事件 当一个session关闭的时候会触发sessionClosed事件。可以将session的清理操作放在这个方法里进行。
    sessionIdle事件 当一个session空闲的时候会触发sessionIdle事件。当使用UDP时该方法将不会被调用。
    exceptionCaught事件 当用户代码或MINA框架抛出异常时,会触发事件事件。如果该异常是一个IOException,那么connection会被关闭。
    messageReceived事件 当接收到消息的时候会触发messageReceived事件。所有的业务处理代码应该写在这里,但要留心你所要的消息类型。
    messageSent事件 当消息已被远端接收到的时候,会触发messageSent事件(调用IoSession.write()发送消息)。

    从服务器的后台log中也可以发现,这些事件的创建顺序:sessionCreated() -- sessionOpened() -- messageReceived() -- messageSent() --  exceptionCaught() .

    在创建session这个里面,并没有做什么事情。

    关于sessionopen:

        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));
        }

    在sessionOpen里面,则做了一些事情。首先IoSession这个类型,从eclipse的自动补全中,可以看到里面有很多属性,包括源码中列出的该session来自的地址ip。在这个里面,作者又给它增加了几个属性:XML_PARSER,CONNECTION,STANZA_HANDLER。三个分别均为对象。关于connection属性,应该是为了服务器发送消息的时候需要的。最后一个字段按字面意思来,是处理xml节点的。总的看来,这个方法是对从客户端发来的连接信息,进行了又一次的封装,增加了一些信息。

    messageReceived(),当客户端连接的时候,服务器是会有消息接收的,收到的是客户端发来的,建立连接的一条消息:

    <stream:stream to="192.168.10.100" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0">
        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();
            }
        }

    这个方法里面,也做了一些事情。首先是拿到在sessionOpen中增加的STANZA_HANDLER对象,然后再拿到XMPP packet的解析器。下面就开始了StanzaHandler类的process方法,开始处理得到的xml信息的东西了。这个貌似是个大事件,还得擦亮枪慢慢来看,这个方法有点长,着实纠结分段看吧:

        public void process(String stanza, XMPPPacketReader reader)
                throws Exception {
            boolean initialStream = stanza.startsWith("<stream:stream");
            if (!sessionCreated || initialStream) {
                if (!initialStream) {
                    return; // Ignore <?xml version="1.0"?>
                }
                if (!sessionCreated) {
                    sessionCreated = true;
                    MXParser parser = reader.getXPPParser();
                    parser.setInput(new StringReader(stanza));
                    createSession(parser);
                } else if (startedTLS) {
                    startedTLS = false;
                    tlsNegotiated();
                }
                return;
            }

    如果发来的信息,不是以<stream:stream开头的,那直接就gg了,返回到 XmppIoHandler类的messageReceived方法,一切就over了。

    里面很多的逻辑判断,如果创建了session或者所得到的节点信息是以<stream:stream开头的,这两个有一个符合条件的话,就会进入下面的判断。

    客户端和服务器连接的时候,if (!sessionCreated)这个会确定执行,因为sessionCreated默认就是false。这里把客户端发来的:

    <stream:stream to="192.168.10.100" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0">

    这段信息,放到了MXParser对象(这些是openfire官方的东西)中,然后调用了createSession方法,这个方法中有下列代码:

      // Create the correct session based on the sent namespace
            String namespace = xpp.getNamespace(null);
            if ("jabber:client".equals(namespace)) {
                session = ClientSession.createSession(serverName, connection, xpp);
                if (session == null) {
                    StringBuilder sb = new StringBuilder(250);
                    sb.append("<?xml version='1.0' encoding='UTF-8'?>");
                    sb.append("<stream:stream from="").append(serverName);
                    sb.append("" id="").append(randomString(5));
                    sb.append("" xmlns="").append(xpp.getNamespace(null));
                    sb.append("" xmlns:stream="").append(
                            xpp.getNamespace("stream"));
                    sb.append("" version="1.0">");
    
                    // bad-namespace-prefix in the response
                    StreamError error = new StreamError(
                            StreamError.Condition.bad_namespace_prefix);
                    sb.append(error.toXML());
                    connection.deliverRawText(sb.toString());
                    connection.close();
                    log
                            .warn("Closing session due to bad_namespace_prefix in stream header: "
                                    + namespace);
                }
            }

    原本注释里面说这个是根据发送来的命名空间,来建立正确的session。即是这句:

    session = ClientSession.createSession(serverName, connection, xpp);

    这个是静态类,当进入这个方法时,我们就可以看到在控制台输出的一条log信息。
    这里对connection做了一些处理,设置语言还有xmpp的版本信息。然后才是真正的创建ClientSession:

     ClientSession session = SessionManager.getInstance()
                    .createClientSession(connection);


     

    
    
    
  • 相关阅读:
    CentOS5.6下SVN的安装
    在servlet中的init方法中使用getInitParameter方法空指针错误
    Linux iostat监测IO状态【转】
    自己实现一个list比较器 实现Comparator()接口
    一些常用的随机实现
    java里null强转为某个类会报错吗?
    java游戏服务器简单工厂模式
    起个头!准备写一个 设计模式系列
    HashMap根据value值排序
    java游戏服务器 策略+简单工厂
  • 原文地址:https://www.cnblogs.com/juepei/p/3899784.html
Copyright © 2011-2022 走看看