一、什么是心跳检测机制
所谓心跳, 即在 TCP 长连接中, 客户端和服务器之间定期发送的一种特殊的数据包, 通知对方自己还在线, 以确保 TCP 连接的有效性.
心跳机制主要是客户端和服务端长时间连接时,客户端需要定时发送心跳包来保证自己是存活的,否则一个连接长时间没有作用就会浪费服务端的资源。
二、心跳检测机制的适用场景
长连接的应用场景非常的广泛,比如监控系统,IM系统,即时报价系统,推送服务等等。像这些场景都是比较注重实时性,如果每次发送数据都要进行一次DNS解析,建立连接的过程肯定是极其影响体验。
而长连接的维护必然需要一套机制来控制。比如 HTTP/1.0 通过在 header 头中添加 Connection:Keep-Alive参数,如果当前请求需要保活则添加该参数作为标识,否则服务端就不会保持该连接的状态,发送完数据之后就关闭连接。HTTP/1.1以后 Keep-Alive 是默认打开的。
Netty 是基于 TCP 协议开发的,在四层协议 TCP 协议的实现中也提供了 keepalive 报文用来探测对端是否可用。TCP 层将在定时时间到后发送相应的 KeepAlive 探针以确定连接可用性。
所以,心跳检测一般存在于建立长连接 或者 需要保活的场景。
三、netty的心跳检测机制
基础协议对应用来说不是那么尽善尽美,一个 Netty 服务端可能会面临上万个连接,如何去维护这些连接是应用应该去处理的事情。在 Netty 中提供了 IdleStateHandler 类专门用于处理心跳。
IdleStateHandler 的构造函数如下:
CopypublicIdleStateHandler(long readerIdleTime, long writerIdleTime,
long allIdleTime,TimeUnit unit){
}
说明:
IdleStateHandler 是netty 提供的处理空闲状态的处理器
long readerIdleTime : 表示多长时间没有读, 就会发送一个心跳检测包检测是否连接
long writerIdleTime : 表示多长时间没有写, 就会发送一个心跳检测包检测是否连接
long allIdleTime : 表示多长时间没有读写, 就会发送一个心跳检测包检测是否连接
IdleStateHandler 的文档说明:
triggers an {@link IdleStateEvent} when a {@link Channel} has not performed
read, write, or both operation for a while.
四、Netty心跳检测机制实例
我用即将编写的一个例子来解释什么是心跳检测机制。我将要实现这样的功能:
当服务器超过3秒没有读时,就提示读空闲
当服务器超过5秒没有写操作时,就提示写空闲
当服务器超过7秒没有读或者写操作时,就提示读写空闲
服务端:
public class MyServer {
public static void main(String[] args) throws Exception{
//创建两个线程组
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(); //8个NioEventLoop
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup);
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.handler(new LoggingHandler(LogLevel.INFO));
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//加入一个netty 提供 IdleStateHandler
/*
*当 IdleStateEvent 触发后 , 就会传递给管道 的下一个handler去处理
*通过调用(触发)下一个handler 的 userEventTiggered , 在该方法中去处理 IdleStateEvent(读空闲,写空闲,读写空闲)
*/
pipeline.addLast(new IdleStateHandler(7000,7000,10, TimeUnit.SECONDS));
//加入一个对空闲检测进一步处理的handler(自定义)
pipeline.addLast(new MyServerHandler());
}
});
//启动服务器
ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
自定义handler(在该handler中决定如何处理):
public class MyServerHandler extends ChannelInboundHandlerAdapter {
/**
*
* @param ctx 上下文
* @param evt 事件
* @throws Exception
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if(evt instanceof IdleStateEvent) {
//将 evt 向下转型 IdleStateEvent
IdleStateEvent event = (IdleStateEvent) evt;
String eventType = null;
switch (event.state()) {
case READER_IDLE:
eventType = "读空闲";
break;
case WRITER_IDLE:
eventType = "写空闲";
break;
case ALL_IDLE:
eventType = "读写空闲";
break;
}
//这里已经可以知道浏览器所处的空闲是何种空闲,可以执行对应的处理逻辑了
System.out.println(ctx.channel().remoteAddress() + "--超时时间--" + eventType);
System.out.println("服务器做相应处理..");
//如果发生空闲,我们关闭通道
// ctx.channel().close();
}
}
}