netty 心跳机制

服务端

public class Netty5Server {
    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try{
            // 首先,netty通过ServerBootstrap启动服务端
            ServerBootstrap server = new ServerBootstrap();
            /**
             * 第1步定义两个线程组,用来处理客户端通道的accept和读写事件
             * bossGroup用来处理accept事件,workergroup用来处理通道的读写事件
             * bossGroup获取客户端连接,连接接收到之后再将连接转发给workergroup去处理
             */
            server.group(bossGroup, workerGroup)
                    //第2步 构造Socketchannel的工厂类,绑定服务端通道
                    .channel(NioServerSocketChannel.class)
                    //用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。
                    //用来初始化服务端可连接队列
                    //服务端处理客户端连接请求是按顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定了队列的大小。
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    //第3步绑定handler,处理读写事件,ChannelInitializer是给通道初始化
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //空闲处理程序
                            ch.pipeline().addLast(new IdleStateHandler(3,0,0, TimeUnit.SECONDS));
                            ch.pipeline().addLast("decoder",new StringDecoder());
                            ch.pipeline().addLast("encoder",new StringEncoder());
                            //处理服务端的业务逻辑:心跳超时处理、客服端返回的数据处理
                            ch.pipeline().addLast(new Test5ServerHandler());
                        }
                    });
            //第4步绑定8080端口
            ChannelFuture channelFuture = server.bind(8005).sync();
            System.out.println("server start.");
            //当通道关闭了,就继续往下走
            channelFuture.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
public class Test5ServerHandler extends ChannelInboundHandlerAdapter {
    private int idle_count = 1;//空闲次数
    private int count = 1;//发送次数

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception{ //建立连接时
        System.out.println("[Server]获得连接:"+ctx.channel().remoteAddress() + date());
    }
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("[Server] the "+count+" times, recive msg: " + msg);
        String message = (String)msg;
        System.out.println("[Server] response msg: "+message);
        // 如果是心跳命令,服务端收到命令后回复一个相同的命令给客户端
        if("hb_request".equals(message)){
            ctx.channel().writeAndFlush("server is active.");
        }else{
            ctx.channel().writeAndFlush("解析数据");
        }
        count++;
    }
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { //用户超时事件触发
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent)evt;
        String eventType = null;
        switch (event.state()){
            case READER_IDLE: // 如果读通道处于空闲状态,说明没有接收到心跳命令
                eventType = "读空闲";
                idle_count ++;
                break;
            case WRITER_IDLE:
                eventType = "写空闲";
                break;//不处理
            case ALL_IDLE:
                eventType = "读写空闲";//不处理
                break;
        }
        System.out.println(ctx.channel().remoteAddress() + "userEventTriggered()读到超时事件:"+event.state()+" "+eventType);
        if(idle_count > 5){
            System.out.println("[Server]超过3次无客户端请求(读空闲超过3次),关闭该channel");
            ctx.channel().writeAndFlush("you are out");
            //ctx.channel().close();
        }else
            super.userEventTriggered(ctx, evt); //ctx.fireUserEventTriggered(evt);
        }
    }

    private String date(){
        SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-DD HH:mm:ss");
        return "  "+ sdf.format(new Date());
    }
}

客户端、

public class Netty5Client {
    public static void main(String[] args) throws Exception {
            // 首先,netty通过Bootstrap启动客户端
        Bootstrap bootstrap = new Bootstrap();
        // 第1步 定义线程组,处理读写和链接事件,没有了accept事件
        EventLoopGroup worker = new NioEventLoopGroup();
        bootstrap.group(worker)
                // 第2步 构造Socketchannel的工厂类,绑定客户端通道
                .channel(NioSocketChannel.class)
                // 第3步 给NioSocketChannel初始化handler, 处理读写事件
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        //空闲处理程序
                        ch.pipeline().addLast(new IdleStateHandler(0,2,0, TimeUnit.SECONDS));
                        // 解码和编码,应和服务端一致
                        ch.pipeline().addLast("decoder", new StringDecoder());
                        ch.pipeline().addLast("encoder", new StringEncoder());
                        //客户端业务处理,如编解码和心跳的设置
                        ch.pipeline().addLast(new Test5ClientHandler());
                }
            });
        // 连接服务端
            Channel channel = bootstrap.connect("127.0.0.1",8005).sync().channel();//connect server
            String text = "CLIENT xxx START ONLINE.";
            channel.writeAndFlush(text);//给服务端发送数据
            System.out.println("[Client] start send msg to server: " + text);
            channel.closeFuture().sync();
    }
}

客户端业务处理类

public class Test5ClientHandler extends ChannelInboundHandlerAdapter {
    /** 客户端请求的心跳命令 */
    private static final ByteBuf HEARTBEAT_SEQUENCE = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("hb_request", CharsetUtil.UTF_8));
    private int idle_count = 1;//idle count
    private int count = 1;//send count
    private int cycleTimes = 1;//循环次数

    //超时处理方法
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object obj){
        if(obj instanceof IdleStateEvent){
            IdleStateEvent event = (IdleStateEvent)obj;
            if(IdleState.WRITER_IDLE.equals(event.state())){ //如果写通道处于空闲就发送心跳命令
                //设置发送次数,允许发送3次心跳包
                if(idle_count <= 10){
                    idle_count++;
                    ctx.channel().writeAndFlush(HEARTBEAT_SEQUENCE.duplicate());
                }else{
                    System.out.println("停止发送心跳请求...");
                }
            }
        }
        cycleTimes++;
    }
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception{
        System.out.println("[Client]conn req :"+ date());
        ctx.channel().writeAndFlush("this client activing...");
        ctx.fireChannelActive();
    }
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("[Client]conn sucess :"+count+",Read msg : "+ msg);//业务逻辑处理
        count++;
    }
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception{
        System.out.println("[Client]conn close :"+date());
    }
    private String date(){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(new Date());
    }
}

参考资料

Netty中ctx.writeAndFlush与ctx.channel().writeAndFlush的区别
https://blog.csdn.net/FishSeeker/article/details/78447684
https://blog.csdn.net/chehec2010/article/details/90643436

网络编程Netty IoT长连接优化 https://blog.csdn.net/qq_34365173/article/details/106311934

断线重连机制实现 https://blog.csdn.net/li_c_yang/article/details/104841022

Netty检查连接断开的几种方法 https://www.cnblogs.com/alan6/p/11715722.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值