ultra_dev
Netty 프레임워크 기본 [공식 문서 참조] 본문
○ 목차
- Netty란
- 이벤트루프와 소켓 생성 흐름
- Basic 예제
- Discard 예제
- Echo 예제
- Time 예제
- LifeCycle 예제
- Duplexhandler
○ 프로젝트
- 프로젝트 구조

○ Netty란
- Netty는 비동기 이벤트 기반 네트워크 애플리케이션 프레임워크로, 빠르고 신뢰성 있는 네트워크 서버 및 클라이언트를 쉽게 개발할 수 있게 도와줌
- 비동기식 네트워크 통신을 지원하며, Java NIO를 기반으로 구축되었으며 주요 특징으로는 높은 성능, 간편한 사용성, 강력한 유연성 등이 있음
○ 사용이유
- 현재 회사에서 자율주행차량 관련 TCP-Gateway 프로젝트에서 효율적인 개발을 위해 사용
- Byte 단위로 데이터를 주고받기 때문에 네트워크 프로토콜의 효율적인 처리와 사용자 정의 데이터 포맷을 손쉽게 구현할 수 있음
- 비동기 논블로킹 방식으로 동작해 다수의 연결을 효율적으로 처리하고, 리소스 사용을 최소화할 수 있음
○ 이벤트 루프란?
- 이벤트 루프(Event Loop):
- 싱글 스레드 기반 비동기 I/O 처리 루프
- 연결 요청, 데이터 읽기/쓰기, 이벤트 핸들링 등의 작업을 비동기적으로 처리
- 한 이벤트 루프는 다수의 채널(Channel)을 관리
- 한 채널은 항상 동일한 이벤트 루프에 바인딩되어 처리됨
- 이를 통해 스레드 경쟁을 방지하고, 높은 성능과 안정성을 제공
- 이벤트 루프 그룹(EventLoopGroup)
- Netty는 작업의 성격에 따라 이벤트 루프를 그룹으로 관리. 주요 이벤트 루프 그룹은 다음과 같음
- BossGroup
- 서버 소켓(ServerSocketChannel)에서 클라이언트의 연결 요청(Accept)을 처리
- 연결 요청이 수락되면 작업을 WorkerGroup으로 위임
- 일반적으로 스레드 1개로 충분
- WorkerGroup
- 클라이언트와의 데이터 송수신 및 이벤트 처리를 담당
- WorkerGroup은 여러 스레드를 사용하며, 각 스레드는 다수의 클라이언트를 비동기적으로 처리
- 이벤트 루프 주요 특징
- 싱글 스레드 모델
- 이벤트 루프는 1개의 스레드로 작동하며, 각 채널에 전용으로 할당
- 한 채널의 모든 작업(I/O, 이벤트 핸들링 등)은 동일한 스레드에서 처리
- 다중 채널 관리
- 각 이벤트 루프는 다수의 채널을 비동기로 처리
- 네트워크 부하가 크지 않다면 스레드 1개로 수백~수천 개의 채널을 관리할 수 있음
- 비동기 및 논블로킹 처리
- Netty는 작업을 비동기적으로 처리하며, 논블로킹 방식으로 네트워크 I/O를 수행
- 작업의 결과는 ChannelFuture 객체를 통해 비동기적으로 확인 가능
- 싱글 스레드 모델
○ 소켓 생성 흐름 간략화
- Netty에서 소켓 생성은 ServerBootstrap과 Bootstrap을 통해 이루어지며 흐름은 아래와 같음
- 서버 소켓 생성
- ServerBootstrap을 통해 서버 소켓 생성
- 특정 포트에 바인딩 되어 클라이언트의 연결 요청 대기
- 이 소켓을 "서버 소켓 A"이라고 명명
- 서버 소켓 A는 여러 클라이언트의 연결 요청을 처리하는 수신용 소켓으로 데이터 통신에 관여하지 않음
- 서버 소켓 A는 BossGroup에 속하며, BossGroup의 이벤트 루프가 관리
- 클라이언트 소켓 생성
- Bootstrap을 통해 클라이언트 소켓 생성
- 서버의 IP 주소와 포트로 연결 요청 전송
- 이 소켓을 "클라이언트 소켓 B"이라고 명명
- 연결 요청 및 수락
- "클라이언트 소켓 B"가 "서버 소켓 A"에 연결 요청 전송
- BossGroup에 속한 이벤트 루프가 이 연결 요청을 처리하며, 요청이 수락되면 서버는 WorkerGroup으로 작업을 위임
- 따라서, "서버 소켓 A"가 연결 요청 수락한 뒤, 새로운 채널 생성
- 새로운 소켓 생성
- 연결이 수락 되면, 서버는 새로운 소켓 생성
- 이 소켓을 "서버 소켓 C"라고 명명
- "서버 소켓 C"는 WorkerGroup의 한 이벤트 루프(스레드)에 바인딩되어, "클라이언트 소켓 B"와 직접 통신하며 데이터 송수신 작업 처리
- "서버 소켓 A"는 여전히 다른 클라이언트의 연결 요청 대기
○ 01_Basic 예제
- 목표
- Netty를 사용하여 간단한 클라이언트-서버 애플리케이션을 구축
- 관련 코드
- 서버 BasicServer Class

