openfire连接登陆优化方案

来源:互联网 发布:淘宝代付超限 编辑:程序博客网 时间:2024/06/01 09:03

openfire连接登陆优化方案

客户端登陆openfire,大概总共需要9个来回才完成登录。

在2G情况下,就表现为客户端登录特别慢,所以,为解决这个问题,对openfire进行了如下优化


openfire的连接、登陆过程分为几个步骤,完整报文如下,总共分为9个round trip:

===================================================================================================================

1  STREAM

RECV:<stream:stream to="jacklin-pc" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0">
SENT:<?xml version='1.0' encoding='UTF-8'?><stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="jacklin-pc" id="96508a6d" xml:lang="en" version="1.0">
SENT:<stream:features><starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"></starttls><mechanisms xmlns="urn:ietf:p 
arams: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>

2  TLS

RECV:<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>

SENT:<proceed xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>

3  STREAM

RECV:<stream:stream to="jacklin-pc" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0">

SENT:<?xml version='1.0' encoding='UTF-8'?><stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="jacklin-pc" id="96508a6d" 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>
4  SASL

RECV:<auth mechanism="PLAIN" xmlns="urn:ietf:params:xml:ns:xmpp-sasl">cmVhbG09ImphY2tsaW4tcGMiLG5vbmNlPSJMamw2RGt4Y3hGSDZxb2dTRE55Nmw2VkYreVQ2YjROMFlTa1BBSlZqIixxb3A9ImF1dGgiLGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNz</auth>

SENT:<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"></success>
5  STREAM

RECV:<stream:stream to="jacklin-pc" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0">
SENT:<?xml version='1.0' encoding='UTF-8'?><stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="jacklin-pc" id="96508a6d" xml:lang="en" version="1.0"><stream:features><compression xmlns="http://jabber.org/features/compress"><method>zlib</method></compression><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></stream:features>
6  BIND

RECV:<iq id="SfW08-0" type="set"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>Spark 2.6.3</resource></bind></iq>

SENT:<iq type="result" id="SfW08-0" to="jacklin-pc/96508a6d"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>test001@jacklin-pc/Spark 2.6.3</jid></bind></iq>
7  SESSION

RECV:<iq id="SfW08-1" type="set"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>
SENT:<iq type="result" id="SfW08-1" to="test001@jacklin-pc/Spark 2.6.3"/>
8  PRESENCE

RECV:<presence id="SfW08-6"><status>在线</status><priority>1</priority></presence>

9  ZLIB

RECV:<compress xmlns='http://jabber.org/protocol/compress'><method>zlib</method></compress>

SENT:<compressed xmlns='http://jabber.org/protocol/compress'/>

===================================================================================================================

1  STREAM优化

其中STREAM类似查询服务器功能,服务器会把服务器的特性返回给客户端,例如SASL策略,iq-auth,zlib压缩,xmpp-bind等等,其实,如果是内部定制的系统,这些特性服务器与客户端都是共知的,所以不需要查询,完全可以省略这些步骤。但是,我发现,在客户端第一次发送stream时,是需要初始化一些内容的,所以,需要再如下地方,加入如下代码:

org.jivesoftware.openfire.nio.ConnectionHandler

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. public void sessionOpened(IoSession session) throws Exception {  
  3.     // Create a new XML parser for the new connection. The parser will be  
  4.     // used by the XMPPDecoder filter.  
  5.     final XMLLightweightParser parser = new XMLLightweightParser(CHARSET);  
  6.     session.setAttribute(XML_PARSER, parser);  
  7.     // Create a new NIOConnection for the new session  
  8.     final NIOConnection connection = createNIOConnection(session);  
  9.     session.setAttribute(CONNECTION, connection);  
  10.     session.setAttribute(HANDLER, createStanzaHandler(connection));  
  11.     // Set the max time a connection can be idle before closing it. This  
  12.     // amount of seconds  
  13.     // is divided in two, as Openfire will ping idle clients first (at 50%  
  14.     // of the max idle time)  
  15.     // before disconnecting them (at 100% of the max idle time). This  
  16.     // prevents Openfire from  
  17.     // removing connections without warning.  
  18.     final int idleTime = getMaxIdleTime() / 2;  
  19.     if (idleTime > 0) {  
  20.         session.setIdleTime(IdleStatus.READER_IDLE, idleTime);  
  21.     }  
  22.   
  23.     // ADD LOCALSESSION START===========================================================  
  24.     Log.info("[DO LOCALSESSION]");  
  25.     int hashCode = Thread.currentThread().hashCode();  
  26.     XMPPPacketReader parser1 = parsers.get(hashCode);  
  27.     if (parser1 == null) {  
  28.         parser1 = new XMPPPacketReader();  
  29.         parser1.setXPPFactory(factory);  
  30.         parsers.put(hashCode, parser1);  
  31.     }  
  32.   
  33.     String msg = "<stream:stream to='jacklin-pc' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>";  
  34.   
  35.     StanzaHandler handler = (StanzaHandler) session.getAttribute(HANDLER);  
  36.     try {  
  37.         handler.process(msg, parser1);  
  38.     } catch (Exception e) {  
  39.         Log.error(  
  40.                 "Closing connection due to error while processing message: "  
  41.                         + msg, e);  
  42.         connection.close();  
  43.     }  
  44.     // ADD LOCALSESSION END==============================================================  
  45.   
  46. }  

