본문 바로가기

Java/Netty

TCP Keepalive

TCP 연결이 설정된 이후 정상적인 상황이라면 FIN 메시지를 송수신 했을 때, 연결이 해제됩니다.

하지만 전원이 차단되거나 오류에 의해 오동작을 하면 하나의 단말만 연결이 유지되는 Half Open Connection이 발생할 수 있습니다. 이러한 소켓이 꽉차면 정상적인 Connection요청에 응답을 할 수 없는 상황이 발생하게 됩니다.

이런 현상을 막기 위해 Keep-alive라는 기능이 있습니다.

 

TCP Keepalive

Tcp Keep-alive는 한 번 맺은 세션을 요청이 끝나더라도 유지해주는 기능입니다.

 

  • tcp_keepalive_time으로 설정한 keepalive timeout 시간이 지나면 서버에서 Keepalive 확인 패킷을 보냅니다.
  • 이 패킷에 대한 응답(ACK 패킷)을 받으면 타이머는 원래 값으로 돌아가 다시 카운트를 진행합니다.
  • 응답을 받지 못한 경우에는 tcp_keepalive_intvl에 정의된 시간만큼 경과한 후 요청을 다시 보내게 되고, tcp_keepalive_probes 에 정의된 횟수만큼 보냅니다.
  • 이후로도 응답이 없을 경우 클라이언트는 연결이 끊어졌다고 인지하고 서버에 RST 패킷을 보낸 다음 자신의 소켓을 닫습니다.

(* RST 패킷 : 비정상적인 세션 연결 끊기에 해당. 이 패킷을 발송하는 측이 현재 접속하고 있는 곳과 즉시 연결을 끊고자 할 때 사용)

Keepalive의 장점

  • Keepalive 기능을 사용하면 불필요한 3 way handshake를 줄이고 TIME_WAIT 소켓도 줄일 수 있습니다.
  • 연결이 끊어졌음에도 FIN 패킷을 받지 못해 정리되지 않고 남아있던 좀비 커넥션을 없애는 효과가 있습니다

Netty에서의 keep-alive 설정

Netty에서 사용하기 위해서는 Bootstrap에서 ChannelOption.SO_KEEPALIVE option으로 Keep-alive 기능을 활성화 하고, Keep-Alive 메세지를 보내는 기능을 위해 IdleStateHandler를 설정해야합니다.

참고로 서버와 클라이언트 양쪽에서 Keep-alive 설정을 하는 것이 바람직하고, 더 안정적입니다. 서로 유휴 상태를 감지할 수 있기 때문이죠

  • 서버 Bootstrap에서 option 설정 (클라이언트는 .option으로 설정) 
    .childOption(ChannelOption.*SO_KEEPALIVE*,true) // Keep-alive 를 활성화
  • ChannelPipeline에 IdleStateHandler 추가
 p.addLast(new IdleStateHandler(60,30,0)) // 초단위로 설정

 

netty javadoc

  • Handler에서 Idle 상태가 발생했을 때 처리할 로직 작성
/**
     * keep-alive 관련 설정
     * @param ctx
     * @param obj
     * @throws Exception
     */
    @Override
    public void userEventTriggered( ChannelHandlerContext ctx, Object obj ) throws Exception {

        if( obj instanceof IdleStateEvent ){
            IdleStateEvent event = ( IdleStateEvent ) obj;
            InetSocketAddress inetSocketAddress = ( InetSocketAddress ) ctx.channel().remoteAddress();

            if( event.state() == IdleState.READER_IDLE ){
                // 읽기 유휴 상태인 경우
                log.info(TcpMessageCode.READER_IDLE.getMessage(), inetSocketAddress.getAddress(), inetSocketAddress.getPort() );
                ctx.close(); // 채널을 닫는다
            }else if( event.state() == IdleState.WRITER_IDLE ){
                // 쓰기 유휴 상태인 경우
                log.info(TcpMessageCode.WRITER_IDLE.getMessage(), inetSocketAddress.getAddress(), inetSocketAddress.getPort() );
                ctx.close(); // 채널을 닫는다
            }
        }
    }

 

'Java > Netty' 카테고리의 다른 글

ChannelGroup과 GlobalEventExecutor  (0) 2024.07.01
Netty의 이벤트 루프 이해하기  (0) 2024.06.20