首先说一下业务场景:

不同于netty常用的im,我这里只是单纯的实现服务端与客户端做一个心跳检测,查看客户端是否在线即可。因为是领导指定用netty,所以简单的看了下demo,又因为业务需求的简单,所以也只是浅显的了解了一下,还有一点:正常来讲客户端和服务端监听都可以。但是我们这是领导觉得少占用服务端资源,所以选择了客户端监听。

1.导包。(虽然我没用过,但是网上很多人都说了netty的向下兼容问题。所以有遇到的可以考虑一下这个问题)



<dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>5.0.0.Alpha2</version
        </dependency>



2.代码实现。其实这个涉及到很深奥的东西,比如什么编码解码之类的!但是因为我要做的比较简单,所以也没太仔细看这方面的东西。然后再重申!我这里只要实现简单的心跳监控就ok了!



package com.dsyl.done.netty;

import java.io.IOException;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.EventLoop;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.ReferenceCountUtil;

public class HeartBeatClientHandler extends SimpleChannelInboundHandler<String> {

	  private ClientStarter clientStarter;

	  public HeartBeatClientHandler(ClientStarter clientStarter) {
	    this.clientStarter = clientStarter;
	  }

	  /**
	   * 客户端监听写事件。也就是设置时间内没有与服务端交互则发送ping 给服务端
	   */
	  @Override
	  public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
	    if(evt instanceof IdleStateEvent) {
	      IdleState state = ((IdleStateEvent)evt).state();
	      if(state == IdleState.ALL_IDLE) {
	        ctx.writeAndFlush("PING");
	        System.out.println("send PING");
	      }
	    }
	    super.userEventTriggered(ctx, evt);
	  }
	  /**
	   * channelInactive 被触发一定是和服务器断开了。分两种情况。一种是服务端close,一种是客户端close。
	   */
	  @Override
	  public void channelInactive(ChannelHandlerContext ctx) throws Exception {
	    super.channelInactive(ctx);
	    System.err.println("客户端与服务端断开连接,断开的时间为:"+(new Date()).toString());
	    // 定时线程 断线重连
	    final EventLoop eventLoop = ctx.channel().eventLoop();
	    //设置断开连接后重连时间,此设置是断开连接一分钟(60秒)后重连
	    eventLoop.schedule(() -> clientStarter.connect(), 60, TimeUnit.SECONDS);
	  }

	  /**
	   * 在服务器端不使用心跳检测的情况下,如果客户端突然拔掉网线断网(注意这里不是客户度程序关闭,而仅是异常断网)
	   */
	  @Override
	  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
	    if(cause instanceof IOException) {
	      System.out.println("server "+ctx.channel().remoteAddress()+"关闭连接");
	    }
	  }

	/**
	 * 消息监控,监听服务端传来的消息(和netty版本有关,有的版本这个方法叫做clientRead0)
	 */
	  @Override
	protected void messageReceived(ChannelHandlerContext ctx, String msg) throws Exception {
	    if (msg.equals("PONG")) {
		      System.out.println("receive form server PONG");
		    }
		    ReferenceCountUtil.release(msg);
		
	}
	}



  以上代码大部分出自于技术贴,但是很多注释都是我一点点整理的。可能措辞不准确但是挺便于理解的。我当时就是一个个方法查找含义的。然后也没啥业务逻辑



package com.dsyl.done.netty;

import java.net.InetSocketAddress;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;

public class ClientStarter {

    private Bootstrap bootstrap;
    private int times = 0;

    public ClientStarter(Bootstrap bootstrap) {
        this.bootstrap = bootstrap;
        ClientStarter clientStarter = this;
        bootstrap.group(new NioEventLoopGroup())
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new StringEncoder());
                        ch.pipeline().addLast(new StringDecoder());
                        //发送消息频率。单位秒。此设置是60秒发送一次消息
                        ch.pipeline().addLast(new IdleStateHandler(60, 60, 60));
                        ch.pipeline().addLast(new HeartBeatClientHandler(clientStarter));
                    }
                });
    }

    public void connect() {
        ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("ip", 端口));
        channelFuture.addListener(future ->{
                if (future.isSuccess()) {
                    System.out.println("connect to server success");
                } else {
                    System.out.println("connect to server failed,try times:" + ++times);
                    connect();
                }
        });
    }
}



  这个是客户端实现的代码(ip和端口自己按照情况填写)。测试 的时候以下两行代码:

ClientStarter starter = new ClientStarter(new Bootstrap());
starter.connect();

使用的时候服务端还需要一些额外的设置,不然可能发生下图这种情况:

java业务层面做心跳保活机制 java心跳监控_netty

 

 连接成功后服务端就把客户端踹下线了。然后重复这个过程。这个是服务端设置的问题。