netty客户端:

public class ClientConnector implements Runnable{
    private Channel channel;
    private static String ip;
    private static int port;

    static {
        ip = BasicAppsConfig.getNettyServerIpPort().split(":")[0];
        port = Integer.parseInt(BasicAppsConfig.getNettyServerIpPort().split(":")[1]);
    }
    @Override
    public void run() {
        connect(ip,port);
    }

    public Channel connect(String host, int port) {
        doConnect(host, port);
        return this.channel;
    }

    private void doConnect(String host, int port) {
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(workerGroup);
            b.channel(NioSocketChannel.class);
            b.option(ChannelOption.SO_KEEPALIVE, true);
            b.handler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                public void initChannel(NioSocketChannel ch) throws Exception {
                    /*// 实体类传输数据,protobuf序列化
                    ch.pipeline().addLast("decoder",
                            new ProtobufDecoder(MessageProto.Message.getDefaultInstance()));
                    ch.pipeline().addLast("encoder",
                            new ProtobufEncoder());*/
                    //发送消息频率。单位秒。此设置
                    //第一个参数 60 表示读操作空闲时间
                    //第二个参数 20 表示写操作空闲时间
                    //第三个参数 6100 表示读写操作空闲时间
                    //第四个参数 单位/秒
                    ch.pipeline().addLast(new IdleStateHandler(70, 60, 100, TimeUnit.SECONDS));

                    ch.pipeline().addLast(new CliensideMessageHandler());
                }
            });

            ChannelFuture f = b.connect(host, port);
            f.addListener(new ConnectionListener());
            channel = f.channel();//此处不需f.channel().closeFuture();否则无法实现nettyServer端断开重连
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public static String getIp() {
        return ip;
    }

    public static void setIp(String ip) {
        ClientConnector.ip = ip;
    }

    public static int getPort() {
        return port;
    }

    public static void setPort(int port) {
        ClientConnector.port = port;
    }
}

 

Handler:

public class CliensideMessageHandler extends ChannelInboundHandlerAdapter {
  private ClientConnector clientConnector = new ClientConnector();

