티스토리 뷰
Java 기반의 비동기 네트워크 프레임워크인 Netty는 고성능 서버/클라이언트 애플리케이션 개발에 자주 활용됩니다.
이번 글에서는 총길이를 사전에 알 수 없는 바이트 스트림 데이터를 수신해야 하는 상황에서 Netty를 활용해 어떻게 문제를 해결할 수 있었는지 정리해 보겠습니다.
Netty와 데이터 수신의 특징
Netty는 비동기 이벤트 기반 네트워크 프레임워크로, TCP와 같은 연결 지향 프로토콜 기반의 통신에서 널리 사용됩니다.
우리는 외부 기관과 통신할 때 Netty를 통해 데이터를 바이트 스트림 형태로 수신하고 있으며, 다음과 같은 상황을 마주하게 됩니다:
- 데이터는 여러 조각으로 나뉘어 들어오며 한 번에 도착한다는 보장이 없다.
- 연결을 유지하자니 언제 끝날지 모르고, 끊자니 데이터가 누락될 위험이 있다.
즉, 메시지의 경계를 알 수 없는 스트림을 다루기 때문에 프레이밍(Framing)이 반드시 필요합니다.
Netty가 제공하는 프레이밍(Framing) Decoder
Netty는 다양한 유형의 메시지 분할 처리를 위한 Decoder를 기본 제공하고 있습니다:
- LineBasedFrameDecoder
- 개행 문자(\n, \r\n) 기준으로 메시지를 분리합니다.
- 텍스트 기반 프로토콜(ex. Telnet)에서 사용됩니다.
- DelimiterBasedFrameDecoder
- 사용자 정의 구분자(ex. @@END@@)를 기준으로 메시지를 나눕니다.
- FixedLengthFrameDecoder
- 고정된 길이의 메시지를 처리합니다. (ex. 1024바이트씩)
- LengthFieldBasedFrameDecoder
- 메시지의 헤더에 포함된 길이 필드를 기반으로 동작합니다.
- 대부분의 TCP 기반 바이너리 프로토콜에서 활용됩니다.
이러한 Decoder들은 메시지 구조가 명확히 정의되어 있고 송수신 간 협의가 잘 되어 있을 때 매우 효과적입니다.
프레이밍이 어려운 특수한 상황
그러나 다음과 같은 요구사항이 있는 경우, 위의 Decoder 방식으로는 처리하기 어렵습니다:
- 메시지 길이가 가변적이다.
- 메시지가 매우 길다. (예: 50만 바이트 이상)
- 헤더에 전체 길이 정보를 포함할 수 없다.
이러한 경우, 메시지 경계를 일반적인 방식으로 판단하기 어려워 별도의 처리 전략이 필요합니다.
해결 전략: 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의 유휴 감지 기능을 적절히 활용하면 안정적인 데이터 수신 처리가 가능합니다.
'JAVA' 카테고리의 다른 글
[java] jdom xPath 호출 시 멈춤 현상 해결 (0) | 2024.10.11 |
---|---|
[java] 인코딩이 다른 시스템간 byte 데이터 주고받기 (1) | 2024.09.26 |
MyBatis ORACLE SEQUENCE 미증가 해결 (1) | 2024.04.19 |
ObjectMapper 결과 값 주입이 안되는 경우 (0) | 2024.01.17 |
java RSA decryption is failed (bouncycastleprovider 관련) (1) | 2023.10.06 |
- Total
- Today
- Yesterday
- Java
- SSL
- 깨짐
- Eclipse
- spring
- jQuery
- React
- Windows
- WAS
- 컨테이너
- vscode
- gradle
- JSON
- TLS
- Git
- stateless 설계
- tomcat
- mybatis
- 날짜
- SpringBoot
- Linux
- html
- parse
- web
- IMAGE
- docker
- SQL
- usereventtriggered
- Oracle
- JPA
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |