AndroidPn源碼分析(一)

來源:互聯網
上載者:User

標籤:des   android   style   blog   http   color   使用   os   

好了,開始研究源碼了。目前對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   messageSentsessionCreated事件一個新的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);


 

 

 

 

 

 

 


 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.