Netty心跳基本檢測機制
首先赡勘,了解下為什么需要心跳从橘?
假設(shè)客戶端(如手機矩距,PAD)與服務(wù)器端已經(jīng)建立了長連接拗盒,客戶端開啟飛行模式或者強制關(guān)機了,服務(wù)器端的handlerRemoved方法不會被調(diào)用锥债,因為感知不到锣咒。此時,客戶端往服務(wù)器端發(fā)送的消息無法到達赞弥。如何處理這種情況呢毅整?那么就是通過心跳來做的,每隔一段時間客戶端向服務(wù)器端發(fā)送心跳包绽左,客戶端經(jīng)過一段時間收到后悼嫉,客戶端認為與服務(wù)器端連接是正常的。如果客戶端經(jīng)過一段時間沒有收到心跳包拼窥,客戶端則認為與服務(wù)端無法通信戏蔑,由客戶端發(fā)起斷開連接,然后重新向服務(wù)器端發(fā)起新的連接鲁纠。
Netty提供了空閑狀態(tài)監(jiān)測的處理器总棵。IdleStateHandler
我們來看下Javadoc說明
/**
* Triggers an {@link IdleStateEvent} when a {@link Channel} has not performed
* read, write, or both operation for a while.
*
* <h3>Supported idle states</h3>
* <table border="1">
* <tr>
* <th>Property</th><th>Meaning</th>
* </tr>
* <tr>
* <td>{@code readerIdleTime}</td>
* <td>an {@link IdleStateEvent} whose state is {@link IdleState#READER_IDLE}
* will be triggered when no read was performed for the specified period of
* time. Specify {@code 0} to disable.</td>
* </tr>
* <tr>
* <td>{@code writerIdleTime}</td>
* <td>an {@link IdleStateEvent} whose state is {@link IdleState#WRITER_IDLE}
* will be triggered when no write was performed for the specified period of
* time. Specify {@code 0} to disable.</td>
* </tr>
* <tr>
* <td>{@code allIdleTime}</td>
* <td>an {@link IdleStateEvent} whose state is {@link IdleState#ALL_IDLE}
* will be triggered when neither read nor write was performed for the
* specified period of time. Specify {@code 0} to disable.</td>
* </tr>
* </table>
*
* <pre>
* // An example that sends a ping message when there is no outbound traffic
* // for 30 seconds. The connection is closed when there is no inbound traffic
* // for 60 seconds.
*
* public class MyChannelInitializer extends {@link ChannelInitializer}<{@link Channel}> {
* {@code @Override}
* public void initChannel({@link Channel} channel) {
* channel.pipeline().addLast("idleStateHandler", new {@link IdleStateHandler}(60, 30, 0));
* channel.pipeline().addLast("myHandler", new MyHandler());
* }
* }
*
* // Handler should handle the {@link IdleStateEvent} triggered by {@link IdleStateHandler}.
* public class MyHandler extends {@link ChannelDuplexHandler} {
* {@code @Override}
* public void userEventTriggered({@link ChannelHandlerContext} ctx, {@link Object} evt) throws {@link Exception} {
* if (evt instanceof {@link IdleStateEvent}) {
* {@link IdleStateEvent} e = ({@link IdleStateEvent}) evt;
* if (e.state() == {@link IdleState}.READER_IDLE) {
* ctx.close();
* } else if (e.state() == {@link IdleState}.WRITER_IDLE) {
* ctx.writeAndFlush(new PingMessage());
* }
* }
* }
* }
*
* {@link ServerBootstrap} bootstrap = ...;
* ...
* bootstrap.childHandler(new MyChannelInitializer());
* ...
* </pre>
*
* @see ReadTimeoutHandler
* @see WriteTimeoutHandler
*/
public class IdleStateHandler extends ChannelDuplexHandler {
}
在一定時間內(nèi)當(dāng)有讀、寫或者讀寫沒有被執(zhí)行的時候改含,會觸發(fā)一個空閑狀態(tài)監(jiān)測事件情龄。
從Javadoc中我們也看到了基本的示例代碼,理論+實踐模式 :)
進一步看下構(gòu)造方法:
/**
* Creates a new instance firing {@link IdleStateEvent}s.
*
* @param readerIdleTimeSeconds
* an {@link IdleStateEvent} whose state is {@link IdleState#READER_IDLE}
* will be triggered when no read was performed for the specified
* period of time. Specify {@code 0} to disable.
* @param writerIdleTimeSeconds
* an {@link IdleStateEvent} whose state is {@link IdleState#WRITER_IDLE}
* will be triggered when no write was performed for the specified
* period of time. Specify {@code 0} to disable.
* @param allIdleTimeSeconds
* an {@link IdleStateEvent} whose state is {@link IdleState#ALL_IDLE}
* will be triggered when neither read nor write was performed for
* the specified period of time. Specify {@code 0} to disable.
*/
public IdleStateHandler(
int readerIdleTimeSeconds,
int writerIdleTimeSeconds,
int allIdleTimeSeconds) {
this(readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds,
TimeUnit.SECONDS);
}
構(gòu)造方法中提供了三個參數(shù)捍壤,第一個參數(shù)readerIdleTimeSeconds是指給定時間內(nèi)沒有讀動作骤视,則觸發(fā)READER_IDLE事件;第二個參數(shù)writerIdleTimeSeconds是指給定時間內(nèi)服務(wù)端沒有寫入動作則觸發(fā)WRITER_IDLE事件鹃觉,第三個參數(shù)allIdleTimeSeconds表示給定實踐內(nèi)沒有發(fā)生讀或者寫動作专酗,則觸發(fā)ALL_IDLE事件。
我們可以編寫自定義處理器盗扇,監(jiān)測空閑狀態(tài)祷肯,繼承SimpleChannelInboundHandler沉填,同時,重寫
userEeventTriggered(ChannelHandlerContext ctx佑笋,Object evt)方法拜轨,兩個參數(shù)分別是上下文對象,事件對象允青。userEeventTriggered方法是SimpleChannelInboundHandler父類ChannelInboundHandlerAdapter抽象適配類的方法。
當(dāng)有操作操作超出指定空閑秒數(shù)時卵沉,便會觸發(fā)UserEventTriggered事件颠锉。
IdleState代表了Channel的空心閑狀態(tài)枚舉,三種狀態(tài)史汗,包括READ_IDLE琼掠、WRITE_IDLE、ALL_IDLE
下面附上示例代碼:
- Main方法啟動類停撞,這里我們增加了LoggerHandler日志處理器
public class MyIdleServer {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new MyIdleServerInitializer());
ChannelFuture channelFuture = bootstrap.bind(8899).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
- 編寫MyIdleServerInitializer瓷蛙,初始化處理器鏈,增加了IdleStateHandler空閑事件處理器.
public class MyIdleServerInitializer extends ChannelInitializer<SocketChannel>{
@Override protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline channelPipeline = ch.pipeline();
channelPipeline.addLast(new IdleStateHandler(6, 4, 10));
channelPipeline.addLast(new MyIdleServerHandler());
}
}
- 編寫自定義處理器MyIdleServerHandler戈毒,重寫了channelRead0艰猬,目的主要為了測試寫超時觸發(fā)事件.
public class MyIdleServerHandler extends SimpleChannelInboundHandler<Object> {
@Override public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
int i=0;
while (i< 3) {
sleep(3000);
System.out.println("服務(wù)端執(zhí)行寫入操作3s");
ctx.channel().writeAndFlush("服務(wù)端寫個客戶端數(shù)據(jù)");
i++;
}
}
@Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent idleStateEvt = (IdleStateEvent) evt;
String eventType = null; // 空閑事件類型
switch (idleStateEvt.state()) {
case READER_IDLE:
eventType = "讀空閑";
break;
case WRITER_IDLE:
eventType = "寫空閑";
break;
case ALL_IDLE:
eventType = "讀寫空閑";
break;
}
System.out.println("客戶端地址:" + ctx.channel().remoteAddress() + "超時事件被觸發(fā),eventType:" + eventType);
ctx.channel().close();
}
}
}
- 啟動MyIdleServer后埋市,使用MyChatClient請參見Netty多客戶端通信一章
因設(shè)置寫超時時間設(shè)置4秒冠桃,MyChatClient不輸入任何內(nèi)容,結(jié)果如下:
當(dāng)MyChatClient輸入內(nèi)容后道宅,執(zhí)行結(jié)果會看到等到channelRead0執(zhí)行完寫入操作后食听,超過寫超時時間才會觸發(fā)WRITE_IDLE狀態(tài).