简体   繁体   English

如何在循环中或递归使用AsynchronousSocketChannel#read?

[英]How to use AsynchronousSocketChannel#read in a loop or recursively?

I found a related question , but it wasn't particularly helpful as it didn't provide a full example. 我找到了一个相关的问题 ,但是由于它没有提供完整的示例,因此它并不是特别有用。

The problem: how to use AsynchronousSocketChannel for reading data of unknown length using a buffer of a fixed size 问题:如何使用AsynchronousSocketChannel使用固定大小的缓冲区读取未知长度的数据

First attempt (reads once): 第一次尝试(读取一次):

final int bufferSize = 1024;
final SocketAddress address = /*ip:port*/;
final ThreadFactory threadFactory = Executors.defaultThreadFactory();
final ExecutorService executor = Executors.newCachedThreadPool(threadFactory);
final AsynchronousChannelGroup asyncChannelGroup = AsynchronousChannelGroup.withCachedThreadPool(executor, 5);
final AsynchronousSocketChannel client = AsynchronousSocketChannel.open(asyncChannelGroup);
client.connect(address).get(5, TimeUnit.SECONDS);//block until the connection is established

//write the request
Integer bytesWritten = client.write(StandardCharsets.US_ASCII.encode("a custom request in a binary format")).get();

//read the response
final ByteBuffer readTo = ByteBuffer.allocate(bufferSize);
final StringBuilder responseBuilder = new StringBuilder();
client.read(readTo, readTo, new CompletionHandler<Integer, ByteBuffer>() {
        public void completed(Integer bytesRead, ByteBuffer buffer) {
            buffer.flip();
            responseBuilder.append(StandardCharsets.US_ASCII.decode(buffer).toString());
            try {
                client.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        public void failed(Throwable exc, ByteBuffer attachment) {
            try {
                client.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
);
asyncChannelGroup.awaitTermination(5, TimeUnit.SECONDS);
asyncChannelGroup.shutdown();
System.out.println(responseBuilder.toString());

What changes would I need to make to cleanly implement continuously reading into the buffer while bytesRead != -1 (ie end of stream is reached)? bytesRead != -1 (即到达流的末尾)时,我需要进行哪些更改以干净地实现连续读入缓冲区?

Here's a simplified version of what I ended up doing (using Guava 's ListenableFuture ): 这是我最终所做的工作的简化版本(使用GuavaListenableFuture ):

class SomeUtilClass {
public interface Processor<T> {
    boolean process(Integer byteCount, ByteBuffer buffer);
    T result();
}
public static <T> ListenableFuture<T> read(
    final AsynchronousSocketChannel delegate,
    final Processor<T> processor,
    ByteBuffer buffer
) {
    final SettableFuture<T> resultFuture = SettableFuture.create();
    delegate.read(buffer, buffer, new Handler<T, Integer, ByteBuffer>(resultFuture) {
        public void completed(Integer bytesRead, ByteBuffer buffer) {
            buffer.flip();
            if(processor.process(bytesRead, buffer)) {
                buffer.clear();
                delegate.read(buffer, buffer, this);
            } else {
                resultFuture.set(processor.result());
            }
        }
    });
    return resultFuture;
}
}

Further improvements included using a Commons Pool of ByteBuffer s 进一步的改进包括使用ByteBufferCommons Pool

In my mind, the simplest way would be to split this code into its own method, then having the CompletionHandler recursively call that method when bytesRead != -1 . 在我看来,最简单的方法是将这段代码拆分为自己的方法,然后在bytesRead != -1bytesRead != -1 CompletionHandler递归调用该方法。 That way you can separate out the responsibilities of the code and avoid the necessity of either "busy waiting" or using Thread.sleep() while your asynchronous read is running. 这样,您可以分离代码的职责,并避免在异步读取运行时“忙等待”或使用Thread.sleep()的必要性。 You could, of course, also add a case when bytesRead == -1 to do something with the data that has been read in. 当然,您还可以添加一个情况,即bytesRead == -1对已读入的数据执行某些操作。

I wouldn't do anything prolonged from the callback methods failed and completed as they run on threads that aren't under your control. 我不会做任何事情,因为回调方法在不受您控制的线程上运行时failedfailed completed

I understand you want to continue listening for new bytes in the socket even when stream has reached its end ( bytesRead == -1 ). 我了解您希望即使流到达末尾( bytesRead == -1 )仍继续侦听套接字中的新字节。 Put the read method in a while(true) loop. read方法放入while(true)循环中。 Inside it, listen on a synchronized field set by the failed and completed methods. 在其中,侦听由failedcompleted方法设置的synchronized字段。 Let's call it myBytesRead . 我们称之为myBytesRead

In order to enable stopping the endless read, replace the while(true) with some other synchronized condition. 为了能够停止无休止的读取,请用其他一些synchronized条件替换while(true)

private static final BYTES_READ_INIT_VALUE = Integer.MIN_VALUE;
private static final BYTES_READ_COMPLETED_VALUE = -1;
private static final BYTES_READ_FAILED_VALUE = -2;
private Integer myBytesRead = BYTES_READ_INIT_VALUE;

private void setMyBytesRead(final Integer bytesRead) {
    synchronized(myBytesRead) {
        this.myBytesRead = bytesRead;
    }
}

private Integer getMyBytesRead() {
    synchronized(myBytesRead) {
        return myBytesRead;
    }
}

...

// in your method
while (true) {
    final int lastBytesRead = getMyBytesRead();
    if (lastBytesRead == BYTES_READ_FAILED_VALUE) {
        // log failure and retry?
    } else if (lastBytesRead != BYTES_READ_COMPLETED_VALUE) {
        // Thread.sleep( a while ); to avoid exhausting CPU
        continue;
    }

    // else lastBytesRead == BYTES_READ_COMPLETED_VALUE and you can start a new read operation
    client.read(readTo, readTo, new CompletionHandler<Integer, ByteBuffer>() {
            public void completed(Integer bytesRead, ByteBuffer buffer) {
                setMyBytesRead(bytesRead);
                buffer.flip();
                responseBuilder.append(StandardCharsets.US_ASCII.decode(buffer).toString());
                try {
                    client.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            public void failed(Throwable exc, ByteBuffer attachment) {
                try {
                    setMyBytesRead(BYTES_READ_FAILED_VALUE);
                    client.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    );
}

My initial attempt: 我最初的尝试:

package com.example;

import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;

public class LoopingReader implements Callable<String> {
    final AsynchronousSocketChannel client;
    final String responseTerminator;
    final StringBuilder responseBuilder;

    LoopingReader(
        AsynchronousSocketChannel client,
        String responseTerminator
    ) {
        this.client = client;
        this.responseTerminator = responseTerminator;

        responseBuilder = new StringBuilder();
    }

    public String call() {
        boolean doLoop;
        do {
            int bytesRead = executeIteration();//blocking
            boolean didReachEndOfStream = bytesRead == -1;
            boolean didEncounterResponseTerminator = responseBuilder.indexOf(responseTerminator) != -1;

            doLoop =  !didReachEndOfStream && !didEncounterResponseTerminator;
        } while(doLoop);
        return responseBuilder.toString();
    }

    int executeIteration() {
        final ByteBuffer buffer = ByteBuffer.allocate(256);//use pool here
        final int bytesRead;
        try {
            bytesRead = client.read(buffer).get();
        } catch (InterruptedException | ExecutionException e) {
            throw new IllegalStateException("Failed to read", e);
        }
        decodeAndAppend(buffer);
        return bytesRead;
    }

    void decodeAndAppend(ByteBuffer buffer) {
        buffer.flip();
        responseBuilder.append(StandardCharsets.US_ASCII.decode(buffer));
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 AsynchronousSocketChannel #read永远不会完成。 - AsynchronousSocketChannel#read is never completing. Java AsynchronousSocketChannel读取操作 - Java AsynchronousSocketChannel read operation AsynchronousSocketChannel Read/WritePendingException - 可以同步吗? - AsynchronousSocketChannel Read/WritePendingException - possible to synchronize? AsynchronousSocketChannel在一个中写入/读取所有消息 - AsynchronousSocketChannel write/read all messages in one 如果缓冲区完全填满,AsynchronousSocketChannel 永远不会返回 read = -1 - AsynchronousSocketChannel never returns read = -1 if the buffer is completely filled 如何设置Java NIO AsynchronousSocketChannel连接超时 - How to set Java NIO AsynchronousSocketChannel connect timeout 如何在AsynchronousSocketChannel上正确同步并发读取和写入 - How to properly synchronize concurrent reads and writes on a AsynchronousSocketChannel 如何递归使用比较器? - How to use a Comparator recursively? 如何从AsynchronousSocketChannel反序列化Serializable对象 - How to deserialize a Serializable object from AsynchronousSocketChannel 如何在Java中管理AsynchronousSocketChannel或AsynchronousServerSocketChannel - how to manage both AsynchronousSocketChannel or AsynchronousServerSocketChannel in java
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM