Android基于XMPP协议之asmack源码分析

一、整体聊天功能实现的流程

1>与openfire服务器建立连接
2>获取连接对象,建立输入输出流
3>开启输入数据流,线程阻塞等待消息,发出消息
4>关闭输入输出流,关闭连接

二、解析连接服务器的类XmppConnection

首先 XmppConnection调用connect方法连接服务器,这里调用connectUsingConfiguration(config)方法,这里我们看到了私有变量config,这里应该初始化了系统的配置信息,这里在初始化的时候调用了mConfiguration = new ConnectionConfiguration(HOST, PORT);下面接着看connectUsingConfiguration方法;

  public void connect() throws XMPPException {
        // Establishes the connection, readers and writers
        connectUsingConfiguration(config);
        // Automatically makes the login if the user was previously connected successfully
        // to the server and the connection was terminated abruptly
        if (connected && wasAuthenticated) {
            // Make the login
            if (isAnonymous()) {
                // Make the anonymous login
                loginAnonymously();
            }
            else {
                login(config.getUsername(), config.getPassword(), config.getResource());
            }
            notifyReconnection();
        }
    }

我们看到,这里获取的 this.socket存于XmppConnection中,每个XmppConnection对应的当前设备和服务器的链接,对应的一个socket对象,缓存于当前XmppConnection中,便于使用,这里新建socket连接后做了一个初始化连接的操作,即initConnection(),我们继续看这个操作