- 서버 ServerHandler Class

- 클라이언트 BasicClient Class

- 클라이언트 ClientHandler Class

- 주요 구성 요소
- EventLoopGroup: I/O 작업을 처리하는 스레드 풀
- Bootstrap: 클라이언트 설정을 위한 도우미 클래스
- ServerBootstrap: 서버 설정을 위한 도우미 클래스
- Channel: 네트워크 소켓을 나타내며 데이터의 입출력을 처리
- ChannelInboundHandlerAdapter: 수신된 데이터를 처리하는 기본 핸들러
- 코드 설명
- 서버: 클라이언트의 연결을 수락하고 연결 상태를 출력
- ServerBootstrap을 통해 서버 설정 및 클라이언트 연결 요청 대기
- ServerHandler의 channelActive 메소드에서 클라이언트 연결 시 클라이언트 주소 출력
- 클라이언트: 서버에 연결하고 연결 상태를 출력
- Bootstrap을 통해 클라이언트 설정 및 서버 연결 요청
- ClientHandler의 channelActive 메소드에서 서버 연결 시 상태 출력
- 서버: 클라이언트의 연결을 수락하고 연결 상태를 출력
- 결과
- BasicServer
- BasicClient
○ 02_Discard 예제

- 목표
- Discard 서버는 수신한 데이터를 처리하지 않고 버리는 서버로, 이를 통해 Netty의 데이터 흐름의 기본 개념을 이해
- 관련 코드
클라이언트

서버

- 코드 설명
- 서버 : 수신된 데이터를 단순히 버림
- ServerBootstrap을 통해 서버 설정 및 클라이언트 연결 요청 대기
- DiscardServerHandler의 channelRead0 메소드에서 수신된 데이터를 읽지만 처리하지 않음 (무시)
- 클라이언트 : 서버에 데이터를 전송
- Bootstrap을 통해 클라이언트 설정 및 서버 연결 요청
- DiscardClientHandler의 channelActive 메소드에서 서버에 데이터를 전송
- 서버 : 수신된 데이터를 단순히 버림
- 결과 : Basic 예제와 상동
○ 03_Echo 예제

- 목표
- Echo 서버는 클라이언트로부터 받은 메시지를 그대로 다시 클라이언트에 전송하는 서버로, 이를 통해 Netty의 메시지 처리 방식의 기본 개념을 이해
- 관련 코드
클라이언트


서버

3. 코드 설명
- 서버 : 클라이언트로부터 수신된 메시지를 그대로 다시 클라이언트로 전송
- ServerBootstrap을 통해 서버 설정 및 클라이언트 연결 요청 대기
- EchoServerHandler의 channelRead 메소드에서 클라이언트로부터 수신된 메시지를 다시 클라이언트로 전송
- 클라이언트 : 서버에 메시지를 보내고, 서버로부터 받은 메시지를 출력
- Bootstrap을 통해 클라이언트 설정 및 서버 연결 요청
- EchoClientHandler의 channelActive 메소드에서 서버에 데이터를 전송
- EchoClientHandler의 channelRead 메소드에서 서버로부터 수신된 메시지를 출력
- 결과
- EchoServer

- EchoClient

○ 04_Time 예제

