zoukankan      html  css  js  c++  java
  • openfire连接登陆优化方案

    client登陆openfire,大概总共须要9个来回才完毕登录。

    在2G情况下。就表现为client登录特别慢,所以,为解决问题,对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类似查询server功能,server会把server的特性返回给client,比如SASL策略,iq-auth,zlib压缩,xmpp-bind等等。事实上,假设是内部定制的系统,这些特性server与client都是共知的,所以不须要查询,全然能够省略这些步骤。

    可是,我发现,在client第一次发送stream时,是须要初始化一些内容的。所以,须要再例如以下地方。增加例如以下代码:

    org.jivesoftware.openfire.nio.ConnectionHandler

    	@Override
    	public void sessionOpened(IoSession session) throws Exception {
    		// Create a new XML parser for the new connection. The parser will be
    		// used by the XMPPDecoder filter.
    		final XMLLightweightParser parser = new XMLLightweightParser(CHARSET);
    		session.setAttribute(XML_PARSER, parser);
    		// Create a new NIOConnection for the new session
    		final NIOConnection connection = createNIOConnection(session);
    		session.setAttribute(CONNECTION, connection);
    		session.setAttribute(HANDLER, createStanzaHandler(connection));
    		// Set the max time a connection can be idle before closing it. This
    		// amount of seconds
    		// is divided in two, as Openfire will ping idle clients first (at 50%
    		// of the max idle time)
    		// before disconnecting them (at 100% of the max idle time). This
    		// prevents Openfire from
    		// removing connections without warning.
    		final int idleTime = getMaxIdleTime() / 2;
    		if (idleTime > 0) {
    			session.setIdleTime(IdleStatus.READER_IDLE, idleTime);
    		}
    
    		// ADD LOCALSESSION START===========================================================
    		Log.info("[DO LOCALSESSION]");
    		int hashCode = Thread.currentThread().hashCode();
    		XMPPPacketReader parser1 = parsers.get(hashCode);
    		if (parser1 == null) {
    			parser1 = new XMPPPacketReader();
    			parser1.setXPPFactory(factory);
    			parsers.put(hashCode, parser1);
    		}
    
    		String msg = "<stream:stream to='jacklin-pc' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>";
    
    		StanzaHandler handler = (StanzaHandler) session.getAttribute(HANDLER);
    		try {
    			handler.process(msg, parser1);
    		} catch (Exception e) {
    			Log.error(
    					"Closing connection due to error while processing message: "
    							+ msg, e);
    			connection.close();
    		}
    		// ADD LOCALSESSION END==============================================================
    
    	}

    此目的是让server在client建立连接阶段就初始化client的资源。

    2  SESSION

    session事实上从server端看,仅仅是回复了一下client,并没有起什么作用,所以client能够不发这段报文,server端不须要修改。server端处理步骤例如以下。

    org.jivesoftware.openfire.handler.IQSessionEstablishmentHandler

    public class IQSessionEstablishmentHandler extends IQHandler {
    
        private IQHandlerInfo info;
    
        public IQSessionEstablishmentHandler() {
            super("Session Establishment handler");
            info = new IQHandlerInfo("session", "urn:ietf:params:xml:ns:xmpp-session");
        }
    
        @Override
    	public IQ handleIQ(IQ packet) throws UnauthorizedException {
            // Just answer that the session has been activated
            IQ reply = IQ.createResultIQ(packet);
            return reply;
        }
    
        @Override
    	public IQHandlerInfo getInfo() {
            return info;
        }
    }

    3  BIND,PRESENCE,ZLIB

    事实上PRESENCE和ZLIB能够在clientBIND操作之后。server端直接进行,不须要client再次协商。所以。我在下面代码。进行了下面修改:

    org.jivesoftware.openfire.handler.IQBindHandler

    @Override
    	public IQ handleIQ(IQ packet) throws UnauthorizedException {
    		LocalClientSession session = (LocalClientSession) sessionManager
    				.getSession(packet.getFrom());
    		// If no session was found then answer an error (if possible)
    		if (session == null) {
    			Log.error("Error during resource binding. Session not found in "
    					+ sessionManager.getPreAuthenticatedKeys() + " for key "
    					+ packet.getFrom());
    			// This error packet will probably won't make it through
    			IQ reply = IQ.createResultIQ(packet);
    			reply.setChildElement(packet.getChildElement().createCopy());
    			reply.setError(PacketError.Condition.internal_server_error);
    			return reply;
    		}
    
    		IQ reply = IQ.createResultIQ(packet);
    		Element child = reply.setChildElement("bind",
    				"urn:ietf:params:xml:ns:xmpp-bind");
    		// Check if the client specified a desired resource
    		String resource = packet.getChildElement().elementTextTrim("resource");
    		if (resource == null || resource.length() == 0) {
    			// None was defined so use the random generated resource
    			resource = session.getAddress().getResource();
    		} else {
    			// Check that the desired resource is valid
    			try {
    				resource = JID.resourceprep(resource);
    			} catch (StringprepException e) {
    				reply.setChildElement(packet.getChildElement().createCopy());
    				reply.setError(PacketError.Condition.jid_malformed);
    				// Send the error directly since a route does not exist at this
    				// point.
    				session.process(reply);
    				return null;
    			}
    		}
    		// Get the token that was generated during the SASL authentication
    		AuthToken authToken = session.getAuthToken();
    		if (authToken == null) {
    			// User must be authenticated before binding a resource
    			reply.setChildElement(packet.getChildElement().createCopy());
    			reply.setError(PacketError.Condition.not_authorized);
    			// Send the error directly since a route does not exist at this
    			// point.
    			session.process(reply);
    			return reply;
    		}
    		if (authToken.isAnonymous()) {
    			// User used ANONYMOUS SASL so initialize the session as an
    			// anonymous login
    			session.setAnonymousAuth();
    		} else {
    			String username = authToken.getUsername().toLowerCase();
    			// If a session already exists with the requested JID, then check to
    			// see
    			// if we should kick it off or refuse the new connection
    			ClientSession oldSession = routingTable.getClientRoute(new JID(
    					username, serverName, resource, true));
    			if (oldSession != null) {
    				try {
    					int conflictLimit = sessionManager.getConflictKickLimit();
    					if (conflictLimit == SessionManager.NEVER_KICK) {
    						reply.setChildElement(packet.getChildElement()
    								.createCopy());
    						reply.setError(PacketError.Condition.conflict);
    						// Send the error directly since a route does not exist
    						// at this point.
    						session.process(reply);
    						return null;
    					}
    
    					int conflictCount = oldSession.incrementConflictCount();
    					if (conflictCount > conflictLimit) {
    						// Kick out the old connection that is conflicting with
    						// the new one
    						StreamError error = new StreamError(
    								StreamError.Condition.conflict);
    						oldSession.deliverRawText(error.toXML());
    						oldSession.close();
    					} else {
    						reply.setChildElement(packet.getChildElement()
    								.createCopy());
    						reply.setError(PacketError.Condition.conflict);
    						// Send the error directly since a route does not exist
    						// at this point.
    						session.process(reply);
    						return null;
    					}
    				} catch (Exception e) {
    					Log.error("Error during login", e);
    				}
    			}
    			// If the connection was not refused due to conflict, log the user
    			// in
    			session.setAuthToken(authToken, resource);
    		}
    
    		child.addElement("jid").setText(session.getAddress().toString());
    		// Send the response directly since a route does not exist at this
    		// point.
    		session.process(reply);
    		// After the client has been informed, inform all listeners as well.
    		SessionEventDispatcher.dispatchEvent(session,
    				SessionEventDispatcher.EventType.resource_bound);
    
    		// ADD COMPRESSION START==================================================
    		 session.getConnection().addCompression();
    
    		 session.getConnection().startCompression();
    		 Log.info("[DO COMPRESSION]");
    		// ADD COMPRESSION END====================================================
    		 
    		 
    		// ADD PRESENCE START====================================================
    		String domain = XMPPServer.getInstance().getServerInfo()
    				.getXMPPDomain();
    		Presence pp = new Presence();
    		pp.setFrom(session.getAddress().toString());
    		// pp.setTo(domain);
    		XMPPServer.getInstance().getPacketRouter().route(pp);
    		Log.info("[DO PRESENCE]:"+ pp.toXML());
    		// ADD PRESENCE END====================================================
    
    		return null;
    	}


    经过以上优化之后,server与client的协商仅仅剩下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>

    client依照例如以下节奏发送报文。并进行操作就能够了。client改动代码。在这里不做描写叙述,请改动者自行尝试





  • 相关阅读:
    Spark 集群 任务提交模式
    Spark RDD 宽窄依赖
    Spark 资源调度包 stage 类解析
    堆内内存与堆外内存
    Spark笔记(一)
    scala 语言特性
    Centos7.4 Storm2.0.0 + Zookeeper3.5.5 高可用集群搭建
    fs模块他的作用是(文件夹)
    事件循环机制
    简单的下路由(我们可以在控制器中看到路由参数)
  • 原文地址:https://www.cnblogs.com/mengfanrong/p/5240074.html
Copyright © 2011-2022 走看看