1.keepalive和Idle檢測
keepalive機制:如果對方突然無響應,我們需要發送一個探測幀去查看對方是是否下線。
Idle檢測:如果你發送數據給對方,對方無響應,你會等一段時間(Idle檢測),如果對方無響應,你就會發送心跳包(Idle檢測)
2. 兩種設置keepalive的方式
下面兩行代碼都可以開啓keepalive模式,keepalive模式默認是關閉的
跟進childOption
源碼,發現它是ServerBootstrap
裏面的一個LinkedHashMap
,用於保存與客戶端的socketChannel的操作。
在該類中搜索發現在init()
方法中,childOptions
在初始化ServerBootstrapAcceptor
被當作參數傳入。
ServerBootstrapAcceptor
的是在連接建立完成之後的以下列操作的一個封裝類
在裏面的channelRead()
方法中使用了setChannelOptions
方法去完成socketChannel的相關option的設置
繼續跟進源碼,setChannelOptions
方法使用了setChannelOption
將map中的操作都設置到socketChannel中,setChannelOption
又使用了setOption
去完成此操作。
因爲我們討論的是NioSocketChannel
,所以我們查看其setOption
方法
2.1 如果java版本大於7並且option是NioChannelOption
調用jdk對channel設置option
2.2 jdk版本不支持或者設置的是OIO類型
手動使用if else完成對channel中的option的設置,如果遇到了if else中不存在的option類型,需要手動在if else上添加判斷分支。
總結:下面兩個代碼的區別是,一個通過if else的方式手動設置channel的option,NioChannelOption則是另一種方式是,它使用的是jdk的設置option的方式設置的,不需要手動增加if else判斷。
.childOption(ChannelOption.SO_KEEPALIVE,true )
.childOption(NioChannelOption.SO_KEEPALIVE,true )
3. IdleStateHandler如何處理Idle
ReaderIdle處理邏輯
private final class ReaderIdleTimeoutTask extends AbstractIdleTask {
ReaderIdleTimeoutTask(ChannelHandlerContext ctx) {
super(ctx);
}
@Override
protected void run(ChannelHandlerContext ctx) {
long nextDelay = readerIdleTimeNanos;// 空閒的延遲時間
if (!reading) {
// 下一個需要定時的任務的延遲時間
nextDelay -= ticksInNanos() - lastReadTime;
}
if (nextDelay <= 0) {// 發生空閒,創建一個schedule任務,延時時間爲readerIdleTimeNanos
// Reader is idle - set a new timeout and notify the callback.
readerIdleTimeout = schedule(ctx, this, readerIdleTimeNanos, TimeUnit.NANOSECONDS);
boolean first = firstReaderIdleEvent;
firstReaderIdleEvent = false;
try {
IdleStateEvent event = newIdleStateEvent(IdleState.READER_IDLE, first);
// 發生事件的回調,將事件放入pipeline
channelIdle(ctx, event);
} catch (Throwable t) {
ctx.fireExceptionCaught(t);
}
} else {
// 沒有發生空閒,創建一個schedule任務,延時時間爲nextDelay
// Read occurred before the timeout - set a new timeout with shorter delay.
readerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
}
}
}
WriterIdle事件,多了一個hasOutputChanged()
方法
private final class WriterIdleTimeoutTask extends AbstractIdleTask {
WriterIdleTimeoutTask(ChannelHandlerContext ctx) {
super(ctx);
}
@Override
protected void run(ChannelHandlerContext ctx) {
long lastWriteTime = IdleStateHandler.this.lastWriteTime;
long nextDelay = writerIdleTimeNanos - (ticksInNanos() - lastWriteTime);
if (nextDelay <= 0) {
// Writer is idle - set a new timeout and notify the callback.
writerIdleTimeout = schedule(ctx, this, writerIdleTimeNanos, TimeUnit.NANOSECONDS);
boolean first = firstWriterIdleEvent;
firstWriterIdleEvent = false;
try {
// 和ReaderIdle不同之處
if (hasOutputChanged(ctx, first)) {
return;
}
IdleStateEvent event = newIdleStateEvent(IdleState.WRITER_IDLE, first);
channelIdle(ctx, event);
} catch (Throwable t) {
ctx.fireExceptionCaught(t);
}
} else {
// Write occurred before the timeout - set a new timeout with shorter delay.
writerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
}
}
}
查看hasOutputChanged()
方法
/**
* Returns {@code true} if and only if the {@link IdleStateHandler} was constructed
* with {@link #observeOutput} enabled and there has been an observed change in the
* {@link ChannelOutboundBuffer} between two consecutive calls of this method.
*
* https://github.com/netty/netty/issues/6150
*/
private boolean hasOutputChanged(ChannelHandlerContext ctx, boolean first) {
// 正常情況下:false,即寫空閒的判斷中的寫指的是寫成功,但實際可能遇到以下情況
// (1)寫了,緩存區滿了,沒有寫成功
// (2)寫了一個大數據,寫了,但沒有完成
// 這個參數是判斷寫的意圖,而不是判斷是否寫成功
if (observeOutput) {
// We can take this shortcut if the ChannelPromises that got passed into write()
// appear to complete. It indicates "change" on message level and we simply assume
// that there's change happening on byte level. If the user doesn't observe channel
// writability events then they'll eventually OOME and there's clearly a different
// problem and idleness is least of their concerns.
// 上一次寫的時間和上一次發生變化的時間不同,說明正在寫
if (lastChangeCheckTimeStamp != lastWriteTime) {
lastChangeCheckTimeStamp = lastWriteTime;
// But this applies only if it's the non-first call.
if (!first) {
return true;
}
}
Channel channel = ctx.channel();
Unsafe unsafe = channel.unsafe();
ChannelOutboundBuffer buf = unsafe.outboundBuffer();
if (buf != null) {
int messageHashCode = System.identityHashCode(buf.current());
long pendingWriteBytes = buf.totalPendingWriteBytes();
// pendingWriteBytes和上一次lastPendingWriteBytes不相同,說明正在追加數據
if (messageHashCode != lastMessageHashCode || pendingWriteBytes != lastPendingWriteBytes) {
lastMessageHashCode = messageHashCode;
lastPendingWriteBytes = pendingWriteBytes;
if (!first) {
return true;
}
}
// flush的進度不同說明正在寫
long flushProgress = buf.currentProgress();
if (flushProgress != lastFlushProgress) {
lastFlushProgress = flushProgress;
if (!first) {
return true;
}
}
}
}
return false;
}
其中兩個Idle異常:
發生ReadIdle問題時,ReadTimeoutHandler
是這樣處理的,直接拋出異常。
發生WriteIdle問題時,系統自帶的處理類WriteTimeoutHandler
會先判斷一段時間內wirte任務是否完成。