  //nettyClient启动时会执行此类 @Override
public void channelActive(ChannelHandlerContext channelHandlerContext) throws Exception { String pmk = ""; try { pmk = channelHandlerContext.channel().localAddress().toString().split(":")[0]; } catch (Exception e) { pmk="/"+ SysInfoCatcher.getIP(); } ByteBuf buffer = StrConverter.getByteBuf(pmk, channelHandlerContext); System.out.println(new Date() + ": 信息收集代理启动,连接服务器ing --->" + StrConverter.getStr(buffer)); logChannelHandlerContext = channelHandlerContext;
    //此处信息的写入,将在nettyServer端读取写出 channelHandlerContext.channel().writeAndFlush(buffer); }
  //在nettyServer端使用管道写入消息时,将在此处读取写出 @Override
public void channelRead(ChannelHandlerContext channelHandlerContext, Object message) throws Exception { ByteBuf byteBuf = (ByteBuf) message; String content = StrConverter.getStr(byteBuf); System.out.println("\n\n" + this.getClass().getName() + " : from server's data [ " + content + " ]\n"); if (content.startsWith("response")) { System.out.println("\n\n" + new Date() + " from server's heartbeat data---> " + content); } else { JSONObject jsonObject=JSON.parseObject(content); System.out.println("\n\n" + this.getClass().getName() + " : from server's content data < " + content + " >\n"); if(jsonObject.getString("commandTag").equals("start")){ System.out.println(jsonObject.getString("commandContent")); }else if (jsonObject.getString("commandTag").equals("ReturnResult")) {

        System.out.println(jsonObject.getString("commandContent"));
} else { System.out.println(jsonObject.getString("commandContent")); } } byteBuf.release(); } /** * 用来接收心跳检测结果,event.state()的状态分别对应IdleStateHandler(70, 60, 100, TimeUnit.SECONDS)中的 * 三个参数的时间设置,当满足某个时间的条件时会触发事件 */ @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { super.userEventTriggered(ctx, evt); ClientConnector clientConnector = new ClientConnector(); if (evt instanceof IdleStateEvent) { IdleStateEvent event = (IdleStateEvent) evt; if (event.state().equals(IdleState.READER_IDLE)) { System.out.println("长期没收到服务器推送数据"); //重新连接 clientConnector.connect(ClientConnector.getIp(), ClientConnector.getPort()); } else if (event.state().equals(IdleState.WRITER_IDLE)) { System.out.println("长期未向服务器发送数据"); //发送心跳包 ByteBuf byteBufOut = StrConverter.getByteBuf("PING/" + ctx.channel().localAddress().toString(), ctx); ctx.writeAndFlush(byteBufOut); } else if (event.state().equals(IdleState.ALL_IDLE)) { System.out.println("ALL"); } } } /** * channelInactive 被触发一定是和服务器断开了。分两种情况。一种是服务端close,一种是客户端close。 */ @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { String pmk = ""; try { pmk = ctx.channel().localAddress().toString().split(":")[0]; } catch (Exception e) { pmk="/"+ SysInfoCatcher.getIP(); }     //断开连接时,将ChannelMap中存储的管道信息删除 if (ChannelMap.getAllChannels().get(pmk) != null) ChannelMap.removeTimeServerChannel(pmk); System.err.println(new Date()+" : "+this.getClass().getName() + "#channelInactive()\n" + "Heartbeat data sender disconnect from server!!"); // 定时线程 断线重连 final EventLoop eventLoop = ctx.channel().eventLoop(); //设置断开连接后重连时间,此设置是断开连接一分钟(10秒)后重连 eventLoop.schedule(() -> clientConnector.connect(ClientConnector.getIp(), ClientConnector.getPort()), 10, TimeUnit.SECONDS); super.channelInactive(ctx); } /** * 在服务器端不使用心跳检测的情况下,如果客户端突然拔掉网线断网(注意这里不是客户度程序关闭,而仅是异常断网) */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { String pmk = ""; try { pmk = ctx.channel().localAddress().toString().split(":")[0]; } catch (Exception e) { pmk="/"+ SysInfoCatcher.getIP(); }     //断开连接时,将ChannelMap中存储的管道信息删除 if (ChannelMap.getAllChannels().get(pmk) != null) ChannelMap.removeTimeServerChannel(pmk); System.err.println("server " + ctx.channel().remoteAddress() + " 关闭连接, due to network exception !!"); cause.printStackTrace(); ctx.close(); } }

 

检测netty连接状态的监听类:

public class ConnectionListener implements ChannelFutureListener {

    private ClientConnector clientConnector = new ClientConnector();

    @Override
    public void operationComplete(ChannelFuture channelFuture) throws Exception {
        if (!channelFuture.isSuccess()) {
            final EventLoop loop = channelFuture.channel().eventLoop();
            loop.schedule(new Runnable() {
                @Override
                public void run() {
                    System.err.println("服务端连接不上,开始重连操作...");
                    clientConnector.connect(ClientConnector.getIp(), ClientConnector.getPort());
                }
            }, 1L, TimeUnit.SECONDS);
        } else {
            System.err.println("连接到服务端成功...");
        }
    }
}

 

nettyServer端的handler:

public class ServersideMessageHandler extends ChannelInboundHandlerAdapter {

  //在netty客户端发送的消息,将在此方法中进行处理 @Override
public void channelRead(ChannelHandlerContext channelHandlerContext, Object message) throws Exception { String msg = StrConverter.getStr((ByteBuf) message);
    //msg为netty客户端传送过来的信息,其内容是ip信息,用来当作ChannelMap的唯一key
    //判断ChannelMap中是否存在key为msg的管道信息,存在且其对象地址与此处的channelHandlerContext一致,则不操作
    //否则将其添加进ChannelMap
if (ChannelMap.getAllChannels().get(msg) != null && ChannelMap.getAllChannels().get(msg)==(channelHandlerContext)) { //接收到服务端发来的数据进行业务处理 }elseif (msg.startsWith("PING")){
      //心跳信息的反馈 System.out.println(
new Date() + " : 服务端响应心跳数据:response: " +channelHandlerContext.channel().remoteAddress()); ByteBuf byteBufOut = StrConverter.getByteBuf("response: "+new Date(),channelHandlerContext); channelHandlerContext.channel().writeAndFlush(byteBufOut); }else { //接收到服务端发来的数据进行业务处理 //如果map中没有此channelHandlerContext 将连接存入map中 System.out.println("\n\n"+new Date() + ": 服务端获取到channelID -> " + msg+"\n\n"); ChannelMap.removeTimeServerChannel(msg); ChannelMap.addTimeServerChannel(msg,channelHandlerContext); System.out.println("map size : "+ ChannelMap.getAllChannels().size()); } } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } /** * channelInactive 被触发一定是和服务器断开了。分两种情况。一种是服务端close,一种是客户端close。 */ @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { String pmk = ""; try { pmk = ctx.channel().remoteAddress().toString().split(":")[0]; } catch (Exception e) { // pmk="/"+ SysInfoCatcher.getIP(); } if(ChannelMap.getAllChannels().get(pmk) != null) ChannelMap.removeTimeServerChannel(pmk); super.channelInactive(ctx); System.err.println(new Date()+" : 客户端与服务端断开连接,将"+pmk+"从ChannelMap中移除!"); System.out.println("map size : "+ChannelMap.getAllChannels().size()); } }

 

NettyServer类
public class NettyServer {
    private static int port=8899;

    public static void activateServer() {
        System.out.println("Start Netty Server ...");

        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        public void initChannel(NioSocketChannel ch) throws Exception {
                            // 注册handler
                            ch.pipeline().addLast(new ServersideMessageHandler());
                        }
                    }).option(ChannelOption.SO_BACKLOG, 128)/*.childOption(ChannelOption.SO_KEEPALIVE, true)*/;

            ChannelFuture f = b.bind(Integer.parseInt(BasicAppsConfig.getNettyServerIpPort().split(":")[1])).sync();

            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}

管道实体类:

public class ChannelMap {
    private static Map<String, Map> parentChannelMap = new ConcurrentHashMap<>();
    private static Map<String, ChannelHandlerContext> channelMap = new ConcurrentHashMap<>();

    public static void addTimeServerChannel(String id, ChannelHandlerContext sc){
        String key=id.split(":")[0];
        channelMap.put(id, sc);
        parentChannelMap.put(key,channelMap);
        System.out.println("size = "+channelMap.size());
    }

    public static Map<String, ChannelHandlerContext> getAllChannels(){
        return channelMap;
    }

    public static ChannelHandlerContext getTimeServerChannel(String id){
        return channelMap.get(id);
    }

    public static  void removeTimeServerChannel(String id){
        channelMap.remove(id);
    }
}

 

 

工具类:

public class StrConverter {
    public static ByteBuf getByteBuf(String cotent, ChannelHandlerContext channelHandlerContext) {
        byte[] bs = cotent.getBytes(Charset.forName("utf-8"));
        ByteBuf byteBuf = channelHandlerContext.alloc().buffer();
        byteBuf.writeBytes(bs);
        return byteBuf;
    }
    public static String getStr(ByteBuf cotent) {
        return cotent.toString(Charset.forName("utf-8"));
    }
}

 

netty客户端与服务器端通讯时的数据类型要一致,否则其中一端将无法发送或解析信息!

使用ChannelMap存储的管道信息channelHandlerContext可以向管道中写入数据,以达到server端向对应的client端传输数据的目的