此目的是让服务器在客户端建立连接阶段就初始化客户端的资源。

2  SESSION

session其实从服务器端看,只是回复了一下客户端,并没有起什么作用,所以客户端可以不发这段报文,服务器端不需要改动。服务器端处理过程如下。

org.jivesoftware.openfire.handler.IQSessionEstablishmentHandler

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public class IQSessionEstablishmentHandler extends IQHandler {  
  2.   
  3.     private IQHandlerInfo info;  
  4.   
  5.     public IQSessionEstablishmentHandler() {  
  6.         super("Session Establishment handler");  
  7.         info = new IQHandlerInfo("session""urn:ietf:params:xml:ns:xmpp-session");  
  8.     }  
  9.   
  10.     @Override  
  11.     public IQ handleIQ(IQ packet) throws UnauthorizedException {  
  12.         // Just answer that the session has been activated  
  13.         IQ reply = IQ.createResultIQ(packet);  
  14.         return reply;  
  15.     }  
  16.   
  17.     @Override  
  18.     public IQHandlerInfo getInfo() {  
  19.         return info;  
  20.     }  
  21. }  

3  BIND,PRESENCE,ZLIB

其实PRESENCE和ZLIB可以在客户端BIND操作之后,服务器端直接进行,不需要客户端再次协商。所以,我在以下代码,进行了以下改动:

