1.什么是keepalive和为什么需要keepalive
为什么需要keepalive?
如果建立TCP连接之后,如果由于某些异常原因,导致连接已经损坏。但是此时双端仍然在维持该连接,此时则会浪费资源,已经在使用时产生报错
连接损坏如何定义?
- 对端异常“崩溃”
- 对端在,但是处理不过来
- 对端在,但是网络请求不可达
2.如何设计keepalive

3.为什么应用层需要keepalive
原因如下:
- 协议分层,各层关注点不同:传输层关注是否通,应用层关注是否可服务,例如虽然连接是ok的,但是可能对端应用无法提供正常服务
- TCP 层的 keepalive 默认关闭,且经过路由等中转设备 keepalive 包可能会被丢弃,传输层的调整会影响全部应用
- TCP的keepalive时间太长

4.什么是Idle检测
Idle检测只是负责对连接的诊断、分析并作出不同的行为,主要用于判断连接是否空闲or连接是否异常,从而来决定是否采取keepalive,有助于减少keepalive的次数
其中Idle状态分为如下三种:
- readerIdleTime:读超时时间,当前端一段时间未收到对端消息
- writerIdleTime:写超时时间,当前端一段时间未向对端发送消息
- allIdeTime:所有类型(当前端既没有传也没有收到数据)的超时时间
/**
* An {@link Enum} that represents the idle state of a {@link Channel}.
*/
public enum IdleState {
/**
* No data was received for a while.
*/
READER_IDLE,
/**
* No data was sent for a while.
*/
WRITER_IDLE,
/**
* No data was either received or sent for a while.
*/
ALL_IDLE
}
5.如何使用TCP keepalive和Idle检测
Server 端开启 TCP keepalive
bootstrap.childOption(ChannelOption.SO_KEEPALIVE,true)
bootstrap.childOption(NioChannelOption.of(StandardSocketOptions.SO_KEEPALIVE), true)
提示:.option(ChannelOption.SO_KEEPALIVE,true) 存在但是无效
开启不同的 Idle 检测:
ch.pipeline().addLast("idleCheckHandler", new IdleStateHandler(0, 20, 0, TimeUnit.SECONDS));
6.Idle检测类包的功能概览

4.读Idle检测的原理
io.netty.handler.timeout.IdleStateEvent:ReaderIdleTimeoutTask
5.写Idle检测原理和ObserverOutput用途
io.netty.handler.timeout.IdleStateHandler:WriterIdleTimeoutTask
其中observeOutput的用处如下,用于写Idle检测判断条件的选择,true-写空闲判断中的写是指有写意向则算写 false-写空闲的判断中的写是指写成功
private boolean hasOutputChanged(ChannelHandlerContext ctx, boolean first) {
if (observeOutput) {
//正常情况下,false,即写空闲的判断中的写是指写成功,但是实际上,有可能遇到几种情况:
//(1)写了,但是缓存区满了,写不出去;(2)写了一个大“数据”,写确实在“动”,但是没有完成。
//所以这个参数,判断是否有“写的意图”,而不是判断“是否写成功”。
// 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();
if (messageHashCode != lastMessageHashCode || pendingWriteBytes != lastPendingWriteBytes) {
lastMessageHashCode = messageHashCode;
lastPendingWriteBytes = pendingWriteBytes;
if (!first) {
return true;
}
}
long flushProgress = buf.currentProgress();
if (flushProgress != lastFlushProgress) {
lastFlushProgress = flushProgress;
if (!first) {
return true;
}
}
}
}
return false;
}