- 목표
- 인코더(TimeEncoder)와 디코더(TimeDecoder)를 통해 데이터를 효율적으로 변환하여 주고받는 과정을 통해 Netty에서 데이터 인코딩과 디코딩 방식의 개념을 이해
- 관련 코드
- 클라이언트 TimeClient Class

- 클라이언트 TimeClientHandler Class

- 서버 TimeServer Class

- 서버 TimeServerHandler Class

- 인코더 TimeEncoder Class

- 디코더 TimeDecoder Class

- POJO UnixTime Class

- 코드 설명
- 서버 : 클라이언트가 접속하면 현재 시간을 전송
- ServerBootstrap을 통해 서버 설정 및 클라이언트 연결 요청 대기
- TimeServerHandler의 channelActive 메소드에서 클라이언트가 접속하면 현재 시간을 전송
- TimeEncoder를 통해 데이터를 UnixTime 객체로 인코딩하여 전송
- 클라이언트 : 서버로부터 시간을 받아 출력
- Bootstrap을 통해 클라이언트 설정 및 서버 연결 요청
- TimeClientHandler의 channelRead 메소드에서 서버로부터 수신된 시간을 디코딩하여 출력
- TimeDecoder를 통해 데이터를 UnixTime 객체로 디코딩하여 처리
○ 05_LifeCycle 예제

- 목표
- 각 핸들러가 순서대로 등록, 해제, 활성화, 비활성화 되는 과정을 보면서, Netty에서 핸들러의 라이프 사이클과 이벤트 흐름을 이해
- 관련 코드
- InboundFirst Class

- OutboundFirst Class

- 서버 LifeCycleServer Class

- 코드 설명
- 서버 : 여러 인바운드 및 아웃바운드 핸들러를 통해 메시지가 처리되는 과정을 로깅
- ServerBootstrap을 통해 서버 설정 및 클라이언트 연결 요청 대기
- InboundFirst와 InboundSecond 핸들러를 통해 연결 상태와 수신 된 메시지 로깅
- OutBoundFirst와 OutBoundSecond 핸들러를 통해 송신 되는 메시지 로깅
- 클라이언트 : 서버에 메시지를 보내고 핸들러의 각 단계에서 상태를 로깅
- Bootstrap을 통해 클라이언트 설정 및 서버 연결 요청
- LifeCycleClientHandler를 통해 서버로 메시지 전송 및 각 단계에서 상태 로깅
- 서버 : 여러 인바운드 및 아웃바운드 핸들러를 통해 메시지가 처리되는 과정을 로깅
- LifeCycle
- 연결 : 핸들러 파이프 라인 등록 → 이벤트 루프 등록 → 채널 활성화

- 해제 : 채널 비활성화 → 이벤트 루프 제거 → 핸들러 파이프 라인 제거

- 메시지 송수신
- 파이프 라인 추가 순서
- InboundHandler (First → Second)
- OutBoundHandler (First → Second)
- 메시지 수신
- InboundHandler의 경우 Bottom-up 방식으로 진행
- 채널 파이프 라인의 맨 아래에 있는 핸들러부터 시작하여 맨 위에 있는 핸들러로 메시지 전달
- 위 순서로 등록된 핸들러에서 메시지 수신 시, InboundFirst → InboundSecond 순서로 처리
- 메시지 송신
- OutboundHandler의 경우 Top-down 방식으로 진행
- 채널 파이프라인의 맨 위에 있는 핸들러부터 시작하여 맨 아래에 있는 핸들러로 메시지 전달
- 위 순서로 등록된 핸들러에서 메시지 송신 시, OutboundSecond → OutboundFirst 순서로 처리
- 결과
- 메시지 수신 : InboundFirst → InboundSecond
- 메시지 송신 : OutboundSecond → OutboundFirst