private void connectUsingConfiguration(ConnectionConfiguration config) throws XMPPException {

        Iterator<HostAddress> it = config.getHostAddresses().iterator();
        List<HostAddress> failedAddresses = new LinkedList<HostAddress>();
        boolean xmppIOError = false;
        while (it.hasNext()) {
            exception = null;
            HostAddress hostAddress = it.next();
            String host = hostAddress.getFQDN();
            int port = hostAddress.getPort();
            try {
                if (config.getSocketFactory() == null) {
                    this.socket = new Socket(host, port);
                }
                else {
                    this.socket = config.getSocketFactory().createSocket(host, port);
                }
            } catch (UnknownHostException uhe) {
                String errorMessage = "Could not connect to " + host + ":" + port + ".";
                exception = new XMPPException(errorMessage, new XMPPError(XMPPError.Condition.remote_server_timeout,
                        errorMessage), uhe);
            } catch (IOException ioe) {
                String errorMessage = "XMPPError connecting to " + host + ":" + port + ".";
                exception = new XMPPException(errorMessage, new XMPPError(XMPPError.Condition.remote_server_error,
                        errorMessage), ioe);
                xmppIOError = true;
            }
        //此处省略部分代码
        socketClosed = false;
        initConnection();
    }

新建连接的socket后这里一开始便有一个initReaderAndWriter方法,这里初始化字符输入输出流, 这里创建了 packetWriter和packetReader ,用于向服务器发送和接收消息,当然下面对应的有程序异常后对于输入输出流和socket的关闭的操作,我们来看看packetWriter这个向服务器发送消息的类具体做了什么操作

 private void initConnection() throws XMPPException {
        boolean isFirstInitialization = packetReader == null || packetWriter == null;
        compressionHandler = null;
        serverAckdCompression = false;
        initReaderAndWriter();
        try {
            if (isFirstInitialization) {
                packetWriter = new PacketWriter(this);
                packetReader = new PacketReader(this);
                // If debugging is enabled, we should start the thread that will listen for 
                if (config.isDebuggerEnabled()) {
                    addPacketListener(debugger.getReaderListener(), null);
                    if (debugger.getWriterListener() != null) {
                        addPacketSendingListener(debugger.getWriterListener(), null);
                    }
                }
            }
            else {
                packetWriter.init();
                packetReader.init();
            }
            packetWriter.startup();
            packetReader.startup();
            connected = true;
            if (isFirstInitialization) {
                // Notify listeners that a new connection has been established
                for (ConnectionCreationListener listener : getConnectionCreationListeners()) {
                    listener.connectionCreated(this);
                }
            }
        }
        catch (XMPPException ex) {
            // An exception occurred in setting up the connection. Make sure we shut down the
            // readers and writers and close the socket.

            if (packetWriter != null) {
                try {
                    packetWriter.shutdown();
                }
                catch (Throwable ignore) { /* ignore */ }
                packetWriter = null;
            }
            if (packetReader != null) {
                try {
                    packetReader.shutdown();
                }
                catch (Throwable ignore) { /* ignore */ }
                packetReader = null;
            }
            if (reader != null) {
                try {
                    reader.close();
                }
                catch (Throwable ignore) { /* ignore */ }
                reader = null;
            }
            if (writer != null) {
                try {
                    writer.close();
                }
                catch (Throwable ignore) {  /* ignore */}
                writer = null;
            }
            if (socket != null) {
                try {
                    socket.close();
                }
                catch (Exception e) { /* ignore */ }
                socket = null;
            }
            this.setWasAuthenticated(authenticated);
            chatManager = null;
            authenticated = false;
            connected = false;
            throw ex;        // Everything stoppped. Now throw the exception.
        }
    }

三、PacketWriter(发送消息)

这里新建了一个消息队列,用于存放用户将要发送的消息,存放于这个消息列队中,然后初始化获取用户将要发送消息的接收过程init(),这里开启了一个线程writerThread调用writePackets方法,这个writerThread线程的开启在PacketWriter的startUp方法中,下面我们看下writePackets做了什么操作

 protected PacketWriter(XMPPConnection connection) {
        this.queue = new ArrayBlockingQueue<Packet>(500, true);
        this.connection = connection;
        init();
    }

protected void init() {
        this.writer = connection.writer;
        done = false;
        writerThread = new Thread() {
            public void run() {
                writePackets(this);
            }
        };
        writerThread.setName("Smack Packet Writer (" + connection.connectionCounterValue + ")");
        writerThread.setDaemon(true);
    }

这里一开便调用了openStream,这里添加字符串去打开xmpp协议指定的输入输出流,以便于后面能发送数据,然后这里有一个while循环,循环的状态基本为阻塞状态,nextPacket去用户那里获取即将要发送的消息我们等会再看,如果拿到的消息不为空,则 writer.write(packet.toXML());直接传输到服务器,中间有一个 queue.clear();表示消息发送完成后清楚消息队列 writer.write(“”)结束消息的标志;完成后关闭输入输出流,接下来我们看下nextPacket方法具体的操作

  void openStream() throws IOException {
        StringBuilder stream = new StringBuilder();
        stream.append("<stream:stream");
        stream.append(" to=\"").append(connection.getServiceName()).append("\"");
        stream.append(" xmlns=\"jabber:client\"");
        stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
        stream.append(" version=\"1.0\">");
        writer.write(stream.toString());
        writer.flush();
    }

 private void writePackets(Thread thisThread) {
        try {
            // Open the stream.
            openStream();
            // Write out packets from the queue.
            while (!done && (writerThread == thisThread)) {
                Packet packet = nextPacket();
                if (packet != null) {
                    writer.write(packet.toXML());
                    if (queue.isEmpty()) {
                        writer.flush();
                    }
                }
            }
            // Flush out the rest of the queue. If the queue is extremely large, it's possible
            // we won't have time to entirely flush it before the socket is forced closed
            // by the shutdown process.
            try {
                while (!queue.isEmpty()) {
                    Packet packet = queue.remove();
                    writer.write(packet.toXML());
                }
                writer.flush();
            }
            catch (Exception e) {
                e.printStackTrace();
            }
            // Delete the queue contents (hopefully nothing is left).
            queue.clear();
            // Close the stream.
            try {
                writer.write("</stream:stream>");
                writer.flush();
            }
            catch (Exception e) {
                // Do nothing
            }
            finally {
                try {
                    writer.close();
                }
                catch (Exception e) {
                    // Do nothing
                }
            }
        }
        catch (IOException ioe) {
            // The exception can be ignored if the the connection is 'done'
            // or if the it was caused because the socket got closed
            if (!(done || connection.isSocketClosed())) {
                done = true;
                // packetReader could be set to null by an concurrent disconnect() call.
                // Therefore Prevent NPE exceptions by checking packetReader.
                if (connection.packetReader != null) {
                        connection.notifyConnectionError(ioe);
                }
            }
        }
    }

这里有一个循环,从消息队列中获取用户即将发送的消息,如果有,则从消息队列中取出来,没有则synchronized 同步中调用 queue.wait(),让当前的writerThread处于等待状态,等待用户将要发送的消息存入消息队列,这里我们看完了packetWriter,接下来我们看下packetReader

 private Packet nextPacket() {
        Packet packet = null;
        // Wait until there's a packet or we're done.
        while (!done && (packet = queue.poll()) == null) {
            try {
                synchronized (queue) {
                    queue.wait();
                }
            }
            catch (InterruptedException ie) {
                // Do nothing
            }
        }
        return packet;
    }

三、PacketReader(接收消息)

同样这边的PacketReader构造方法中调用了初始化方法init,我们来看看,这里的主要两个方法parsePackets和resetParser,parsePackets则是为接收到的消息做解析的准备,暂时先放下,这里同样也是开启了一个线程 readerThread,resetParser则是初始化XML解析器和输入输出流parser.setInput(connection.reader),为我们parsePackets解析接收到的数据做铺垫,下面我们看下parsePackets方法,由于比较多,这里显示部分代码

 protected void init() {
        done = false;
        connectionID = null;
        readerThread = new Thread() {
            public void run() {
                parsePackets(this);
            }
        };
        readerThread.setName("Smack Packet Reader (" + connection.connectionCounterValue + ")");
        readerThread.setDaemon(true);
        // Create an executor to deliver incoming packets to listeners. We'll use a single
        // thread with an unbounded queue.
        listenerExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {

            public Thread newThread(Runnable runnable) {
                Thread thread = new Thread(runnable,
                        "Smack Listener Processor (" + connection.connectionCounterValue + ")");
                thread.setDaemon(true);
                return thread;
            }
        });
        resetParser();
    }

