public class ReadIdleAfterWritingStateHandler extends ChannelDuplexHandler {
private static final long MIN_TIMEOUT_NANOS = TimeUnit.MILLISECONDS.toNanos(1);
// writing한 후 reading이 없는 제한시간
private final long readTimeout;
private volatile ScheduledFuture<?> timeout;
private volatile long lastWriteTime;
private volatile boolean readed;
public ReadIdleAfterWritingStateHandler(long readerIdleTimeAfterWriting) {
if (readerIdleTimeAfterWriting <= 0)
throw new InvalidParameterException("time must > 0");
readTimeout = Math.max(TimeUnit.SECONDS.toNanos(readerIdleTimeAfterWriting), MIN_TIMEOUT_NANOS);
}
public long getReaderIdleTimeInMillis() {
return TimeUnit.NANOSECONDS.toMillis(readTimeout);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
destroy();
super.handlerRemoved(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
destroy();
super.channelInactive(ctx);
}
@Override
public void write(final ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
// Allow writing with void promise if handler is only configured for read timeout events.
if (readTimeout > 0) {
ChannelPromise unvoid = promise.unvoid();
ctx.write(msg, unvoid).addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
lastWriteTime = System.nanoTime();
initialize(ctx);
}
});
} else {
ctx.write(msg, promise);
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
destroy();
readed = true;
ctx.fireChannelRead(msg);
}
private void initialize(ChannelHandlerContext ctx) {
readed = false;
if(timeout!= null && !timeout.isDone())
timeout.cancel(false);
timeout = ctx.executor().schedule(new ReaderIdleTimeoutTask(ctx), readTimeout, TimeUnit.NANOSECONDS);
}
synchronized private void destroy() {
if (timeout != null) {
timeout.cancel(false);
timeout = null;
}
}
/**
* Is called when an {@link IdleStateEvent} should be fired. This implementation calls
* {@link ChannelHandlerContext#fireUserEventTriggered(Object)}.
*/
protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception {
ctx.fireExceptionCaught(ReadTimeoutException.INSTANCE);
ctx.close();
}
private final class ReaderIdleTimeoutTask implements Runnable {
private final ChannelHandlerContext ctx;
ReaderIdleTimeoutTask(ChannelHandlerContext ctx) {
this.ctx = ctx;
}
public void run() {
if (!ctx.channel().isOpen())
return;
long nextDelay = readTimeout;
if (readed) {
destroy();
return;
}
else
nextDelay -= System.nanoTime() - lastWriteTime;
if (nextDelay <= 0) {
// Reader is idle - set a new timeout and notify the callback.
//readerIdleTimeoutAfterWriting = ctx.executor().schedule(this, readerIdleTimeNanosAfterWriting, TimeUnit.NANOSECONDS);
destroy();
try {
channelIdle(ctx, IdleStateEvent.READER_IDLE_STATE_EVENT);
}
catch (Throwable t) {
ctx.fireExceptionCaught(t);
}
}
else {
// Read occurred before the timeout - set a new timeout with shorter delay.
timeout = ctx.executor().schedule(this, nextDelay, TimeUnit.NANOSECONDS);
}
}
}
}
고민은 ReaderIdleTimeoutTask의 run()에서
if (readed) {
destroy();
return;
}
else
nextDelay -= System.nanoTime() - lastWriteTime;
이 부분인데요 readed가 false여서 return을 하지 않고 계속 진행하는 도중에
패킷이 들어와서 public void channelRead(ChannelHandlerContext ctx, Object msg)가 호출되는 경우입니다.
IdleStateHandler를 분석해봤는데 제 능력에서는 위와 같은 경우에 어떻게 되는 건지 잘 모르겠더라구요.
조언을 해주시면 정말 큰 도움이 될 것 같습니다.
감사합니다.
2016년 6월 24일 금요일 오후 3시 45분 21초 UTC+9, 이홍일 님의 말: