openfire源码分析---6

用户连接请求

本章开始,将根据业务分析openfire的源码。用户连接openfire服务器时时,会首先向openfire发送如下XMPP消息,

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

根据前面几章的分析,该函数最终会到达ClientStanzaHandler的process函数,为了方便分析,这里贴出该函数,

    public void process(String stanza, XMPPPacketReader reader) throws Exception {

        boolean initialStream = stanza.startsWith("<stream:stream") || stanza.startsWith("<flash:stream");
        if (!sessionCreated || initialStream) {

            ...

            if (!sessionCreated) {
                sessionCreated = true;
                MXParser parser = reader.getXPPParser();
                parser.setInput(new StringReader(stanza));
                createSession(parser);
            }

            ...

            return;
        }

        ...

    }

根据客户端发来的消息,这里initialStream为true,又因为此时是客户端发来的第一条消息,因此Session并未创建,因此这里执行createSession函数,定义在ClientStanzaHandler中,

    boolean createSession(String namespace, String serverName, XmlPullParser xpp, Connection connection) throws XmlPullParserException {
        if ("jabber:client".equals(namespace)) {
            session = LocalClientSession.createSession(serverName, xpp, connection);
            return true;
        }
        return false;
    }

根据客户端消息可知,这里的命名空间namespace确实是”jabber:client”,因此调用createSession构造一个LocalSession。注意这里传入的参数connection是在ConnectionHandler的sessionOpened函数中构造的NIOConnection。

    public static LocalClientSession createSession(String serverName, XmlPullParser xpp, Connection connection)
            throws XmlPullParserException {

        String language = "en";
        int majorVersion = 0;
        int minorVersion = 0;
        for (int i = 0; i < xpp.getAttributeCount(); i++) {
            if ("lang".equals(xpp.getAttributeName(i))) {
                language = xpp.getAttributeValue(i);
            }
            if ("version".equals(xpp.getAttributeName(i))) {
                try {
                    int[] version = decodeVersion(xpp.getAttributeValue(i));
                    majorVersion = version[0];
                    minorVersion = version[1];
                }
                catch (Exception e) {

                }
            }
        }
        connection.setLanaguage(language);
        connection.setXMPPVersion(majorVersion, minorVersion);

        if (!connection.isSecure()) {
            boolean hasCertificates = false;
            try {
                hasCertificates = SSLConfig.getKeyStore().size() > 0;
            }
            catch (Exception e) {

            }
            Connection.TLSPolicy tlsPolicy = getTLSPolicy();
            if (Connection.TLSPolicy.required == tlsPolicy && !hasCertificates) {
                return null;
            }
            connection.setTlsPolicy(hasCertificates ? tlsPolicy : Connection.TLSPolicy.disabled);
        } else {
            connection.setTlsPolicy(Connection.TLSPolicy.disabled);
        }

        connection.setCompressionPolicy(getCompressionPolicy());
        LocalClientSession session = SessionManager.getInstance().createClientSession(connection);

        StringBuilder sb = new StringBuilder(200);
        sb.append("<?xml version='1.0' encoding='");
        sb.append(CHARSET);
        sb.append("'?>");
        if (isFlashClient) {
            sb.append("<flash:stream xmlns:flash=\"http://www.jabber.com/streams/flash\" ");
        }
        else {
            sb.append("<stream:stream ");
        }
        sb.append("xmlns:stream=\"http://etherx.jabber.org/streams\" xmlns=\"jabber:client\" from=\"");
        sb.append(serverName);
        sb.append("\" id=\"");
        sb.append(session.getStreamID().toString());
        sb.append("\" xml:lang=\"");
        sb.append(language);
        if (majorVersion != 0) {
            sb.append("\" version=\"");
            sb.append(majorVersion).append(".").append(minorVersion);
        }
        sb.append("\">");
        connection.deliverRawText(sb.toString());

        sb = new StringBuilder(490);
        sb.append("<stream:features>");
        if (connection.getTlsPolicy() != Connection.TLSPolicy.disabled) {
            sb.append("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\">");
            if (connection.getTlsPolicy() == Connection.TLSPolicy.required) {
                sb.append("<required/>");
            }
            sb.append("</starttls>");
        }
        sb.append(SASLAuthentication.getSASLMechanisms(session));
        String specificFeatures = session.getAvailableStreamFeatures();
        if (specificFeatures != null) {
            sb.append(specificFeatures);
        }
        sb.append("</stream:features>");

        connection.deliverRawText(sb.toString());
        return session;
    }

