반응형

웹서버와 자바 어플리케이션 간의 소켓 연결을 위해 Netty를 사용하고 있는데 특성상 연결이 끊길경우 반드시 자동 재접속을 시도를 해야 한다. 이것을 구현해보려고 많은 예시를 봐왔지만 딱히 해결 될 기미가 보이지 않던 찰나에 좋은 코드를 찾았고 이를 적용하니 정상적으로 대응이 되었다.

 

public final class UptimeClient {
  
      static final String HOST = System.getProperty("host", "127.0.0.1");
      static final int PORT = Integer.parseInt(System.getProperty("port", "8080"));
      // Sleep 5 seconds before a reconnection attempt.
      static final int RECONNECT_DELAY = Integer.parseInt(System.getProperty("reconnectDelay", "5"));
      // Reconnect when the server sends nothing for 10 seconds.
      private static final int READ_TIMEOUT = Integer.parseInt(System.getProperty("readTimeout", "10"));
  
      private static final UptimeClientHandler handler = new UptimeClientHandler();
      private static final Bootstrap bs = new Bootstrap();
  
      public static void main(String[] args) throws Exception {
          EventLoopGroup group = new NioEventLoopGroup();
          bs.group(group)
                  .channel(NioSocketChannel.class)
                  .remoteAddress(HOST, PORT)
                  .handler(new ChannelInitializer<SocketChannel>() {
                      @Override
                      protected void initChannel(SocketChannel ch) throws Exception {
                          ch.pipeline().addLast(new IdleStateHandler(READ_TIMEOUT, 0, 0), handler);
                      }
                  });
          bs.connect();
      }
  
      static void connect() {
          bs.connect().addListener(new ChannelFutureListener() {
              @Override
              public void operationComplete(ChannelFuture future) throws Exception {
                  if (future.cause() != null) {
                      handler.startTime = -1;
                      handler.println("Failed to connect: " + future.cause());
                  }
              }
          });
      }
  }

 

위의 예제의 경우 Netty Client 소스 코드를 Main에서 실행하도록 했지만 사실 Main 문을 그냥 하나의 메서드로 바꿔버린다음 다른 곳에서 호출하는 형식으로 써도 된다. 실제로 그렇게 쓰고 있고 적용이 잘 된다.

 

예를 들어 public static void main 을 public void NettyClient로 바꾼다음 다른 곳에서 NettyClient()로 바꾼다던지 말이다.

 

NettyClient의 실질적인 기능을 부여할 Handler의 경우 addLast를 통해 추가한다

 


  package io.netty.example.uptime;
  
  import io.netty.channel.ChannelHandler.Sharable;
  import io.netty.channel.ChannelHandlerContext;
  import io.netty.channel.SimpleChannelInboundHandler;
  import io.netty.handler.timeout.IdleState;
  import io.netty.handler.timeout.IdleStateEvent;
  
  import java.util.concurrent.TimeUnit;
  
 /**
   * Keep reconnecting to the server while printing out the current uptime and
   * connection attempt getStatus.
   */
  @Sharable
  public class UptimeClientHandler extends SimpleChannelInboundHandler<Object> {
  
      long startTime = -1;
  
      @Override
      public void channelActive(ChannelHandlerContext ctx) {
          if (startTime < 0) {
              startTime = System.currentTimeMillis();
          }
          println("Connected to: " + ctx.channel().remoteAddress());
      }
  
      @Override
      public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
          // Discard received data
      }
  
      @Override
      public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
          if (!(evt instanceof IdleStateEvent)) {
              return;
          }
  
          IdleStateEvent e = (IdleStateEvent) evt;
          if (e.state() == IdleState.READER_IDLE) {
              // The connection was OK but there was no traffic for last period.
              println("Disconnecting due to no inbound traffic");
              ctx.close();
          }
      }
  
      @Override
      public void channelInactive(final ChannelHandlerContext ctx) {
          println("Disconnected from: " + ctx.channel().remoteAddress());
      }
  
      @Override
      public void channelUnregistered(final ChannelHandlerContext ctx) throws Exception {
          println("Sleeping for: " + UptimeClient.RECONNECT_DELAY + 's');
  
          ctx.channel().eventLoop().schedule(new Runnable() {
              @Override
             public void run() {
                  println("Reconnecting to: " + UptimeClient.HOST + ':' + UptimeClient.PORT);
                  UptimeClient.connect();
              }
          }, UptimeClient.RECONNECT_DELAY, TimeUnit.SECONDS);
      }
  
      @Override
      public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
          cause.printStackTrace();
          ctx.close();
      }
  
      void println(String msg) {
          if (startTime < 0) {
              System.err.format("[SERVER IS DOWN] %s%n", msg);
          } else {
              System.err.format("[UPTIME: %5ds] %s%n", (System.currentTimeMillis() - startTime) / 1000, msg);
          }
      }
  }

Netty에서 많은 기능을 제공하지만 재접속 부분만 보자면 channelUnregistered를 보면 된다.

 

channelUnregistered에서 EventLoop를 통해 꾸준히 Connection을 시도하는 것을 확인할 수 있다.

 

이렇게 적용했을때 단순 연결 끊김 외에도 유령 세션에도 대응할 수 있었다. 

+ Recent posts