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端传输数据的目的