Netty检查连接断开的几种方法

最近项目中需要判定客户端是否还在线,需要用到心跳检测机制。这里做个笔记总结一下。

心跳检测机制:

网络中接收和发送数据都是通过操作系统的socket实现的。但是如果套接字已经断开,那发送和接收数据就会出问题。
但如何判断套接字是否断开了呢?这就需要建立一种机制,能够检测通信对方是否还存活。如果已经断开,就要释放资源。这种机制通常采用心跳检测实现。
所谓的“心跳”就是定时发送一个自定义的结构体(心跳包或心跳帧),让对方知道自己“在线”,以确保链接的有效性。心跳检测规定定时发送心跳检测数据包,接收方接心跳包后回复,否则认为连接断开。

一、Netty心跳检测方式

1、pipeline加入IdleStateHandler

Netty提供了心跳检测类IdleStateHandler,它有三个参数,分别是读超时时间、写超时时间、读写超时时间。
1)readerIdleTime:读超时时间;
2)writerIdleTime:写超时时间;
3)allIdleTime:所有类型超时时间;
这里最重要是的readerIdleTime,当设置了readerIdleTime以后,服务端server会每隔readerIdleTime时间去检查一次channelRead方法被调用的情况,如果在readerIdleTime时间内该channel上的channelRead()方法没有被触发,就会调用userEventTriggered方法。

//读超时时间设置为10s,0表示不监控
ch.pipeline().addLast(new IdleStateHandler(10, 0, 0, TimeUnit.SECONDS));
//加入处理事件
ch.pipeline().addLast(new ServerHeartBeat());

2、重写userEventTriggered方法

重写ChannelInboundHandlerAdapter处理类的userEventTriggered方法,在方法中处理idleEvent.state() == IdleState.READER_IDLE情况。

public class ServerHeartBeat extends ChannelInboundHandlerAdapter {

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {//超时事件
            IdleStateEvent idleEvent = (IdleStateEvent) evt;
            if (idleEvent.state() == IdleState.READER_IDLE) {//
                ctx.channel().close();
            } else if (idleEvent.state() == IdleState.WRITER_IDLE) {//

            } else if (idleEvent.state() == IdleState.ALL_IDLE) {//全部

            }
        }
        super.userEventTriggered(ctx, evt);
    }
}

二、TCP连接心跳检测方式

1、TCP心跳机制

上面采用的是Netty的心跳检测机制,属于应用层自定义的心跳检测机制。

应用层实现心跳机制好处:
一是灵活, 应用层心跳可以自由定义,可实现各时间间隔(秒级、毫秒级)的检测,包里还可以携带额外的信息。
二是通用, 应用层的心跳不依赖于底层协议。如果后期想把TCP改为UDP,协议层不提供心跳机制了,但应用层的心跳依旧是通用的。

应用层实现心跳机制坏处:
一是增加开发量, 由于使用特定的网络框架, 还可能很增加代码结构的复杂度。
二是额外增加了网络通信数据包,流量消耗更大。

TCP协议本身也实现了心跳检测机制。我们也可以使用TCP的心跳机制检测连接是否断开。
如果设置了心跳,TCP会在一定时间(比如设置的是3秒钟)内发送设置的次数的心跳(比如说2次),并且该心跳信息不会影响自己定义的协议。

2、TCP心跳设置

TCP协议自带的保活功能,使用起来很简单。Linux环境下,修改/etc/sysctl.conf文件
添加以下内容:

#表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是7200秒(2小时),改为5秒钟。
net.ipv4.tcp_keepalive_time = 5

#如果对方不予应答,探测包的发送次数
net.ipv4.tcp_keepalive_probes = 5

#探测消息发送的频率
net.ipv4.tcp_keepalive_intvl = 1

这个是修改的linux tcp发送探测包配置的。修改完成后输入以下命令生效

/sbin/sysctl -p
/sbin/sysctl -w net.ipv4.route.flush=1

还可以使用echo的方式修改,命令如下:

#echo 5 > /proc/sys/net/ipv4/tcp_keepalive_time
#echo 5 > /proc/sys/net/ipv4/tcp_keepalive_probes
#echo 1 > /proc/sys/net/ipv4/tcp_keepalive_intvl 

修改后查看下参数,验证设置是否已经生效。

#cat /proc/sys/net/ipv4/tcp_keepalive_time  
#cat /proc/sys/net/ipv4/tcp_keepalive_probes  
#cat /proc/sys/net/ipv4/tcp_keepalive_intvl 

这样设置完成以后,每次客户端断开连接后5秒后会执行以下方法。

3、使用Netty,重写handlerRemoved方法

public class TcpServerHandler extends SimpleChannelInboundHandler<ByteBuf>{

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throw Exception{

        //执行客户端断开连接后的业务操作
    }
}

这里的时间设置不一定适合各业务场景,需要根据具体的业务设置合适的时间间隔。有时候网络不稳定,一个探测包没有探测到,系统就自动断开连接了。

三、使用redis保存连接

服务端收到客户端发送的心跳数据包后,使用redis保存,同时设置超时时间,一旦超时,触发超时事件,表示客户端连接出问题了。

比如:心跳时间为60s,那么服务器端收到客户端心跳数据包后,保存到redis,并设置超时事件为70s(根据实际情况,需要大于心跳时间),一旦超时触发超时事件,进行连接断开逻辑处理。

 

posted @ 2019-10-24 23:21  Alan6  阅读(22754)  评论(0编辑  收藏  举报