第一个for循环遍历客户端发来的xmpp消息,查看是否有”lang”或者”version”属性,根据前面的xmpp消息,这里结束循环时,language不变,majorVersion=1,minorVersion=0。然后将该语言和版本信息设置进NIOConnection中,该NIOConnection是在ClientConnectionHandler的sessionOpened函数中构造的,里面保存了Session信息。isSecure定义在NIOConnection中,

    public boolean isSecure() {
        return ioSession.getFilterChain().contains(TLS_FILTER_NAME);
    }

该函数检查该Session是否包含了安全传输协议,其依据是Session中的过滤器是否包含了TLS_FILTER_NAME指定名称的过滤器,这里假设没有,后面的代码假设最后会设置tls策略为disable,表是不启用安全传输。
接下来调用setCompressionPolicy设置压缩策略,CompressionPolicy是个enum变量,这里默认设置为optional,即开启压缩策略。
然后调用SessionManager的createClientSession创建Session,该Session是openfire中的Session,而前面的Session是mina框架中构造的Session,两者的功能不一致。

    public LocalClientSession createClientSession(Connection conn) {
        return createClientSession(conn, nextStreamID());
    }

nextStreamID会调用BasicStreamIDFactory构造一个随机的ID,

    public LocalClientSession createClientSession(Connection conn, StreamID id) {

        LocalClientSession session = new LocalClientSession(serverName, conn, id);
        conn.init(session);
        conn.registerCloseListener(clientSessionListener, session);
        localSessionManager.getPreAuthenticatedSessions().put(session.getAddress().getResource(), session);
        connectionsCounter.incrementAndGet();
        return session;
    }

首先创建一个LocalClientSession,然后调用init函数将LocalClientSession设置进NioConnection中,接着设置连接关闭时的监听器,然后将刚刚构造的LocalClientSession设置进未验证的Session哈希表中,这里的localSessionManager为LocalSessionManager,用来保存openfire中的Session。
回到createSession函数中,下面开始就是构造返回信息了,根据代码,最后构造的XMPP返回信息如下所示,

<?xml version='1.0' encoding='UTF-8'?>
<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="#serverName" id="#id" xml:lang="en" version="1.0"\>
<stream:features>
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
<mechanism>DIGEST-MD5</mechanism>
<mechanism>JIVE-SHAREDSECRET</mechanism>
<mechanism>PLAIN</mechanism>
<mechanism>ANONYMOUS</mechanism>
<mechanism>CRAM-MD5</mechanism>
</mechanisms>
<compression xmlns="http://jabber.org/features/compress">
<method>zlib</method>
</compression>
<auth xmlns="http://jabber.org/features/iq-auth"/>
<register xmlns="http://jabber.org/features/iq-register"/>
</stream:features>

这里的#serverName和#id分别是服务器主机名和前面调用nextStreamID构造的随机ID;几个mechanism标签是SASLAuthentication.getSASLMechanisms函数返回的openfire默认支持的SASL方法;compression、auth和register标签是getAvailableStreamFeatures返回的结果。
最后调用deliverRawText发送数据,deliverRawText就是调用mina框架IoSession的write方法。

到此,客户端完成了与openfire服务器的第一次请求,总结一下,openfire服务器主要根据本次请求构造Connection和Session并保存到未认证Session哈希表中,并返回服务器可支持的SASL方法、业所策略、注册方法等等。

你可能感兴趣的:(openfire源码分析---6)