欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

AndroidPN客户端的阻塞读写(1) 博客分类: android androidPN推送

程序员文章站 2024-02-23 11:54:52
...

      androidPN服务端用的是mina,略去不表,客户端的socket通讯用的是asmack,期间使用xmpp协议通讯,这个xmpp通用是通用了,但用的是xml格式互发,之间不得不加了一堆的xml解析,大部分篇幅都是干这个,对此没多大兴趣,这里只是简单记录一下阅读源码中client与server的阻塞读写,寻找可以借鉴之处。

     客户端启动之后,负责管理连接的XMPPConnection初始化:

 if (isFirstInitialization) {
                packetWriter = new PacketWriter(this);
                packetReader = new PacketReader(this);

   分别负责读写。然后启动二者:

            // Start the packet writer. This will open a XMPP stream to the server
            packetWriter.startup();
            // Start the packet reader. The startup() method will block until we
            // get an opening stream packet back from server.
            packetReader.startup();
            // Make note of the fact that we're now connected.
            connected = true;

   

先来看packetWriter是如何向服务器发送数据的,它的初始化方法

    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);
    }

开一个守候线程writeThread,跑writePackets(this),该方法主要代码:

            // 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();
                    }
                }
            }

 其中queue,线程安全:

private final BlockingQueue<Packet> queue;

 queue中有数据就write到服务器,如果没数据,阻塞在nextPacket():

    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;
    }

 看看,queue.wait(),写线程阻塞于此,省电! 既然有wait(),必然有notifyAll():

    public void sendPacket(Packet packet) {
        if (!done) {
            // Invoke interceptors for the new packet that is about to be sent. Interceptors
            // may modify the content of the packet.
            connection.firePacketInterceptors(packet);

            try {
                queue.put(packet);
            }
            catch (InterruptedException ie) {
                ie.printStackTrace();
                return;
            }
            synchronized (queue) {
                queue.notifyAll();
            }

            // Process packet writer listeners. Note that we're using the sending
            // thread so it's expected that listeners are fast.
            connection.firePacketSendingListeners(packet);
        }
    }

 当其他线程把要写的数据put进queue时,writerThread即被唤醒,继续运作,标准的生产消费模式。这个过程还是比较简单的,一目了然,相比之下PacketReader就不那么省心。

 

    PacketReader初始化:

    protected void init() {
//...
        readerThread = new Thread() {
            public void run() {
                parsePackets(this);
            }
        };
 //...
        resetParser();
    }
 接着看parsePackets:
    private void parsePackets(Thread thread) {
        try {
            int eventType = parser.getEventType();
            do {
                if (eventType == XmlPullParser.START_TAG) {
//...
//...很长很长,都在xml纠结
//...
                eventType = parser.next();
            } while (!done && eventType != XmlPullParser.END_DOCUMENT && thread == readerThread);
        }

好吧,循环体是有了,但没见着一个reader.read(),阻塞读在哪里?还有输入流在哪里?

注意到上面初始化方法中最后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();
        }
    }
 这里把输入流connection.reader交给了parser。看来只能从parser中查找,XmlPullParserFactory中
    public XmlPullParser newPullParser() throws XmlPullParserException {
        final XmlPullParser pp = new KXmlParser();
        for (Map.Entry<String, Boolean> entry : features.entrySet()) {
            pp.setFeature(entry.getKey(), entry.getValue());
        }

        return pp;
    }
 parser来自于org.kxml2.io.KXmlParser,继续下kxml2源码包找,真够折腾的
  public void setInput(Reader reader) throws XmlPullParserException {
        this.reader = reader;
//...
        if (reader == null)
            return;
//...
    }
 可见socket输入流交给了parser,顺便看下parsePackets方法中用到的parser.next()等方法,都调用了peek()方法:
    /** Does never read more than needed */

    private final int peek(int pos) throws IOException {
        while (pos >= peekCount) {
            int nw;
            if (srcBuf.length <= 1)
                nw = reader.read();
            else if (srcPos < srcCount)
                nw = srcBuf[srcPos++];
            else {
                srcCount = reader.read(srcBuf, 0, srcBuf.length);
                if (srcCount <= 0)
                    nw = -1;
                else
                    nw = srcBuf[0];
                srcPos = 1;
            }
            if (nw == '\r') {
                wasCR = true;
                peek[peekCount++] = '\n';
            }
            else {
                if (nw == '\n') {
                    if (!wasCR)
                        peek[peekCount++] = '\n';
                }
                else
                    peek[peekCount++] = nw;
                wasCR = false;
            }
        }
        return peek[pos];
    }
 这里终于出现了reader.read(),就在这里阻塞读。这是一个多么苦逼的过程。为了xml解析不得不把一行代码化为数百行代码,而且这样很费电的你知道吗^_^。
 到此结束,化了一上午看这些,有些收获,有些头痛
相关标签: androidPN 推送