一、心跳
什么是心跳
在 TPC 中,客户端和服务端建立连接之后,需要定期发送数据包,来通知对方自己还在线,以确保 TPC 连接的有效性。如果一个连接长时间没有心跳,需要及时断开,否则服务端会维护很多无用连接,浪费服务端的资源。
IdleStateHandler
Netty 已经为我们提供了心跳的 Handler:IdleStateHandler
。当连接的空闲时间(读或者写)太长时,IdleStateHandler
将会触发一个 IdleStateEvent
事件,传递的下一个 Handler。我们可以通过在 Pipeline Handler 中重写 userEventTrigged
方法来处理该事件,注意我们自己的 Handler 需要在 IdleStateHandler
后面。
下面我们来看看 IdleStateHandler 的源码。
1. 构造函数
最完整的构造函数如下:
public IdleStateHandler(boolean observeOutput,
long readerIdleTime, long writerIdleTime, long allIdleTime,
TimeUnit unit) {
}
参数解析:
observeOutput
:是否考虑出站时较慢的情况。如果 true:当出站时间太长,超过空闲时间,那么将不触发此次事件。如果 false,超过空闲时间就会触发事件。默认 false。readerIdleTime
:读空闲的时间,0 表示禁用读空闲事件。writerIdleTime
:写空闲的时间,0 表示禁用写空闲事件。allIdleTime
:读或写空闲的时间,0 表示禁用事件。unit
:前面三个时间的单位。
2. 事件处理
IdleStateHandler
继承 ChannelDuplexHandler
,重写了出站和入站的事件,我们来看看代码。
当 channel 添加、注册、活跃的时候,会初始化 initialize(ctx)
,删除、不活跃的时候销毁 destroy()
,读写的时候设置 lastReadTime
和 lastWriteTime
字段。
public class IdleStateHandler extends ChannelDuplexHandler {
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel().isActive() && ctx.channel().isRegistered()) {
initialize(ctx);
}
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
destroy();
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel().isActive()) {
initialize(ctx);
}
super.channelRegistered(ctx);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
initialize(ctx);
super.channelActive(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
destroy();
super.channelInactive(ctx);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 判断是否开启 读空闲 或者 读写空闲 监控
if (readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
// 设置 reading 标志位
reading = true;
firstReaderIdleEvent = firstAllIdleEvent = true;
}
ctx.fireChannelRead(msg);
}
// 读完成之后
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// 判断是否开启 读空闲 或者 读写空闲 监控,检查 reading 标志位
if ((readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) && reading) {
// 设置 lastReadTime,后面判断读超时有用
lastRe