○ 핸들러(인바운드, 아웃바운드, 듀플렉스 비교)
- 인바운드 핸들러 (ChannelInboundHandler)
- 역할: 수신된 데이터를 처리
- 주요 메서드
- channelRead(ChannelHandlerContext ctx, Object msg): 수신된 메시지 처리
- channelActive(ChannelHandlerContext ctx): 채널이 활성화될 때 호출
- channelInactive(ChannelHandlerContext ctx): 채널이 비활성화될 때 호출
- 아웃바운드 핸들러 (ChannelOutboundHandler)
- 역할: 송신할 데이터를 처리
- 주요 메서드
- write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise): 송신할 메시지 처리
- flush(ChannelHandlerContext ctx): 대기 중인 데이터를 네트워크로 전송
- 듀플렉스 핸들러 (ChannelDuplexHandler)
- 역할: 인바운드와 아웃바운드 이벤트를 모두 처리
- 주요 메서드
- 인바운드 메서드: channelRead, channelActive, channelInactive 등
- 아웃바운드 메서드: write, flush 등
- 특징: 인바운드와 아웃바운드 핸들러의 기능을 결합하여 단일 핸들러에서 양방향 데이터를 처리
- 사용 이유
- 코드 단순화: 인바운드와 아웃바운드 이벤트를 동일한 논리로 처리해야 하는 경우, 두 핸들러를 별도로 작성하는 것보다 단일 핸들러에서 관리하는 것이 코드 작성과 관리가 용이
- 유지보수성: 동일한 핸들러에서 양방향 이벤트를 처리함으로써 관련된 로직이 한 곳에 집중되므로 유지보수가 용이
- 상태 관리: 인바운드와 아웃바운드 처리 간에 공유 상태가 필요한 경우, 듀플렉스 핸들러를 사용하면 상태 관리가 쉬움
- 대표 사례
- 로깅 핸들러: 메시지의 수신과 송신을 모두 로깅해야 하는 경우, 듀플렉스 핸들러를 사용하여 단일 클래스에서 로깅 로직을 관리
- 트래픽 모니터링: 네트워크 트래픽을 모니터링하여 수신 및 송신 데이터를 분석하는 경우, 듀플렉스 핸들러를 통해 인바운드와 아웃바운드 트래픽을 동시에 처리 가능
- 데이터 변환: 특정 프로토콜에서 데이터를 수신하여 변환한 후 다른 프로토콜로 송신해야 하는 경우, 듀플렉스 핸들러를 사용하여 변환 로직을 통합할 수 있음
- 듀플렉스 핸들러 예시 코드
- 이 핸들러는 ChannelDuplexHandler를 상속받아 인바운드와 아웃바운드 이벤트를 모두 처리
- channelRead 메서드에서는 수신된 메시지를 로그에 기록한 후 다음 핸들러로 전달
- write 메서드에서는 송신할 메시지를 로그에 기록한 후 다음 아웃바운드 핸들러로 전달
- 이와 같이 ChannelDuplexHandler를 사용하면 인바운드와 아웃바운드 이벤트를 단일 클래스에서 처리할 수 있어 관련 로직을 통합하고 관리할 수 있음
- 이 핸들러는 ChannelDuplexHandler를 상속받아 인바운드와 아웃바운드 이벤트를 모두 처리

- 듀플렉스 핸들러 메시지 송수신 순서 결과
- 연결 순서

1. InboundFirst
2. OutBoundFirst
3. DupldexFirst
4. OutboundSecond
5. DuplexSecond
6. InboundSecond
- 결과
- 아래와 같이 메시지 수신은 인바운드 방식을 따라서 Bottom-up, 메시지 송신은 아웃바운드 방식을 따라서 Top-down 방식으로 진행됨을 알 수 있음

- 메시지 수신 순서(Bottom-up)
- InboundFirst
- DuplexFirst
- DuplexSecond
- InboundSecond
- 메시지 송신 순서 (Top-down)
- DuplexSecond
- OutboundSecond
- DuplexFirst
- OutboundFirst
'SPRING&JAVA' 카테고리의 다른 글
[Netty] ChannelHandlerContext.close()와 Channel().close() 차이 (0) | 2024.08.25 |
---|---|
[Netty] ByteBuf와 메모리 관리: 참조 카운트, 자동 관리, channelRead vs channelRead0 (0) | 2024.07.28 |
Amazon SQS와 Lambda를 활용한 서버리스 활용 및 성능 개선기(feat. 람다에서 비동기 코드 사용시 주의하기) (0) | 2024.04.05 |
스프링 배치 - 3편 (간단한 성능 개선기, JPA) (0) | 2024.03.30 |
스프링 배치 - 2편(DB) (0) | 2024.03.25 |
Comments