1、Netty数据通信的场景
1.1在实际场景中,我们使用Netty进行通信大致有以下3种方式:
第一种,使用长连接通道不断开的形式进行通信,也就是服务器和客户端的通道一直处于开启的状态。如果服务器性能足够好,并且我们的客户端数量也比较少的情况下,是适合使用长连接的通道。
第二种,采用短连接方式,一次性批量提交数据,也就是我们会把数据保存在本地临时缓冲区或者临时表里。当达到数量时,就进行批量提交;或者通过定时任务轮询提交。这种情况是有弊端的,就是无法做到实时传输。如果应用程序对实时性要求不高,可以考虑使用。
第三种,采用一种特殊的长连接。在指定的某一时间之内,服务器与某台客户端没有任何通信,则断开连接,如果断开连接后,客户端又需要向服务器发送请求,那么再次建立连接。但是这种模式我们要考虑2个因素:
1、如何在超时(即服务器和客户端没有任何通信)后关闭通道?关闭通道后我们又如何再次建立连接?
在客户端跟服务端都使用了Netty提供的超时方法ReadTimeoutHandler(),参数是以秒来算的。关闭通道后我们重新启动一个线程调用getChannelFuture()方法。重新连接。
2、客户端宕机时,我们无需考虑,下次客户端重启之后我们就可以与服务器建立连接,但是服务器宕机时,我们的客户端如何与服务器进行连接呢?
首先我们肯定的是在liunx环境下做部署的,我们可以在liunx下编写启动服务端的脚本(.sh),要是服务端真出问题只需要启动脚本就可以启动服务器了。
1.2 代码实现(以第三种为例)
类库:jboss-marshalling-1.3.0.CR9.jar jboss-marshalling-serial-1.3.0.CR9.jar netty-all-5.0.0.Alpha2-sources.jar netty-all-5.0.0.Alpha2.jar下载地址:
https://www.jboss.org/jbossmarshalling/downloads 我所用的jar 包为 https://pan.baidu.com/s/1tm2EgYtDpTS5dejVnHhvPw 密码:vc8b
代码实现
客户端实体类
package com.xyq.netty.runtime; import java.io.Serializable; public class Request implements Serializable{ private static final long serialVersionUID = 1L; private int id; private String name; private String requestMessage; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getRequestMessage() { return requestMessage; } public void setRequestMessage(String requestMessage) { this.requestMessage = requestMessage; } }
服务器端实体类
package com.xyq.netty.runtime; import java.io.Serializable; public class Response implements Serializable{ private static final long serialVersionUID = 1L; private int id; private String name; private String responseMessage; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getResponseMessage() { return responseMessage; } public void setResponseMessage(String responseMessage) { this.responseMessage = responseMessage; } }
JBoss marshalling编解码工具类
package com.xyq.netty.runtime; import io.netty.handler.codec.marshalling.DefaultMarshallerProvider; import io.netty.handler.codec.marshalling.DefaultUnmarshallerProvider; import io.netty.handler.codec.marshalling.MarshallerProvider; import io.netty.handler.codec.marshalling.MarshallingDecoder; import io.netty.handler.codec.marshalling.MarshallingEncoder; import io.netty.handler.codec.marshalling.UnmarshallerProvider; import org.jboss.marshalling.MarshallerFactory; import org.jboss.marshalling.Marshalling; import org.jboss.marshalling.MarshallingConfiguration; public class MarshallingCodeCFactory { public static MarshallingDecoder buliteMarshallingDecoder(){ //1、首先通过编组工具类的精通方法获取编组实例对象参数序列标识创建的是java序列化工厂对象。 final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial"); //2、创建了MarshallingConfiguration对象,配置了版本号为5 final MarshallingConfiguration configuration = new MarshallingConfiguration(); configuration.setVersion(5); //3、根据marshallerFactory和配置创建提供商 UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, configuration); //4、构建Netty的MarshallingDecoder对象,两个参数分别为提供商和单个消息序列化后的最大长度 MarshallingDecoder decoder = new MarshallingDecoder(provider, 1024*1024*1); return decoder; } public static MarshallingEncoder buliteMarshallingEncoder(){ final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial"); final MarshallingConfiguration configuration = new MarshallingConfiguration(); configuration.setVersion(5); MarshallerProvider provider = new DefaultMarshallerProvider(marshallerFactory, configuration); //5、构建Netty的MarshallingEncoder对象,MarshallingEncoder用于实现序列化接口的POJO对象序列化为二进制数组 MarshallingEncoder encoder = new MarshallingEncoder(provider); return encoder; } }
服务器端
package com.xyq.netty.runtime; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.timeout.ReadTimeoutHandler; public class Server { public static void main(String[] args) throws Exception{ //1、定义两个线程组 EventLoopGroup pGroup = new NioEventLoopGroup();//一个是用于处理服务器端接收客户端连接的 EventLoopGroup cGroup = new NioEventLoopGroup();//一个是进行网络通信的(网络读写的) try { //2、创建辅助工具类,用于服务器通道的一系列配置 ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(pGroup, cGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 1024) .option(ChannelOption.SO_SNDBUF, 32*1024) .option(ChannelOption.SO_RCVBUF, 32*1024) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { //设置marshalling编码 ch.pipeline().addLast(MarshallingCodeCFactory.buliteMarshallingDecoder()); //设置marshalling解码 ch.pipeline().addLast(MarshallingCodeCFactory.buliteMarshallingEncoder()); //超时handler(当服务器端与客户端在指定时间以上没有任何进行通信,则会关闭响应的通道,主要为减小服务端资源占用) ch.pipeline().addLast(new ReadTimeoutHandler(5)); //处理业务 ch.pipeline().addLast(new ServerHandler()); } }); //4、进行绑定 ChannelFuture cf = bootstrap.bind(8765).sync(); System.out.println("server start ..."); //5、等待关闭 cf.channel().closeFuture().sync(); } finally{ pGroup.shutdownGracefully(); cGroup.shutdownGracefully(); } } }
服务器端处理类
package com.xyq.netty.runtime; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; public class ServerHandler extends ChannelHandlerAdapter{ @Override public void channelRead(ChannelHandlerContext ctx, Object msg)throws Exception { //读取客户端发过来的信息 Request request = (Request) msg; System.out.println("服务器接收到客户端的信息为 " + request.getId() + " " + request.getName()); //给客户端回应信息 Response response = new Response(); response.setId(request.getId()); response.setName(request.getName()+"res"); ctx.writeAndFlush(response); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)throws Exception { ctx.close(); } }
客户端
package com.xyq.netty.runtime; import java.util.concurrent.TimeUnit; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.timeout.ReadTimeoutHandler; public class Client { //为了保证线安全采用了单例模式,构造静态内部类实现单例模式 private static class SingletonHolder{ static final Client instance = new Client(); } public static Client getInstance(){ return SingletonHolder.instance; } private EventLoopGroup group; private Bootstrap bootstrap; private ChannelFuture cf; private Client(){ //创建线程组 group = new NioEventLoopGroup(); //创建工具类 bootstrap = new Bootstrap(); bootstrap.group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { //设置marshalling解码 ch.pipeline().addLast(MarshallingCodeCFactory.buliteMarshallingDecoder()); //设置marshalling编码 ch.pipeline().addLast(MarshallingCodeCFactory.buliteMarshallingEncoder()); //超时handler(当服务器端与客户端在指定时间以上没有任何进行通信,则会关闭响应的通道,主要为减小服务端资源占用) ch.pipeline().addLast(new ReadTimeoutHandler(5)); ch.pipeline().addLast(new ClientHandler()); } }); } /** * 建立连接的方法 */ public void connet(){ try { this.cf = bootstrap.connect("127.0.0.1", 8765).sync(); System.out.println("Client connet ...."); } catch (InterruptedException e) { e.printStackTrace(); } } public ChannelFuture getChannelFuture(){ //如果没有连接先链接 if(this.cf == null ){ this.connet(); } //this.cf.channel().isActive() 这里得到的是链接状态 在重新连接时候使用的 返回true/false if(!this.cf.channel().isActive()){ this.connet(); } return this.cf; } public static void main(String[] args) throws Exception{ final Client client = Client.getInstance(); ChannelFuture cf = client.getChannelFuture(); //发送信息 for(int i=0; i<3; i++){ Request request = new Request(); request.setId(i); request.setName("request" + i); cf.channel().writeAndFlush(request); TimeUnit.SECONDS.sleep(4); } //等待关闭 cf.channel().closeFuture().sync(); //重新连接 new Thread(new Runnable() { @Override public void run() { try { System.out.println("进入子线程..."); //重新调用连接 ChannelFuture cf = client.getChannelFuture(); System.out.println(cf.channel().isActive()); System.out.println(cf.channel().isOpen()); //再次发送数据 Request request = new Request(); request.setId(4); request.setName("request" + 4); cf.channel().writeAndFlush(request); cf.channel().closeFuture().sync(); System.out.println("子线程结束......"); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); System.out.println("断开连接主线程结束..."); } }
客户端处理类
package com.xyq.netty.runtime; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import io.netty.util.ReferenceCountUtil; public class ClientHandler extends ChannelHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg)throws Exception { try { //接收服务端的响应信息 Response response = (Response) msg; System.out.println("客户端接收到服务端的响应消息为 " + response.getId() +" " + response.getName()); } finally{ ReferenceCountUtil.release(msg); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)throws Exception { ctx.close(); } }
2、Netty心跳检测
2.1 心跳检测
一般来讲我们去维护服务器集群,肯定要有一台或(几台)服务器主机(Master),然后还应该有N台从服务器(Slave),那么我们的主机肯定要时时刻刻知道自己下面的从服务器的各方面情况。然后进行实时监控的功能。这个在分布式架构里叫做心跳检测或者说心跳监控。
2.2 代码实现
netty-all-5.0.0.Alpha2.jar sigar.jar
下载地值 和上边的一样
请求信息实体类
package com.xyq.netty.heartBeat; import java.io.Serializable; import java.util.HashMap; public class RequestInfo implements Serializable{ private static final long serialVersionUID = 1L; private String ip; private HashMap<String, Object> cpuPercMap; private HashMap<String, Object> memoryMap; public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public HashMap<String, Object> getCpuPercMap() { return cpuPercMap; } public void setCpuPercMap(HashMap<String, Object> cpuPercMap) { this.cpuPercMap = cpuPercMap; } public HashMap<String, Object> getMemoryMap() { return memoryMap; } public void setMemoryMap(HashMap<String, Object> memoryMap) { this.memoryMap = memoryMap; } }
Jboss Marshalling 编解码工具类
package com.xyq.netty.heartBeat; import io.netty.handler.codec.marshalling.DefaultMarshallerProvider; import io.netty.handler.codec.marshalling.DefaultUnmarshallerProvider; import io.netty.handler.codec.marshalling.MarshallerProvider; import io.netty.handler.codec.marshalling.MarshallingDecoder; import io.netty.handler.codec.marshalling.MarshallingEncoder; import io.netty.handler.codec.marshalling.UnmarshallerProvider; import org.jboss.marshalling.MarshallerFactory; import org.jboss.marshalling.Marshalling; import org.jboss.marshalling.MarshallingConfiguration; public class MarshallingCodeCFactory { public static MarshallingDecoder buliteMarshallingDecoder(){ //1、首先通过编组工具类的精通方法获取编组实例对象参数序列标识创建的是java序列化工厂对象。 final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial"); //2、创建了MarshallingConfiguration对象,配置了版本号为5 final MarshallingConfiguration configuration = new MarshallingConfiguration(); configuration.setVersion(5); //3、根据marshallerFactory和配置创建提供商 UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, configuration); //4、构建Netty的MarshallingDecoder对象,两个参数分别为提供商和单个消息序列化后的最大长度 MarshallingDecoder decoder = new MarshallingDecoder(provider, 1024*1024*1); return decoder; } public static MarshallingEncoder buliteMarshallingEncoder(){ final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial"); final MarshallingConfiguration configuration = new MarshallingConfiguration(); configuration.setVersion(5); MarshallerProvider provider = new DefaultMarshallerProvider(marshallerFactory, configuration); //5、构建Netty的MarshallingEncoder对象,MarshallingEncoder用于实现序列化接口的POJO对象序列化为二进制数组 MarshallingEncoder encoder = new MarshallingEncoder(provider); return encoder; } }
服务器端
package com.xyq.netty.heartBeat; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; public class Server { public static void main(String[] args) throws Exception{ //1、定义两个线程组 EventLoopGroup pGroup = new NioEventLoopGroup();//一个是用于处理服务器端接收客户端连接的 EventLoopGroup cGroup = new NioEventLoopGroup();//一个是进行网络通信的(网络读写的) try { //2、创建辅助工具类,用于服务器通道的一系列配置 ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(pGroup, cGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 1024) .option(ChannelOption.SO_SNDBUF, 32*1024) .option(ChannelOption.SO_RCVBUF, 32*1024) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { //设置marshalling编码 ch.pipeline().addLast(MarshallingCodeCFactory.buliteMarshallingDecoder()); //设置marshalling解码 ch.pipeline().addLast(MarshallingCodeCFactory.buliteMarshallingEncoder()); //处理业务 ch.pipeline().addLast(new ServerHeartBeatHanlder()); } }); //4、进行绑定 ChannelFuture cf = bootstrap.bind(8765).sync(); System.out.println("server start ..."); //5、等待关闭 cf.channel().closeFuture().sync(); } finally{ pGroup.shutdownGracefully(); cGroup.shutdownGracefully(); } } }
服务器处理类
package com.xyq.netty.heartBeat; import java.util.HashMap; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; public class ServerHeartBeatHanlder extends ChannelHandlerAdapter{ private static HashMap<String, String> AUTH_IP_MAP = new HashMap<String, String>(); private static final String SUCCESS_KEY = "auth_success_key"; static{ AUTH_IP_MAP.put("169.254.27.94", "1234"); } //认证 private boolean auth(ChannelHandlerContext ctx, Object msg){ //分割 String[] ret = ((String) msg).split(","); //在AUTH_IP_MAP中获取values String auth = AUTH_IP_MAP.get(ret[0]); if(auth != null && auth.equals(ret[1])){ ctx.writeAndFlush(SUCCESS_KEY); return true; }else { //链接失败,关闭客户端的链接 System.out.println(msg); ctx.writeAndFlush("认证失败").addListener(ChannelFutureListener.CLOSE); return false; } } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if(msg instanceof String){ auth(ctx, msg); }else if (msg instanceof RequestInfo) { //将数据放到RequestInfo中 RequestInfo info = (RequestInfo) msg; System.out.println("--------------------------------------------"); System.out.println("当前主机ip为: " + info.getIp()); System.out.println("当前主机cpu情况: "); HashMap<String, Object> cpu = info.getCpuPercMap(); System.out.println("总使用率: " + cpu.get("combined")); System.out.println("用户使用率: " + cpu.get("user")); System.out.println("系统使用率: " + cpu.get("sys")); System.out.println("等待率: " + cpu.get("wait")); System.out.println("空闲率: " + cpu.get("idle")); System.out.println("当前主机memory情况: "); HashMap<String, Object> memory = info.getMemoryMap(); System.out.println("内存总量: " + memory.get("total")); System.out.println("当前内存使用量: " + memory.get("used")); System.out.println("当前内存剩余量: " + memory.get("free")); System.out.println("--------------------------------------------"); //返回一条状态给客户端 ctx.writeAndFlush("信息收到"); }else { ctx.writeAndFlush("信息失败").addListener(ChannelFutureListener.CLOSE); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); } }
客户端
package com.xyq.netty.heartBeat; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; public class Client { public static void main(String[] args) throws Exception{ //创建线程组 EventLoopGroup group = new NioEventLoopGroup(); try { //创建工具类 Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { //设置marshalling解码 ch.pipeline().addLast(MarshallingCodeCFactory.buliteMarshallingDecoder()); //设置marshalling编码 ch.pipeline().addLast(MarshallingCodeCFactory.buliteMarshallingEncoder()); ch.pipeline().addLast(new ClientHeartBeatHandler()); } }); //4、建立连接 ChannelFuture cf = bootstrap.connect("127.0.0.1", 8765).sync(); System.out.println("Client connet ...."); //5、等待关闭 cf.channel().closeFuture().sync(); } finally{ group.shutdownGracefully(); } } }
客户端处理类
package com.xyq.netty.heartBeat; import java.net.InetAddress; import java.util.HashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.hyperic.sigar.CpuPerc; import org.hyperic.sigar.Mem; import org.hyperic.sigar.Sigar; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import io.netty.util.ReferenceCountUtil; public class ClientHeartBeatHandler extends ChannelHandlerAdapter { private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); private ScheduledFuture<?> heartBeat; //主动向服务器发送认证信息 private InetAddress addr ; private static final String SUCCESS_KEY = "auth_success_key"; @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { addr = InetAddress.getLocalHost(); String ip = addr.getHostAddress(); String key = "1234"; //证书 String auth = ip + "," + key; ctx.writeAndFlush(auth); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { try { if(msg instanceof String){ String ret = (String)msg; if(SUCCESS_KEY.equals(ret)){ // 握手成功,主动发送心跳消息 this.heartBeat = this.scheduler.scheduleWithFixedDelay(new HeartBeatTask(ctx), 0, 2, TimeUnit.SECONDS); System.out.println(msg); } else { System.out.println(msg); } } } finally { ReferenceCountUtil.release(msg); } } private class HeartBeatTask implements Runnable { private final ChannelHandlerContext ctx; public HeartBeatTask(final ChannelHandlerContext ctx) { this.ctx = ctx; } @Override public void run() { try { RequestInfo info = new RequestInfo(); //ip info.setIp(addr.getHostAddress()); Sigar sigar = new Sigar(); //cpu prec CpuPerc cpuPerc = sigar.getCpuPerc(); HashMap<String, Object> cpuPercMap = new HashMap<String, Object>(); cpuPercMap.put("combined", cpuPerc.getCombined()); cpuPercMap.put("user", cpuPerc.getUser()); cpuPercMap.put("sys", cpuPerc.getSys()); cpuPercMap.put("wait", cpuPerc.getWait()); cpuPercMap.put("idle", cpuPerc.getIdle()); // memory Mem mem = sigar.getMem(); HashMap<String, Object> memoryMap = new HashMap<String, Object>(); memoryMap.put("total", mem.getTotal() / 1024L); memoryMap.put("used", mem.getUsed() / 1024L); memoryMap.put("free", mem.getFree() / 1024L); info.setCpuPercMap(cpuPercMap); info.setMemoryMap(memoryMap); ctx.writeAndFlush(info); } catch (Exception e) { e.printStackTrace(); } } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); if (heartBeat != null) { heartBeat.cancel(true); heartBeat = null; } ctx.fireExceptionCaught(cause); } }