java - (二)netty 心跳监测机制

1.心跳检测机制

1.1 定时断线重连

客户端断线重连机制

客户端数量多,且需要传递的数据量级较大。可以在周期性的发送数据的时候,使用。要求对数据的即时性不高的时候,才可使用。

优点:可以使用数据缓存。不是每条数据进行一次数据交互。可以定时回收资源,对资源利用率高。相对来说,即时性可以通过其他方式保证。如: 120秒自动断线。数据变化1000次请求服务器一次。300秒中自动发送不足1000次的变化数据。

对于长连接的程序断网重连几乎是程序的标配。断网重连具体可以分为两类:

CONNECT失败,需要重连

实现ChannelFutureListener 用来启动时监测是否连接成功,不成功的话重试

// 实现ChannelFutureListener 用来启动时监测是否连接成功,不成功的话重试


//1.lambda 方式
private void doConnect() {
    
    
    Bootstrap b = ...;
    b.connect().addListener((ChannelFuture f) -> {
    
    
        if (!f.isSuccess()) {
    
    
            long nextRetryDelay = nextRetryDelay(...); // 延时多少重新发送连接请求
            f.channel().eventLoop().schedule(nextRetryDelay, ..., () -> {
    
    
                doConnect();
            });
        }
    });
}


// 2.自定义类的方式
public class ConnectionListener implements ChannelFutureListener {
    
    
  private Client client;
  public ConnectionListener(Client client) {
    
    
    this.client = client;
  }
  @Override
  public void operationComplete(ChannelFuture channelFuture) throws Exception {
    
    
    if (!channelFuture.isSuccess()) {
    
    
      System.out.println("Reconnect");
      //因为是建立网络连接所以可以共用EventLoop
      final EventLoop loop = channelFuture.channel().eventLoop();
      loop.schedule(new Runnable() {
    
    
        @Override
        public void run() {
    
    
          client.createBootstrap(new Bootstrap(), loop);
        }
      }, 1L, TimeUnit.SECONDS);
    }
  }
}

程序运行过程中断网、远程强制关闭连接、收到错误包必须重连;

public class Client{
    
    

	public Bootstrap createBootstrap(Bootstrap bootstrap, EventLoopGroup eventLoop) {
    
    
     	if (bootstrap != null) {
    
    
       		final MyInboundHandler handler = new MyInboundHandler(this);
       		bootstrap.group(eventLoop);
       		bootstrap.channel(NioSocketChannel.class);
       		bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
       		bootstrap.handler(new ChannelInitializer<SocketChannel>() {
    
    
         		@Override
         		protected void initChannel(SocketChannel socketChannel) throws Exception {
    
    
           			socketChannel.pipeline().addLast(handler);
         		}
       		});
      		 bootstrap.remoteAddress("localhost", 8888);
      		 bootstrap.connect().addListener(new ConnectionListener(this));
     	}
     	return bootstrap;
   }
}

public class MyInboundHandler extends SimpleChannelInboundHandler {
    
    
   private Client client;
   public MyInboundHandler(Client client) {
    
    
     this.client = client;
   }
   // 和服务器断开连接时触发
   @Override
   public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    
    
     final EventLoop eventLoop = ctx.channel().eventLoop();
     eventLoop.schedule(new Runnable() {
    
    
       @Override
       public void run() {
    
    
         client.createBootstrap(new Bootstrap(), eventLoop);
       }
     }, 1L, TimeUnit.SECONDS);
     super.channelInactive(ctx);
   }
 }

1.2 心跳监测

使用定时发送消息的方式,实现硬件检测,达到心态检测的目的。

心跳监测是用于检测电脑硬件和软件信息的一种技术。如:CPU使用率,磁盘使用率,内存使用率,进程情况,线程情况等。

Netty提供的心跳检测机制分为三种:

  • 读空闲,链路持续时间t没有读取到任何消息;
  • 写空闲,链路持续时间t没有发送任何消息;
  • 读写空闲,链路持续时间t没有接收或者发送任何消息;

