티스토리 뷰

Java 기반의 비동기 네트워크 프레임워크인 Netty는 고성능 서버/클라이언트 애플리케이션 개발에 자주 활용됩니다.
이번 글에서는 총길이를 사전에 알 수 없는 바이트 스트림 데이터를 수신해야 하는 상황에서 Netty를 활용해 어떻게 문제를 해결할 수 있었는지 정리해 보겠습니다.


Netty와 데이터 수신의 특징

Netty는 비동기 이벤트 기반 네트워크 프레임워크로, TCP와 같은 연결 지향 프로토콜 기반의 통신에서 널리 사용됩니다.
우리는 외부 기관과 통신할 때 Netty를 통해 데이터를 바이트 스트림 형태로 수신하고 있으며, 다음과 같은 상황을 마주하게 됩니다:

  • 데이터는 여러 조각으로 나뉘어 들어오며 한 번에 도착한다는 보장이 없다.
  • 연결을 유지하자니 언제 끝날지 모르고, 끊자니 데이터가 누락될 위험이 있다.

즉, 메시지의 경계를 알 수 없는 스트림을 다루기 때문에 프레이밍(Framing)이 반드시 필요합니다.


Netty가 제공하는 프레이밍(Framing) Decoder

Netty는 다양한 유형의 메시지 분할 처리를 위한 Decoder를 기본 제공하고 있습니다:

  1. LineBasedFrameDecoder
    • 개행 문자(\n, \r\n) 기준으로 메시지를 분리합니다.
    • 텍스트 기반 프로토콜(ex. Telnet)에서 사용됩니다.
  2. DelimiterBasedFrameDecoder
    • 사용자 정의 구분자(ex. @@END@@)를 기준으로 메시지를 나눕니다.
  3. FixedLengthFrameDecoder
    • 고정된 길이의 메시지를 처리합니다. (ex. 1024바이트씩)
  4. LengthFieldBasedFrameDecoder
    • 메시지의 헤더에 포함된 길이 필드를 기반으로 동작합니다.
    • 대부분의 TCP 기반 바이너리 프로토콜에서 활용됩니다.

이러한 Decoder들은 메시지 구조가 명확히 정의되어 있고 송수신 간 협의가 잘 되어 있을 때 매우 효과적입니다.


프레이밍이 어려운 특수한 상황

그러나 다음과 같은 요구사항이 있는 경우, 위의 Decoder 방식으로는 처리하기 어렵습니다:

  1. 메시지 길이가 가변적이다.
  2. 메시지가 매우 길다. (예: 50만 바이트 이상)
  3. 헤더에 전체 길이 정보를 포함할 수 없다.

이러한 경우, 메시지 경계를 일반적인 방식으로 판단하기 어려워 별도의 처리 전략이 필요합니다.


해결 전략: IdleStateHandler + userEventTriggered 활용

총 길이를 알 수 없는 메시지를 다룰 때는 연결 유휴 상태(Idle)를 기반으로 메시지 수신 완료를 판단하는 방식이 유효합니다.
이를 위해 Netty에서는 IdleStateHandler와 userEventTriggered를 함께 사용할 수 있습니다.

userEventTriggered란?

Netty의 ChannelInboundHandler에는 userEventTriggered라는 메서드가 있습니다.
이 메서드는 일반적인 읽기/쓰기 이벤트가 아닌 프레임워크나 사용자 정의 이벤트를 처리하는 데 사용됩니다.

@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    // 사용자 정의 이벤트 처리
}

ctx.fireUserEventTriggered(event) 형태로 파이프라인을 따라 이벤트가 전달됩니다.

 

IdleStateHandler로 타임아웃 감지하기

Netty의 IdleStateHandler는 지정한 시간 동안 읽기/쓰기 이벤트가 없을 경우 자동으로 idle 이벤트를 발생시킵니다.

pipeline.addLast(new IdleStateHandler(10, 0, 0, TimeUnit.SECONDS)); 
pipeline.addLast(new MyIdleHandler());

 

매개변수 설명

public IdleStateHandler(
    long readerIdleTime,
    long writerIdleTime,
    long allIdleTime,
    TimeUnit unit
)

 

  • readerIdleTime: 지정 시간 동안 읽기 없음 → READER_IDLE 발생
  • writerIdleTime: 지정 시간 동안 쓰기 없음 → WRITER_IDLE 발생
  • allIdleTime: 읽기/쓰기 모두 없음 → ALL_IDLE 발생
  • unit: 시간 단위 (예: TimeUnit.SECONDS)
public class MyIdleHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 데이터 수신 시 Idle 타이머 재설정 가능
        // pipeline에서 IdleStateHandler는 처음부터 설정되어 있어야 함
        ... 메시지 적재 ...
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent e = (IdleStateEvent) evt;
            if (e.state() == IdleState.READER_IDLE) {
                System.out.println("10초 동안 읽기 없음 → 메시지 수신 완료로 간주");
                ... 메시지 처리 로직 ...
                ctx.close(); // 연결 종료
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }
}

 


정리

클라이언트 측에서 스트림 형태로 데이터를 전송할 때,
특정 시간(예: 10초) 동안 추가 데이터가 수신되지 않는다면 더 이상 전송할 메시지가 없는 것으로 간주하고
수신 완료 후 후속 처리 로직을 진행하는 방식으로 문제를 해결할 수 있습니다.

이러한 방식은 프레이밍 조건을 정의하기 어려운 통신 환경에서 유용하며,
Netty의 유휴 감지 기능을 적절히 활용하면 안정적인 데이터 수신 처리가 가능합니다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
글 보관함