openfire 接受消息处理的过程

openfire底层采用了MINA框架,它是采用事件监听的方式,其中IoHandler接口定义了不同的事件类型,因此根据不同的事件类型做相关的处理

 

Apache MINA 是一个网络应用框架,有助于用户非常方便地开发高性能、高伸缩性的网络应用。它通过Java NIO提供了一个抽象的、事件驱动的、异步的位于各种传输协议(如TCP/IP和UDP/IP)之上的API,Apache MINA 通常可被称之为:

NIO 框架库;

客户端/服务器框架库;

或者一个网络socket库。

 

Apache MINA 是一个网络应用程序框架,它对Java中的socket和NIO进行了有效和清晰的封装,方便开发人员开发TCP/UDP程序,从而抛开在使用原始的socket时需要考虑的各种繁杂而又烦人问题(线程、性能、会话等),把更多精力专著在应用中的业务逻辑的开发上

public interface IoHandler
{
//创建session
    public abstract void sessionCreated(IoSession iosession)
        throws Exception;
//开启session
    public abstract void sessionOpened(IoSession iosession)
        throws Exception;
//关闭session
    public abstract void sessionClosed(IoSession iosession)
        throws Exception;
//session空闲
    public abstract void sessionIdle(IoSession iosession, IdleStatus idlestatus)
        throws Exception;
//异常处理
    public abstract void exceptionCaught(IoSession iosession, Throwable throwable)
        throws Exception;
//接收消息
    public abstract void messageReceived(IoSession iosession, Object obj)
        throws Exception;
//发送消息
    public abstract void messageSent(IoSession iosession, Object obj)
        throws Exception;
}

抽象类ConnectionHandler继承了IoHandlerAdapter类,而IoHandlerAdapter实现了IoHandler接口:

public abstract class ConnectionHandler extends IoHandlerAdapter 

 

下面是ConnectionHandler类实现关于messageReceived事件的实现方法

@Override
public void messageReceived(IoSession session, Object message) throws Exception {
	// Get the stanza handler for this session
	//得到当前会话的StanzaHandler,这个对象在sessionOpened事件对应的方法中已经创建了,可以参考sessionOpened()的实现
	StanzaHandler handler = (StanzaHandler) session.getAttribute(HANDLER);
	// Get the parser to use to process stanza. For optimization there is going
	// to be a parser for each running thread. Each Filter will be executed
	// by the Executor placed as the first Filter. So we can have a parser associated
	// to each Thread
	int hashCode = Thread.currentThread().hashCode();
	XMPPPacketReader parser = parsers.get(hashCode);
	if (parser == null) {
		parser = new XMPPPacketReader();
		parser.setXPPFactory(factory);
		parsers.put(hashCode, parser);
	}
	// Update counter of read btyes
	updateReadBytesCounter(session);
	//System.out.println("RCVD: " + message);
	// Let the stanza handler process the received stanza
	try {
	//处理接收的message,parser为XMPPPacketReader对象,用来解析XML字符串,因为openfire信息之间的传递全部都是XML格式的字符串(XMPP协议)
	//下面方法的实现参考StanzaHandler.process(String stanza, XMPPPacketReader reader)方法
		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();
	}
}


 @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);
	//createStanzaHandler方法是当前类的一个抽象类,具体实现需要查看ConnectionHandler的子类是如何实现的,
	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);
	}
}

下面我就以ClientConnectionHandler类作为例子来讲解

public class ClientConnectionHandler extends ConnectionHandler

public ClientStanzaHandler(PacketRouter router, String serverName, Connection connection) {
	super(router, serverName, connection);
}

@Override
StanzaHandler createStanzaHandler(NIOConnection connection) {
	return new ClientStanzaHandler(XMPPServer.getInstance().getPacketRouter(), serverName, connection);
}

StanzaHandler类:A StanzaHandler is the main responsible for handling incoming stanzas.

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

	boolean initialStream = stanza.startsWith("<stream:stream") || stanza.startsWith("<flash:stream");
	if (!sessionCreated || initialStream) {
		if (!initialStream) {
			// Allow requests for flash socket policy files directly on the client listener port
			if (stanza.startsWith("<policy-file-request/>")) {
				String crossDomainText = FlashCrossDomainServlet.CROSS_DOMAIN_TEXT +
						XMPPServer.getInstance().getConnectionManager().getClientListenerPort() +
						FlashCrossDomainServlet.CROSS_DOMAIN_END_TEXT + '\0';
				connection.deliverRawText(crossDomainText);
				return;
			}
			else {
				// Ignore <?xml version="1.0"?>
				return;
			}
		}
		// Found an stream:stream tag...
		if (!sessionCreated) {
			sessionCreated = true;
			MXParser parser = reader.getXPPParser();
			parser.setInput(new StringReader(stanza));
			createSession(parser);
		}
		else if (startedTLS) {
			startedTLS = false;
			tlsNegotiated();
		}
		else if (startedSASL && saslStatus == SASLAuthentication.Status.authenticated) {
			startedSASL = false;
			saslSuccessful();
		}
		else if (waitingCompressionACK) {
			waitingCompressionACK = false;
			compressionSuccessful();
		}
		return;
	}

	// Verify if end of stream was requested
	if (stanza.equals("</stream:stream>")) {
		session.close();
		return;
	}
	// Ignore <?xml version="1.0"?> stanzas sent by clients
	if (stanza.startsWith("<?xml")) {
		return;
	}
	// Create DOM object from received stanza
	Element doc = reader.read(new StringReader(stanza)).getRootElement();
	if (doc == null) {
		// No document found.
		return;
	}
	String tag = doc.getName();
	if ("starttls".equals(tag)) {
		// Negotiate TLS
		if (negotiateTLS()) {
			startedTLS = true;
		}
		else {
			connection.close();
			session = null;
		}
	}
	else if ("auth".equals(tag)) {
		// User is trying to authenticate using SASL
		startedSASL = true;
		// Process authentication stanza
		saslStatus = SASLAuthentication.handle(session, doc);
	}
	else if (startedSASL && "response".equals(tag)) {
		// User is responding to SASL challenge. Process response
		saslStatus = SASLAuthentication.handle(session, doc);
	}
	else if ("compress".equals(tag)) {
		// Client is trying to initiate compression
		if (compressClient(doc)) {
			// Compression was successful so open a new stream and offer
			// resource binding and session establishment (to client sessions only)
			waitingCompressionACK = true;
		}
	}
	else {
	//最终处理消息,doc就是发送过来的XML字符串转化为Element对象
		process(doc);
	}
}