org.jivesoftware.openfire.handler.IQBindHandler

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. @Override  
  2.     public IQ handleIQ(IQ packet) throws UnauthorizedException {  
  3.         LocalClientSession session = (LocalClientSession) sessionManager  
  4.                 .getSession(packet.getFrom());  
  5.         // If no session was found then answer an error (if possible)  
  6.         if (session == null) {  
  7.             Log.error("Error during resource binding. Session not found in "  
  8.                     + sessionManager.getPreAuthenticatedKeys() + " for key "  
  9.                     + packet.getFrom());  
  10.             // This error packet will probably won't make it through  
  11.             IQ reply = IQ.createResultIQ(packet);  
  12.             reply.setChildElement(packet.getChildElement().createCopy());  
  13.             reply.setError(PacketError.Condition.internal_server_error);  
  14.             return reply;  
  15.         }  
  16.   
  17.         IQ reply = IQ.createResultIQ(packet);  
  18.         Element child = reply.setChildElement("bind",  
  19.                 "urn:ietf:params:xml:ns:xmpp-bind");  
  20.         // Check if the client specified a desired resource  
  21.         String resource = packet.getChildElement().elementTextTrim("resource");  
  22.         if (resource == null || resource.length() == 0) {  
  23.             // None was defined so use the random generated resource  
  24.             resource = session.getAddress().getResource();  
  25.         } else {  
  26.             // Check that the desired resource is valid  
  27.             try {  
  28.                 resource = JID.resourceprep(resource);  
  29.             } catch (StringprepException e) {  
  30.                 reply.setChildElement(packet.getChildElement().createCopy());  
  31.                 reply.setError(PacketError.Condition.jid_malformed);  
  32.                 // Send the error directly since a route does not exist at this  
  33.                 // point.  
  34.                 session.process(reply);  
  35.                 return null;  
  36.             }  
  37.         }  
  38.         // Get the token that was generated during the SASL authentication  
  39.         AuthToken authToken = session.getAuthToken();  
  40.         if (authToken == null) {  
  41.             // User must be authenticated before binding a resource  
  42.             reply.setChildElement(packet.getChildElement().createCopy());  
  43.             reply.setError(PacketError.Condition.not_authorized);  
  44.             // Send the error directly since a route does not exist at this  
  45.             // point.  
  46.             session.process(reply);  
  47.             return reply;  
  48.         }  
  49.         if (authToken.isAnonymous()) {  
  50.             // User used ANONYMOUS SASL so initialize the session as an  
  51.             // anonymous login  
  52.             session.setAnonymousAuth();  
  53.         } else {  
  54.             String username = authToken.getUsername().toLowerCase();  
  55.             // If a session already exists with the requested JID, then check to  
  56.             // see  
  57.             // if we should kick it off or refuse the new connection  
  58.             ClientSession oldSession = routingTable.getClientRoute(new JID(  
  59.                     username, serverName, resource, true));  
  60.             if (oldSession != null) {  
  61.                 try {  
  62.                     int conflictLimit = sessionManager.getConflictKickLimit();  
  63.                     if (conflictLimit == SessionManager.NEVER_KICK) {  
  64.                         reply.setChildElement(packet.getChildElement()  
  65.                                 .createCopy());  
  66.                         reply.setError(PacketError.Condition.conflict);  
  67.                         // Send the error directly since a route does not exist  
  68.                         // at this point.  
  69.                         session.process(reply);  
  70.                         return null;  
  71.                     }  
  72.   
  73.                     int conflictCount = oldSession.incrementConflictCount();  
  74.                     if (conflictCount > conflictLimit) {  
  75.                         // Kick out the old connection that is conflicting with  
  76.                         // the new one  
  77.                         StreamError error = new StreamError(  
  78.                                 StreamError.Condition.conflict);  
  79.                         oldSession.deliverRawText(error.toXML());  
  80.                         oldSession.close();  
  81.                     } else {  
  82.                         reply.setChildElement(packet.getChildElement()  
  83.                                 .createCopy());  
  84.                         reply.setError(PacketError.Condition.conflict);  
  85.                         // Send the error directly since a route does not exist  
  86.                         // at this point.  
  87.                         session.process(reply);  
  88.                         return null;  
  89.                     }  
  90.                 } catch (Exception e) {  
  91.                     Log.error("Error during login", e);  
  92.                 }  
  93.             }  
  94.             // If the connection was not refused due to conflict, log the user  
  95.             // in  
  96.             session.setAuthToken(authToken, resource);  
  97.         }  
  98.   
  99.         child.addElement("jid").setText(session.getAddress().toString());  
  100.         // Send the response directly since a route does not exist at this  
  101.         // point.  
  102.         session.process(reply);  
  103.         // After the client has been informed, inform all listeners as well.  
  104.         SessionEventDispatcher.dispatchEvent(session,  
  105.                 SessionEventDispatcher.EventType.resource_bound);  
  106.   
  107.         // ADD COMPRESSION START==================================================  
  108.          session.getConnection().addCompression();  
  109.   
  110.          session.getConnection().startCompression();  
  111.          Log.info("[DO COMPRESSION]");  
  112.         // ADD COMPRESSION END====================================================  
  113.            
  114.            
  115.         // ADD PRESENCE START====================================================  
  116.         String domain = XMPPServer.getInstance().getServerInfo()  
  117.                 .getXMPPDomain();  
  118.         Presence pp = new Presence();  
  119.         pp.setFrom(session.getAddress().toString());  
  120.         // pp.setTo(domain);  
  121.         XMPPServer.getInstance().getPacketRouter().route(pp);  
  122.         Log.info("[DO PRESENCE]:"+ pp.toXML());  
  123.         // ADD PRESENCE END====================================================  
  124.   
  125.         return null;  
  126.     }  


经过以上优化之后,服务器与客户端的协商只剩下3个round trip,过程如下:

2  TLS
RECV:<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>

SENT:<proceed xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>

4  SASL

RECV:<auth mechanism="PLAIN" xmlns="urn:ietf:params:xml:ns:xmpp-sasl">cmVhbG09ImphY2tsaW4tcGMiLG5vbmNlPSJMamw2RGt4Y3hGSDZxb2dTRE55Nmw2VkYreVQ2YjROMFlTa1BBSlZqIixxb3A9ImF1dGgiLGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNz</auth>

SENT:<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"></success>

6  BIND

RECV:<iq id="SfW08-0" type="set"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>Spark 2.6.3</resource></bind></iq>
SENT:<iq type="result" id="SfW08-0" to="jacklin-pc/96508a6d"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>test001@jacklin-pc/Spark 2.6.3</jid></bind></iq>

客户端按照如下节奏发送报文,并进行操作就可以了,客户端修改代码,在这里不做描述,请修改者自行尝试



0 0
原创粉丝点击