心跳机制的意义:
TCP空闲的时候是不会发送任何数据包。也就是说,当一个TCP的socket,客户端与服务端谁也不发送数据,会一直保持着连接。这其中如果有一方异常掉线(例如死机、路由被破坏、防火墙切断连接等),另一端如果没有发送数据,永远也不可能知道
。这对于一些服务型的程序来说,是灾难性的后果,将会导致服务端socket资源耗尽。
所以为了保证连接的有效性、及时有效地检测到一方的非正常断开,保证连接的资源被有效的利用,我们就会需要一种保活的机制,通常改机制两种处理方式:1、利用TCP协议层实现的Keepalive;2、自己在应用层实现心跳包。
两种方式的对比如下:
1、TCP协议自带的保活功能, 使用起来简单, 减少了应用层代码的复杂度. 更节省流量, 因为一般来说应用层的数据传输到协议层时都会被加上额外的包头包尾. 由TCP协议提供的检活, 其发的ACK包比应用层的心跳包耗费更少的流量。
2、应用层心跳包相对与Keepalive更灵活,因为协议层的心跳只能提供最纯粹的检活功能, 但是应用层自己可以随意控制,甚至加入一些额外逻辑。
3、应用层心跳包相对与Keepalive更灵活,应用层心跳包不依赖于协议,如若有一天不用TCP要改为UDP了,只需做少量改动甚至不用改动即可实现转换。
因此,作为通信框架的Netty,应用层实现心跳机制还是很有必要的。
Netty3实现心跳事件处理实例
1. 在pipeline属性中加入心跳检测机制,代码如下:
pipeline.addLast("idle",new IdleStateHandler(hashedWheelTimer,5,5,10));
这段代码表示设置心跳检测的时间间隔
2. 添加处理心跳的业务逻辑处理类:
pipeline.addLast("helloHandler",new HelloHandler());
3. 实现业务逻辑处理类,继承于SimpleChannelHandler;实现代码如下:
public class HelloHandler extends SimpleChannelHandler {
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
{
System.out.println(e.getMessage());
}
@Override
public void handleUpstream(final ChannelHandlerContext ctx, ChannelEvent e) throws Exception{
if(e instanceof IdleStateEvent)
{
if(((IdleStateEvent)e).getState() == IdleState.ALL_IDLE) {
System.out.println("踢玩家下线");
//关闭会话 踢玩家下线
ChannelFuture write=ctx.getChannel().write("time out, you will close");
write.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture channelFuture) throws Exception {
ctx.getChannel().close();
}
});
}
}
else
{
super.handleUpstream(ctx,e);
}
}
}
Netty5实现心跳处理事件实例
实现流程跟Netty3的实现流程类似,首先在pipeline属性中加入心跳检测机制和心跳的业务逻辑处理类,代码如下:
bootstrap.childHandler(new ChannelInitializer<Channel>() {
protected void initChannel(Channel ch) throws Exception{
ch.pipeline().addLast(new IdleStateHandler(5,5,10));
ch.pipeline().addLast("decoder",new StringDecoder());
ch.pipeline().addLast("encoder",new StringEncoder());
ch.pipeline().addLast(new ServerHandler());
}
});
实现心跳业务逻辑处理类,Netty5的继承类和Netty3有所不同,Netty5是继承于SimpleChannelInboundHandler,代码如下:
protected void messageReceived(ChannelHandlerContext ctx,String msg) throws Exception{
System.out.println(msg);
ctx.channel().writeAndFlush("hi");
ctx.writeAndFlush("hi");
}
public void userEventTriggered(final ChannelHandlerContext ctx,Object evt) throws Exception
{
if(evt instanceof IdleStateEvent){
IdleStateEvent event=(IdleStateEvent)evt;
if(event.state()== IdleState.ALL_IDLE){
//清除超时会话
ChannelFuture writeAndFlush=ctx.writeAndFlush("you will close");
writeAndFlush.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture channelFuture) throws Exception {
ctx.channel().close();
}
});
}
}
else
{
super.userEventTriggered(ctx,evt);
}
}