心跳检测机制分为三个层面:

  • TCP层面的心跳检测,即TCP的Keep-Alive机制,它的作用域是整个TCP协议栈;
  • 协议层的心跳检测,主要存在于长连接协议中。例如SMPP协议;
  • 应用层的心跳检测,它主要由各业务产品通过约定方式定时给对方发送心跳消息实现。
public class MyServer {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //创建两个线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        //8个NioEventLoop
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
    
    

            ServerBootstrap serverBootstrap = new ServerBootstrap();

            serverBootstrap.group(bossGroup, workerGroup);
            serverBootstrap.channel(NioServerSocketChannel.class);
            serverBootstrap.handler(new LoggingHandler(LogLevel.INFO));
            serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
    
    

                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
    
    
                    ChannelPipeline pipeline = ch.pipeline();
                    //加入一个netty 提供 IdleStateHandler
//                    说明
//                    1. IdleStateHandler 是netty 提供的处理空闲状态的处理器
//                    2. long readerIdleTime : 表示多长时间没有读, 就会发送一个心跳检测包检测是否连接
//                    3. long writerIdleTime : 表示多长时间没有写, 就会发送一个心跳检测包检测是否连接
//                    4. long allIdleTime : 表示多长时间没有读写, 就会发送一个心跳检测包检测是否连接
//
//                    5. 文档说明
//                    triggers an {@link IdleStateEvent} when a {@link Channel} has not performed
// * read, write, or both operation for a while.
// *                  6. 当 IdleStateEvent 触发后 , 就会传递给管道 的下一个handler去处理
// *                  通过调用(触发)下一个handler 的 userEventTiggered , 在该方法中去处理 IdleStateEvent(读空闲,写空闲,读写空闲)
                    pipeline.addLast(new IdleStateHandler(7000, 7000, 10, TimeUnit.SECONDS));
                    //加入一个对空闲检测进一步处理的handler(自定义)
                    pipeline.addLast(new MyServerHandler());
                }
            });

            //启动服务器
            ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
            channelFuture.channel().closeFuture().sync();

        } finally {
    
    
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
public class MyServerHandler extends ChannelInboundHandlerAdapter {
    
    

    /**
     * @param ctx 上下文
     * @param evt 事件
     * @throws Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    
    

        if (evt instanceof IdleStateEvent) {
    
    

            //将  evt 向下转型 IdleStateEvent
            IdleStateEvent event = (IdleStateEvent) evt;
            String eventType = null;
            switch (event.state()) {
    
    
                case READER_IDLE:
                    eventType = "读空闲";
                    break;
                case WRITER_IDLE:
                    eventType = "写空闲";
                    break;
                case ALL_IDLE:
                    eventType = "读写空闲";
                    break;
                default:
                    throw new IllegalStateException("Unexpected value: " + event.state());
            }
            System.out.println(ctx.channel().remoteAddress() + "--超时时间--" + eventType);
            System.out.println("服务器做相应处理..");

            //如果发生空闲,我们关闭通道
            // ctx.channel().close();
        }
    }
}

2.Netty 通过WebSocket 编程实现服务器和客户端长连接

2.1 要求

要求:

  1. 实现计语 webSocket 的长连接的全双工的交互
  2. 改变 Http 协议多次请求的约束,实现长连接,服务器可以主动发送消息给浏览器
  3. 客户端浏览器和服务端会相互感知,比如服务器关闭了,浏览器会感知,同样浏览器关闭了,服务器会感知

2.2 代码

public class MyServer {
    
    
    public static void main(String[] args) throws Exception {
    
    

        //创建两个线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        //8个NioEventLoop
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
    
    

            ServerBootstrap serverBootstrap = new ServerBootstrap();

            serverBootstrap.group(bossGroup, workerGroup);
            serverBootstrap.channel(NioServerSocketChannel.class);
            serverBootstrap.handler(new LoggingHandler(LogLevel.INFO));
            serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
    
    

                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
    
    
                    ChannelPipeline pipeline = ch.pipeline();

                    //因为基于http协议,使用http的编码和解码器
                    pipeline.addLast(new HttpServerCodec());
                    //是以块方式写,添加ChunkedWriteHandler处理器
                    pipeline.addLast(new ChunkedWriteHandler());

                    /*
                    说明
                    1. http数据在传输过程中是分段, HttpObjectAggregator ,就是可以将多个段聚合
                    2. 这就是为什么,当浏览器发送大量数据时,就会发出多次http请求
                     */
                    pipeline.addLast(new HttpObjectAggregator(8192));
                    /*
                    说明
                    1. 对应websocket ,它的数据是以 帧(frame) 形式传递
                    2. 可以看到WebSocketFrame 下面有六个子类
                    3. 浏览器请求时 ws://localhost:7000/hello 表示请求的uri
                    4. WebSocketServerProtocolHandler 核心功能是将 http协议升级为 ws协议 , 保持长连接
                    5. 是通过一个 状态码 101
                     */
                    pipeline.addLast(new WebSocketServerProtocolHandler("/hello2"));

                    //自定义的handler ,处理业务逻辑
                    pipeline.addLast(new MyTextWebSocketFrameHandler());
                }
            });

            //启动服务器
            ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
            channelFuture.channel().closeFuture().sync();

        } finally {
    
    
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
/**
 * 这里 TextWebSocketFrame 类型,表示一个文本帧(frame)
 *
 * @author Administrator
 */
public class MyTextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    
    

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
    
    
        System.out.println("服务器收到消息 " + msg.text());
        //回复消息
        ctx.channel().writeAndFlush(new TextWebSocketFrame("服务器时间" + LocalDateTime.now() + " " + msg.text()));
    }

    /**
     * 当web客户端连接后, 触发方法
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    
    
        //id 表示唯一的值,LongText 是唯一的 ShortText 不是唯一
        System.out.println("handlerAdded 被调用" + ctx.channel().id().asLongText());
        System.out.println("handlerAdded 被调用" + ctx.channel().id().asShortText());
    }


    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
    
    
        System.out.println("handlerRemoved 被调用" + ctx.channel().id().asLongText());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    
    
        System.out.println("异常发生 " + cause.getMessage());
        //关闭连接
        ctx.close();
    }
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
    var socket;
    //判断当前浏览器是否支持websocket
    if(window.WebSocket) {
     
     
        //go on
        socket = new WebSocket("ws://localhost:7000/hello2");
        //相当于channelReado, ev 收到服务器端回送的消息
        socket.onmessage = function (ev) {
     
     
            var rt = document.getElementById("responseText");
            rt.value = rt.value + "\n" + ev.data;
        }

        //相当于连接开启(感知到连接开启)
        socket.onopen = function (ev) {
     
     
            var rt = document.getElementById("responseText");
            rt.value = "连接开启了.."
        }

        //相当于连接关闭(感知到连接关闭)
        socket.onclose = function (ev) {
     
     

            var rt = document.getElementById("responseText");
            rt.value = rt.value + "\n" + "连接关闭了.."
        }
    } else {
     
     
        alert("当前浏览器不支持websocket")
    }

    //发送消息到服务器
    function send(message) {
     
     
        if(!window.socket) {
     
      //先判断socket是否创建好
            return;
        }
        if(socket.readyState == WebSocket.OPEN) {
     
     
            //通过socket 发送消息
            socket.send(message)
        } else {
     
     
            alert("连接没有开启");
        }
    }
</script>
    <form onsubmit="return false">
        <textarea name="message" style="height: 300px; width: 300px"></textarea>
        <input type="button" value="发生消息" onclick="send(this.form.message.value)">
        <textarea id="responseText" style="height: 300px; width: 300px"></textarea>
        <input type="button" value="清空内容" onclick="document.getElementById('responseText').value=''">
    </form>
</body>
</html>

猜你喜欢

转载自blog.csdn.net/saienenen/article/details/111598558