Android dPNクライアントのブロッキング読み書き(1)


もっと読む
      Android PNサーバーは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を走って、この方法の主なコード:
            // 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 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に入れると、writer Threadが呼び覚まされ、引き続き動作し、標準的な生産消費パターンが表示されます。この過程はやはり比較的簡単で、一目で分かります。PacketReaderに比べて、そんなに気を使いません。
 
    PacketReader初期化:
    protected void init() {
//...
        readerThread = new Thread() {
            public void run() {
                parsePackets(this);
            }
        };
 //...
        resetParser();
    }
 次にパーパーパーPacketsを見ます。
    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()が見えませんでした。どこで読みますか?また入力フローはどこですか?
上記の初期化方法において最後のreetPaser():
    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をパーパーパーに渡しました。パーサーからしか検索できないようです。XmlPulParsser Factoryでは
    public XmlPullParser newPullParser() throws XmlPullParserException {
        final XmlPullParser pp = new KXmlParser();
        for (Map.Entry entry : features.entrySet()) {
            pp.setFeature(entry.getKey(), entry.getValue());
        }

        return pp;
    }
 パーサーはorg.kxml 2.io.KXml Parsserから来ました。引き続きkxml 2ソースのカバンを探しています。本当に困りました。
  public void setInput(Reader reader) throws XmlPullParserException {
        this.reader = reader;
//...
        if (reader == null)
            return;
//...
    }
 socket入力フローがパーパーに渡されていることが分かります。ついでにパーパーパックス方法で使用するパー.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++] = '
'; } else { if (nw == '
') { if (!wasCR) peek[peekCount++] = '
'; } else peek[peekCount++] = nw; wasCR = false; } } return peek[pos]; }
 ここにやっとreader.readが現れました。ここで読書をブロックします。これはどんなに苦しい過程ですか?xml解析のために、一行のコードを数百行のコードに変えなければなりません。電気がかかります。ご存知ですか?
 これで終わります。午前中にこれらを見ました。収穫があって、頭が痛いです。