架构师养成记--22.客户端与服务器端保持连接的解决方案,netty的ReadTimeoutHandler
概述
保持客户端与服务器端连接的方案常用的有3种
1.长连接,也就是客户端与服务器端一直保持连接,适用于客户端比较少的情况。
2.定时段连接,比如在某一天的凌晨建立连接,适用于对实时性要求不高的情况。
3.设置连接超时,比如超过1分钟没有传输数据就断开连接,等下次需要的时候再建立连接,这种方案比较常用。
netty的ReadTimeOut实现方案3
服务端
大部分代码都保持不变,有变化的代码在第30行,设置服务端的超时时间
1 import io.netty.bootstrap.ServerBootstrap; 2 import io.netty.channel.ChannelFuture; 3 import io.netty.channel.ChannelInitializer; 4 import io.netty.channel.ChannelOption; 5 import io.netty.channel.EventLoopGroup; 6 import io.netty.channel.nio.NioEventLoopGroup; 7 import io.netty.channel.socket.SocketChannel; 8 import io.netty.channel.socket.nio.NioServerSocketChannel; 9 import io.netty.handler.logging.LogLevel; 10 import io.netty.handler.logging.LoggingHandler; 11 import io.netty.handler.timeout.ReadTimeoutHandler; 12 13 public class Server { 14 15 public static void main(String[] args) throws Exception{ 16 17 EventLoopGroup pGroup = new NioEventLoopGroup(); 18 EventLoopGroup cGroup = new NioEventLoopGroup(); 19 20 ServerBootstrap b = new ServerBootstrap(); 21 b.group(pGroup, cGroup) 22 .channel(NioServerSocketChannel.class) 23 .option(ChannelOption.SO_BACKLOG, 1024) 24 //设置日志 25 .handler(new LoggingHandler(LogLevel.INFO)) 26 .childHandler(new ChannelInitializer<SocketChannel>() { 27 protected void initChannel(SocketChannel sc) throws Exception { 28 sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder()); 29 sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder()); 30 sc.pipeline().addLast(new ReadTimeoutHandler(5)); 31 sc.pipeline().addLast(new ServerHandler()); 32 } 33 }); 34 35 ChannelFuture cf = b.bind(8765).sync(); 36 37 cf.channel().closeFuture().sync(); 38 pGroup.shutdownGracefully(); 39 cGroup.shutdownGracefully(); 40 41 } 42 }
ServerHandler代码也没有什么变化
1 import io.netty.channel.ChannelHandlerAdapter; 2 import io.netty.channel.ChannelHandlerContext; 3 4 public class ServerHandler extends ChannelHandlerAdapter{ 5 6 @Override 7 public void channelActive(ChannelHandlerContext ctx) throws Exception { 8 9 } 10 11 @Override 12 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 13 Request request = (Request)msg; 14 System.out.println("Server : " + request.getId() + ", " + request.getName() + ", " + request.getRequestMessage()); 15 Response response = new Response(); 16 response.setId(request.getId()); 17 response.setName("response" + request.getId()); 18 response.setResponseMessage("响应内容" + request.getId()); 19 ctx.writeAndFlush(response);//.addListener(ChannelFutureListener.CLOSE); 20 } 21 22 @Override 23 public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 24 25 } 26 27 @Override 28 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 29 ctx.close(); 30 } 31 32 33 34 }
客户端
客户端的代码也设置了超时时间(实际上只要服务器端设置也就可以了,有人说客户端不设置会出问题,现在还没有发现什么问题)。主要看getChannelFuture这个方法,this.cf == null是第一次连接的时候用到的,!this.cf.channel().isActive() 是连接超时后重新发起连接用到的。再看main方法,可以发现for(int i = 1; i <= 3; i++ ) 这个循环中,每个循环停顿4秒,也就是每隔4秒发送一次请求,而服务器端的超时时间设置为5秒,那么在这个for循环期间连接是不会断开的,等for循环结束 cf.channel().closeFuture().sync(); 断开连接this.cf.channel().isActive() 变为否,在new Thread()中再次发送请求,getChannelFuture会重新建立连接。
1 import io.netty.bootstrap.Bootstrap; 2 import io.netty.channel.ChannelFuture; 3 import io.netty.channel.ChannelInitializer; 4 import io.netty.channel.EventLoopGroup; 5 import io.netty.channel.nio.NioEventLoopGroup; 6 import io.netty.channel.socket.SocketChannel; 7 import io.netty.channel.socket.nio.NioSocketChannel; 8 import io.netty.handler.logging.LogLevel; 9 import io.netty.handler.logging.LoggingHandler; 10 import io.netty.handler.timeout.ReadTimeoutHandler; 11 12 import java.util.concurrent.TimeUnit; 13 14 15 /** 16 * Best Do It 17 */ 18 public class Client { 19 20 private static class SingletonHolder { 21 static final Client instance = new Client(); 22 } 23 24 public static Client getInstance(){ 25 return SingletonHolder.instance; 26 } 27 28 private EventLoopGroup group; 29 private Bootstrap b; 30 private ChannelFuture cf ; 31 32 private Client(){ 33 group = new NioEventLoopGroup(); 34 b = new Bootstrap(); 35 b.group(group) 36 .channel(NioSocketChannel.class) 37 .handler(new LoggingHandler(LogLevel.INFO)) 38 .handler(new ChannelInitializer<SocketChannel>() { 39 @Override 40 protected void initChannel(SocketChannel sc) throws Exception { 41 sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder()); 42 sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder()); 43 //超时handler(当服务器端与客户端在指定时间以上没有任何进行通信,则会关闭响应的通道,主要为减小服务端资源占用) 44 sc.pipeline().addLast(new ReadTimeoutHandler(5)); 45 sc.pipeline().addLast(new ClientHandler()); 46 } 47 }); 48 } 49 50 public void connect(){ 51 try { 52 this.cf = b.connect("127.0.0.1", 8765).sync(); 53 System.out.println("远程服务器已经连接, 可以进行数据交换.."); 54 } catch (Exception e) { 55 e.printStackTrace(); 56 } 57 } 58 59 public ChannelFuture getChannelFuture(){ 60 61 if(this.cf == null){ 62 this.connect(); 63 } 64 if(!this.cf.channel().isActive()){ 65 this.connect(); 66 } 67 68 return this.cf; 69 } 70 71 public static void main(String[] args) throws Exception{ 72 final Client c = Client.getInstance(); 73 //c.connect(); 74 75 ChannelFuture cf = c.getChannelFuture(); 76 for(int i = 1; i <= 3; i++ ){ 77 Request request = new Request(); 78 request.setId("" + i); 79 request.setName("pro" + i); 80 request.setRequestMessage("数据信息" + i); 81 cf.channel().writeAndFlush(request); 82 TimeUnit.SECONDS.sleep(4); 83 } 84 85 cf.channel().closeFuture().sync(); 86 87 88 new Thread(new Runnable() { 89 @Override 90 public void run() { 91 try { 92 System.out.println("进入子线程..."); 93 ChannelFuture cf = c.getChannelFuture(); 94 System.out.println(cf.channel().isActive()); 95 System.out.println(cf.channel().isOpen()); 96 97 //再次发送数据 98 Request request = new Request(); 99 request.setId("" + 4); 100 request.setName("pro" + 4); 101 request.setRequestMessage("数据信息" + 4); 102 cf.channel().writeAndFlush(request); 103 cf.channel().closeFuture().sync(); 104 System.out.println("子线程结束."); 105 } catch (InterruptedException e) { 106 e.printStackTrace(); 107 } 108 } 109 }).start(); 110 111 System.out.println("断开连接,主线程结束.."); 112 113 } 114 115 116 117 }
clientHandler没有什么变化
1 import io.netty.channel.ChannelHandlerAdapter; 2 import io.netty.channel.ChannelHandlerContext; 3 import io.netty.util.ReferenceCountUtil; 4 5 public class ClientHandler extends ChannelHandlerAdapter{ 6 7 @Override 8 public void channelActive(ChannelHandlerContext ctx) throws Exception { 9 10 } 11 12 @Override 13 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 14 try { 15 Response resp = (Response)msg; 16 System.out.println("Client : " + resp.getId() + ", " + resp.getName() + ", " + resp.getResponseMessage()); 17 } finally { 18 ReferenceCountUtil.release(msg); 19 } 20 } 21 22 @Override 23 public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 24 25 } 26 27 @Override 28 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 29 ctx.close(); 30 } 31 32 }
工厂类不变
1 import io.netty.handler.codec.marshalling.DefaultMarshallerProvider; 2 import io.netty.handler.codec.marshalling.DefaultUnmarshallerProvider; 3 import io.netty.handler.codec.marshalling.MarshallerProvider; 4 import io.netty.handler.codec.marshalling.MarshallingDecoder; 5 import io.netty.handler.codec.marshalling.MarshallingEncoder; 6 import io.netty.handler.codec.marshalling.UnmarshallerProvider; 7 8 import org.jboss.marshalling.MarshallerFactory; 9 import org.jboss.marshalling.Marshalling; 10 import org.jboss.marshalling.MarshallingConfiguration; 11 12 /** 13 * Marshalling工厂 14 * @author(alienware) 15 * @since 2014-12-16 16 */ 17 public final class MarshallingCodeCFactory { 18 19 /** 20 * 创建Jboss Marshalling解码器MarshallingDecoder 21 * @return MarshallingDecoder 22 */ 23 public static MarshallingDecoder buildMarshallingDecoder() { 24 //首先通过Marshalling工具类的精通方法获取Marshalling实例对象 参数serial标识创建的是java序列化工厂对象。 25 final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial"); 26 //创建了MarshallingConfiguration对象,配置了版本号为5 27 final MarshallingConfiguration configuration = new MarshallingConfiguration(); 28 configuration.setVersion(5); 29 //根据marshallerFactory和configuration创建provider 30 UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, configuration); 31 //构建Netty的MarshallingDecoder对象,俩个参数分别为provider和单个消息序列化后的最大长度 32 MarshallingDecoder decoder = new MarshallingDecoder(provider, 1024); 33 return decoder; 34 } 35 36 /** 37 * 创建Jboss Marshalling编码器MarshallingEncoder 38 * @return MarshallingEncoder 39 */ 40 public static MarshallingEncoder buildMarshallingEncoder() { 41 final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial"); 42 final MarshallingConfiguration configuration = new MarshallingConfiguration(); 43 configuration.setVersion(5); 44 MarshallerProvider provider = new DefaultMarshallerProvider(marshallerFactory, configuration); 45 //构建Netty的MarshallingEncoder对象,MarshallingEncoder用于实现序列化接口的POJO对象序列化为二进制数组 46 MarshallingEncoder encoder = new MarshallingEncoder(provider); 47 return encoder; 48 } 49 }
自定义的Request和Response对象没有什么变化,这里不再赘述