Netty -- 应用场景 -- 心跳检测

1、服务端 -- 添加心跳、业务 Handler

pipeline.addLast(new IdleStateHandler(5,0,0, TimeUnit.SECONDS));
pipeline.addLast("handler", new NettyServerHandler());

2、服务端 -- NettyServerHandler

package com.sample.modules.netty;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;

public class NettyServerHandler extends ChannelInboundHandlerAdapter{

    /**
     * 心跳丢失次数
     */
    private int counter = 0;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //重置心跳丢失次数
        counter = 0;
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state().equals(IdleState.READER_IDLE)){
                counter++;
                System.out.println("丢失了第 " + counter + " 个心跳包");
                if (counter >= 3) {
                    ctx.channel().close().sync();
                    System.out.println("已与"+ctx.channel().remoteAddress()+"断开连接");
                }
            }
        }else {
            super.userEventTriggered(ctx,evt);
        }
    }
}

3、客户端 -- 添加心跳、业务 Handler

pipeline.addLast(new IdleStateHandler(0,2,0, TimeUnit.SECONDS));
pipeline.addLast(new StringEncoder());
pipeline.addLast("handler", new NettyClientHandler(host,port));

4、客户端 -- NettyClientHandler

package com.sample.modules.netty;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.EventLoop;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;

import java.util.concurrent.TimeUnit;

public class NettyClientHandler extends ChannelInboundHandlerAdapter{

    private String host;
    private int port;
    private NettyClient nettyClient;

    public NettyClientHandler(String host, int port) {
        this.host = host;
        this.port = port;
        nettyClient = new NettyClient(host,port);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("client 断开连接");
        final EventLoop eventLoop = ctx.channel().eventLoop();
        eventLoop.schedule(()->{
            nettyClient.start();
        }, 1, TimeUnit.SECONDS);
        ctx.fireChannelInactive();
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state().equals(IdleState.WRITER_IDLE)) {
                System.out.println("client 需要发送心跳包");
                ctx.channel().writeAndFlush("ping");
            }
        }
    }
}

5、源码解析

1、属性说明

readerIdleTimeNanos: 读空闲超时时间设定
writerIdleTimeNanos: 写空闲超时时间设定
allIdleTimeNanos:    所有类型的空闲超时时间设定,包括读空闲和写空闲;

2、initialize 方法

  1. 重写了 handlerAdded、channelRegistered、channelActive 方法,调用 initialize 方法
  2. 在 initialize 方法中初始化 readerIdleTimeout、writerIdleTimeout、allIdleTimeout 定时任务

3、readerIdleTimeoutTask

@Override
protected void run(ChannelHandlerContext ctx) {
    long nextDelay = readerIdleTimeNanos;
    if (!reading) {
        //1.当前时间减去上次读的时间, 也就是多久没有读数据到来
        nextDelay -= ticksInNanos() - lastReadTime;
    }

    //2.如果小于0, 说明心跳超时
    if (nextDelay <= 0) {
        //3.开启下一次定时任务
        readerIdleTimeout = schedule(ctx, this, readerIdleTimeNanos, TimeUnit.NANOSECONDS);

        boolean first = firstReaderIdleEvent;
        firstReaderIdleEvent = false;

        //4.触发 userEventTriggered 方法
        try {
            IdleStateEvent event = newIdleStateEvent(IdleState.READER_IDLE, first);
            channelIdle(ctx, event);
        } catch (Throwable t) {
            ctx.fireExceptionCaught(t);
        }
    } else {
        readerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
    }
}

4、writerIdleTimeoutTask

@Override
protected void run(ChannelHandlerContext ctx) {

    long lastWriteTime = IdleStateHandler.this.lastWriteTime;
    //1.当前时间减去上次写的时间, 记录还有多久没有写数据了
    long nextDelay = writerIdleTimeNanos - (ticksInNanos() - lastWriteTime);
    //2.写超时
    if (nextDelay <= 0) {
        //3.开启下一次定时任务
        writerIdleTimeout = schedule(ctx, this, writerIdleTimeNanos, TimeUnit.NANOSECONDS);

        boolean first = firstWriterIdleEvent;
        firstWriterIdleEvent = false;

        try {
            //4.如果有写数据, 直接返回
            if (hasOutputChanged(ctx, first)) {
                return;
            }

            //5.触发 userEventTriggered 方法
            IdleStateEvent event = newIdleStateEvent(IdleState.WRITER_IDLE, first);
            channelIdle(ctx, event);
        } catch (Throwable t) {
            ctx.fireExceptionCaught(t);
        }
    } else {
        writerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
    }
}

5、channelRead 方法

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    if (readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
        //1.有数据到来, reading=true
        reading = true;
        firstReaderIdleEvent = firstAllIdleEvent = true;
    }
    //2.数据继续传递
    ctx.fireChannelRead(msg);
}

6、channelReadComplete 方法

@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
    //1.记录上次读的时间
    if ((readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) && reading) {
        lastReadTime = ticksInNanos();
        reading = false;
    }
    //2.继续传递
    ctx.fireChannelReadComplete();
}

7、write 方法

@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
    if (writerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
        //1.异步监听写事件, 记录上次写的时间
        ctx.write(msg, promise.unvoid()).addListener(writeListener);
    } else {
        ctx.write(msg, promise);
    }
}

8、destroy 方法

重写了 handlerRemoved、channelInactive 方法,调用了 destroy 方法,调用各个定时任务的取消方法

private void destroy() {
    state = 2;

    if (readerIdleTimeout != null) {
        readerIdleTimeout.cancel(false);
        readerIdleTimeout = null;
    }
    if (writerIdleTimeout != null) {
        writerIdleTimeout.cancel(false);
        writerIdleTimeout = null;
    }
    if (allIdleTimeout != null) {
        allIdleTimeout.cancel(false);
        allIdleTimeout = null;
    }
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值