private void resetParser() {
        try {
            parser = XmlPullParserFactory.newInstance().newPullParser();
            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
            parser.setInput(connection.reader);
        }
        catch (XmlPullParserException xppe) {
            xppe.printStackTrace();
        }
    }

这里采用的pull解析xml数据,由于把这里的xml数据主要有三个节点,message、iq、presence,分别代表的是消息、请求响应、出席,我们先来看message节点的处理,这里调用的是PacketParserUtils.parseMessage,里面同样也是XmlPullParser解析器解析数据,拿到数据后将数据封装为Message对象后然后返回,获取id、to、from、type等数据,然后我们看看processPacket(packet)方法,具体做了哪些操作

 public static Packet parseMessage(XmlPullParser parser) throws Exception {
        Message message = new Message();
        String id = parser.getAttributeValue("", "id");
        message.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id);
        message.setTo(parser.getAttributeValue("", "to"));
        message.setFrom(parser.getAttributeValue("", "from"));
        message.setType(Message.Type.fromString(parser.getAttributeValue("", "type")));
        String language = getLanguageAttribute(parser);
         return message;
  }

 private void parsePackets(Thread thread) {
  try {
            int eventType = parser.getEventType();
            do {
                if (eventType == XmlPullParser.START_TAG) {
                    int parserDepth = parser.getDepth();
                    ParsingExceptionCallback callback = connection.getParsingExceptionCallback();
                    if (parser.getName().equals("message")) {
                        Packet packet;
                        try {
                            packet = PacketParserUtils.parseMessage(parser);
                        } catch (Exception e) {
                            String content = PacketParserUtils.parseContentDepth(parser, parserDepth);
                            UnparsablePacket message = new UnparsablePacket(content, e);
                            if (callback != null) {
                                callback.handleUnparsablePacket(message);
                            }
                            continue;
                        }
                        processPacket(packet);
                    }
                    else if (parser.getName().equals("iq")) {
                        IQ iq;
                        try {
                            iq = PacketParserUtils.parseIQ(parser, connection);
                        } catch (Exception e) {
                            String content = PacketParserUtils.parseContentDepth(parser, parserDepth);
                            UnparsablePacket message = new UnparsablePacket(content, e);
                            if (callback != null) {
                                callback.handleUnparsablePacket(message);
                            }
                            continue;
                        }
                        processPacket(iq);
                    }
                    else if (parser.getName().equals("presence")) {
                        Presence presence;
                        try {
                            presence = PacketParserUtils.parsePresence(parser);
                        } catch (Exception e) {
                            String content = PacketParserUtils.parseContentDepth(parser, parserDepth);
                            UnparsablePacket message = new UnparsablePacket(content, e);
                            if (callback != null) {
                                callback.handleUnparsablePacket(message);
                            }
                            continue;
                        }
                        processPacket(presence);
                    }
 }

这里出现listenerExecutor,存储了监听的消息池,然后调用了ListenerNotification通知了所有的消息监听器接收消息,并且自己去判断消息的类型的来源,而 collector.processPacket(packet)则是将当前接收到的消息存储到消息队列中,发送和接收的消息处于同一消息队列

 private void processPacket(Packet packet) {
        if (packet == null) {
            return;
        }
        // Loop through all collectors and notify the appropriate ones.
        for (PacketCollector collector: connection.getPacketCollectors()) {
            collector.processPacket(packet);
        }
        // Deliver the incoming packet to listeners.
        listenerExecutor.submit(new ListenerNotification(packet));
    }

 private class ListenerNotification implements Runnable {

        private Packet packet;
        public ListenerNotification(Packet packet) {
            this.packet = packet;
        }
        public void run() {
            for (ListenerWrapper listenerWrapper : connection.recvListeners.values()) {
                listenerWrapper.notifyListener(packet);
            }
        }
    }
  protected void processPacket(Packet packet) {
        if (packet == null) {
            return;
        }
        if (packetFilter == null || packetFilter.accept(packet)) {
            while (!resultQueue.offer(packet)) {
                // Since we know the queue is full, this poll should never actually block.
                resultQueue.poll();
            }
        }
    }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值