private void process(Element doc) throws UnauthorizedException {
	if (doc == null) {
		return;
	}

	// Ensure that connection was secured if TLS was required
	if (connection.getTlsPolicy() == Connection.TLSPolicy.required &&
			!connection.isSecure()) {
		closeNeverSecuredConnection();
		return;
	}

	String tag = doc.getName();
	//消息类型是<message>打头的,表示消息是message类型,这个就是对XMPP协议的解释
	if ("message".equals(tag)) {
		Message packet;
		try {
			packet = new Message(doc, !validateJIDs());
		}
		catch (IllegalArgumentException e) {
			Log.debug("Rejecting packet. JID malformed", e);
			// The original packet contains a malformed JID so answer with an error.
			Message reply = new Message();
			reply.setID(doc.attributeValue("id"));
			reply.setTo(session.getAddress());
			reply.getElement().addAttribute("from", doc.attributeValue("to"));
			reply.setError(PacketError.Condition.jid_malformed);
			session.process(reply);
			return;
		}
		processMessage(packet);
	}
	//消息类型是<presence>打头的,表示消息请求用户的状态
	else if ("presence".equals(tag)) {
		Presence packet;
		try {
			packet = new Presence(doc, !validateJIDs());
		}
		catch (IllegalArgumentException e) {
			Log.debug("Rejecting packet. JID malformed", e);
			// The original packet contains a malformed JID so answer an error
			Presence reply = new Presence();
			reply.setID(doc.attributeValue("id"));
			reply.setTo(session.getAddress());
			reply.getElement().addAttribute("from", doc.attributeValue("to"));
			reply.setError(PacketError.Condition.jid_malformed);
			session.process(reply);
			return;
		}
		// Check that the presence type is valid. If not then assume available type
		try {
			packet.getType();
		}
		catch (IllegalArgumentException e) {
			Log.warn("Invalid presence type", e);
			// The presence packet contains an invalid presence type so replace it with
			// an available presence type
			packet.setType(null);
		}
		// Check that the presence show is valid. If not then assume available show value
		try {
			packet.getShow();
		}
		catch (IllegalArgumentException e) {
			Log.warn("Invalid presence show for -" + packet.toXML(), e);
			// The presence packet contains an invalid presence show so replace it with
			// an available presence show
			packet.setShow(null);
		}
		if (session.getStatus() == Session.STATUS_CLOSED && packet.isAvailable()) {
			// Ignore available presence packets sent from a closed session. A closed
			// session may have buffered data pending to be processes so we want to ignore
			// just Presences of type available
			Log.warn("Ignoring available presence packet of closed session: " + packet);
			return;
		}
		processPresence(packet);
	}
	//消息类型是<iq>打头的,表示客户端对server端的一个请求
	else if ("iq".equals(tag)) {
		IQ packet;
		try {
			packet = getIQ(doc);
		}
		catch (IllegalArgumentException e) {
			Log.debug("Rejecting packet. JID malformed", e);
			// The original packet contains a malformed JID so answer an error
			IQ reply = new IQ();
			if (!doc.elements().isEmpty()) {
				reply.setChildElement(((Element)doc.elements().get(0)).createCopy());
			}
			reply.setID(doc.attributeValue("id"));
			reply.setTo(session.getAddress());
			if (doc.attributeValue("to") != null) {
				reply.getElement().addAttribute("from", doc.attributeValue("to"));
			}
			reply.setError(PacketError.Condition.jid_malformed);
			session.process(reply);
			return;
		}
		if (packet.getID() == null && JiveGlobals.getBooleanProperty("xmpp.server.validation.enabled", false)) {
			// IQ packets MUST have an 'id' attribute so close the connection
			StreamError error = new StreamError(StreamError.Condition.invalid_xml);
			session.deliverRawText(error.toXML());
			session.close();
			return;
		}
		processIQ(packet);
	}
	//如果消息类型不是IQ\Presence\Message三种类型,则执行processUnknowPacket()方法。
	else {
	//abstract boolean processUnknowPacket(Element doc) throws UnauthorizedException;
	//该方法是一个抽象类,具体的实现是要看继承ConnectionHandler类的具体类,它会重写createStanzaHandler方法。
	//在ClientConnectionHandler类中实现该方法对象是ClientStanzaHandler类
		if (!processUnknowPacket(doc)) {
			Log.warn(LocaleUtils.getLocalizedString("admin.error.packet.tag") +
					doc.asXML());
			session.close();
		}
	}
}

openfire之所以能够做到“即时通信”的目的正是因为MINA框架对socket进行了一层封装,说白了还是socket通信。

猜你喜欢

转载自hbiao68.iteye.com/blog/2028893