- 博客分类:
- openfire & xmpp
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通信。