[英]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 ): 这是我最终所做的工作的简化版本(使用Guava的ListenableFuture ):
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 进一步的改进包括使用ByteBuffer的Commons 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 != -1
时bytesRead != -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. 我不会做任何事情,因为回调方法在不受您控制的线程上运行时failed
并failed
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. 在其中,侦听由failed
和completed
方法设置的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.