首页 > 解决方案 > 如何确定在 Android 上通过 TLS/TCP OutputStream 发送了多少数据?

问题描述

关键是这个——我的 Android TLS/TCP 套接字有时会断开中间数据流。通常是因为超时等。我试图确定在套接字切断之前实际成功发送了多少数据,所以我知道从哪里获取。

我该怎么做呢?

我的想法是从套接字中提取输出流,一次写一点。如果我们断开连接,那么我可以从中断的地方继续并继续发送下一个块。

我选择不使用 BufferedOutputStream,因为断开连接后我不确定发送了多少,没有发送多少。我很想弄错。

这是我最好的尝试:

/**
 * Static class that holds the currently writable data
 */
final private class WritingByteArray {
    /**
     * Easy C-tor
     */
    public WritingByteArray( final byte[] bytes ) {
        this.bytes = bytes;
        this.offset = 0;
    }

    /**
     * Convenience
     */
    public int length() {
        return( bytes.length );
    }

    /**
     * Convenience
     */
    public int remaining( int max ) {
        return( Math.min( max, length()-offset ) );
    }

    /**
     * Consume bytes. Really just increment the offset with a safety catch
     */
    private void consume( int size ) {
        offset = Math.min( bytes.length, offset+size );
    }

    /**
     * Remaining without a max
     */
    public int remaining() {
        return( length()-offset );
    }

    /**
     * Are we empty?
     */
    public boolean empty() {
        return( offset>=length() );
    }

    public byte[] bytes; //!< The byte array we're working with
    public int offset; //!< Our reading offset
}

/**
 * Our write data function new
 */
private void writeDataNew() {
    final int WRITE_BUFFER_SIZE = ( 8*1024 ); //!< 8kb of write buffer size

    try {
        OutputStream outputStream = sslSocket.getOutputStream();

        // Do we have data we are currently writing
        if( currentByteArray==null ) {
            Log.d(
                    TAG,
                    "We don't have anything in the current byte array. Pulling from the output buffer."
                 );

            // Pull from the outputBuffer
            synchronized( outputBuffer ) {
                final int size = outputBuffer.size();

                Log.d( TAG, "OutputBuffer has "+size );

                // This was one done outside of synchronization... that's bad!
                if( size==0 ) {
                    Log.d( TAG, "Our output buffer is actually empty." );
                    return;
                }

                // Copy the bytes to our private byte array
                currentByteArray = new WritingByteArray( outputBuffer.toByteArray() );

                // Get the data
                //                    outputStream.write( outputBuffer.toByteArray() );
                // Reset the buffer
                outputBuffer.reset();
            } // Free the mutex so bytearray can be written again
        }

        // Now write the data
        final int size = currentByteArray.remaining( WRITE_BUFFER_SIZE );

        // Do we have bytes to send?
        if( size>0 ) {
            // Alert how much we'll write
            Log.d( TAG, "We have "+size+" data to write" );
            // Write our deduced size
            outputStream.write( currentByteArray.bytes, currentByteArray.offset, size );
            // Flush the output so we can block???
            // DOES THIS BLOCK?
            // If this blocks maybe this will work...
            outputStream.flush();
            // Just a log tag
            Log.d( TAG, "Data wrote "+size+" bytes of data." );

            // Now consume
            currentByteArray.consume( size );

            // Do we have any left?
            if( currentByteArray.empty() ) {
                Log.d( TAG, "Clearing currentByteArray" );

                currentByteArray = null; // Clear it so we know
            }
        }
        else {
            Log.d( TAG, "No more data left " );
            // Just in case
            currentByteArray = null;
        }

        // Call again
        writeDataNew();
    }
    catch( final Exception e ) {
        e.printStackTrace();

        reportError( e.getLocalizedMessage() );

        // Here we can reconnect and send more data if we failed out
    }
}

标签: androidsocketsssltcp

解决方案


有关对等方已接收到多少数据的信息基本上是不可恢复的(当讨论仅限于 TCP 协议本身时)。IOW:你不能仅仅通过修改客户端来做你想做的事情。

是的,TCP确实包含双向 ACK,其中包含有关已成功传输多少数据的信息,但该信息不能直接用于应用层。

即使您可以访问 TCP ACK 信息,也无法知道在连接失败时您已收到对等方发送的每个 ACK ​​。(见:两个将军的问题。)

请注意,即使您不使用 a BufferedOutputStream,缓冲仍然发生在 TLS 层和多个网络层,包括内核中。

底线:不可能 100% 确定地知道对等方的“接收”状态,除非此信息被明确传输,或者两个对等方有某种方式就连接开始时存在的共同“基本”状态达成一致。

大多数实际系统将在对话开始时让客户端查询对等方的状态(HTTP HEAD例如),或者它们将跟踪对等方发送的显式确认。请注意,即使是有序的连接关闭也可以作为明确的确认。


推荐阅读