本章开始,将根据业务分析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(") || stanza.startsWith(");
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(");
sb.append(CHARSET);
sb.append("'?>");
if (isFlashClient) {
sb.append(");
}
else {
sb.append(");
}
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("" );
if (connection.getTlsPolicy() != Connection.TLSPolicy.disabled) {
sb.append("" );
if (connection.getTlsPolicy() == Connection.TLSPolicy.required) {
sb.append(" ");
}
sb.append("");
}
sb.append(SASLAuthentication.getSASLMechanisms(session));
String specificFeatures = session.getAvailableStreamFeatures();
if (specificFeatures != null) {
sb.append(specificFeatures);
}
sb.append("");
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返回信息如下所示,
<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-MD5mechanism>
<mechanism>JIVE-SHAREDSECRETmechanism>
<mechanism>PLAINmechanism>
<mechanism>ANONYMOUSmechanism>
<mechanism>CRAM-MD5mechanism>
mechanisms>
<compression xmlns="http://jabber.org/features/compress">
<method>zlibmethod>
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方法、业所策略、注册方法等等。