목차
네티서버에 필요한 요소
-
ChannelHandler : 데이터 변환, 검증, 로깅, 업무 처리 등을 단계별로 맡는다.
-
ServerBootstrap : 서버 채널과 자식 채널의 설정을 조립하고 포트에 바인딩한다.
Netty networking 대표적인 추상화 클래스
- Channel : 소켓(Socket)
- Channel 인터페이스는 Socket으로 직접 작업할 때의 복잡성을 완화하는 API를 제공하고 미리정의된 클래스 계층의 루트임
1-1. 종류
-
EmbeddedChannel
-
LocalServerChannel
-
NioDatagramChannel
-
NioSctpChannel
-
NioSocketChannel
- EventLoop :흐름제어, 멀티스레딩, 동시성 제어
- 연결의 수명주기중 발생하는 이벤트를 처리하는 핵심 추상화를 정의함
2-1. 관계
-
EventLoop, Thread, EventLoopGroup
-
EventLoopGroup은 하나 이상의 EventLoop를 포함한다.
-
EventLoop는 수명주기동안 한 Thread를 바인딩한다.
-
EventLoop에서 처리되는 모든 입출력 이벤트는 해당 전용 스레드에서 처리됨
-
Channel은 수명주기동안 EventLoop에 등록할 수 있다.
-
EventLoop를 하나 이상의 Channel로 할당할 수 있다.
ex) 과정
-
EventLoop 4개가 들어있는 EventLoopGroup
-
EventLoopGroup에 들어있는 EventLoop를 이용
-
Channel 생성
-
EventLoop를 Channel에 등록
-
EventLoop의 전체 수명주기 중 입출력을 처리
- ChannelFuture : 비동기알림
- 비동기는 작업이 즉시 반환되지 않을 수 있기 때문에 나중에 결과를 확인하는 방법이 필요
ChannelFutre는 미래에 실행될 작업의 결과를 알려준다.
- ChannelHandler와 ChannelPipeline
4-1. ChannelHandler 인터페이스
- 인바운드 아웃바운드 데이터의 처리에 적용되는 모든 애플리케이션 논리 컨테이너 역할을 한다.
4-2. ChannelHandler의 Adapter 종류
-
ChannelHandlerAdapter
-
SimpleChannelInboundHandler<T>
-
ChannelInboundHandlerAdapter
-
ChannelOutboundHandlerAdapter
-
ChannelDuplexHandlerAdapter
4-3. ChannelPipeline 인터페이스
- ChannelHandler 체인을 위한 컨테이너를 제공하고, 체인상에서 인바운드와 아웃바운드 이벤트를 전파하는 API를 정의한다.
- 디코더, 인코더
- 메시지를 수신하거나 전송할때 데이터를 변환하는 것.
인바운드 메시지는 바이트에서 다른 포맷(자바객체등)으로 변환되는 디코딩을 거친다
아웃바운드 메시지는 애플리케이션 객체에서 바이트로 인코딩된다.
5-1. 종류
바이트 -> 메시지 : ByteToMessageDecoder, MessageToByteEncoder
구글 프로토콜 : ProtobufEncoder, ProtobufDecoder
- SimpleChannelInboundHandler 추상클래스
- 기본 클래스의 메서드를 하나 이상 정의하고 모든 핸들러 메서드에 입력인수로 전달되는 ChannelHandlerContext에 대한 참조를 얻는 것.
(현재 입출력 스레드를 블로킹 하지 않아야함)
- 부트스트랩(Bootstrap)
- 프로세스를 지정된 포트로 바인딩하거나 지정된 호스트, 포트에서 실행중인 다른 호스트로 연결하는 일을 하는 애플리케이션의 네트워크 레이어를 구성하는 컨테이너
- BootStrap: 원격 피어로 연결해야하므로 client에서 정의
-
네트워크 기능 : 원격 호스트와 포트로 연결
-
EventLoopGroup의 수 : 1개
- ServerBootstrap: 연결요청을 수신해야하므로 server에서 정의
-
네트워크 기능 : 로컬 포트로 바인딩
-
EventLoopGroup의 수 : 2개
한 요청이 여러 컴포넌트를 통과하는 예
실제로는 서버를 ServerBootstrap으로 바인딩한 뒤, 수락된 연결마다 자식 Channel이 생긴다. Channel은 한 EventLoop에 등록되고, 그 채널의 ChannelPipeline이 수신 메시지를 핸들러 순서대로 처리한다.
flowchart TD A["ServerBootstrap.bind()"] --> B["서버 Channel"] B --> C["연결 수락"] C --> D["자식 Channel + EventLoop"] D --> E["프레임 디코더"] E --> F["메시지 디코더"] F --> G["업무 ChannelHandler"] G --> H["인코더"] H --> I["상대에게 응답"]
ChannelFuture는 bind, connect, write 같은 비동기 작업의 완료를 알려 준다. Future가 생성되었다는 사실만으로 작업이 성공한 것이 아니다. 서버 시작 코드에서는 바인딩 성공을 확인하고, 핸들러 안에서는 필요에 따라 완료 리스너로 실패를 기록한다. EventLoop 안에서 외부 작업이 끝날 때까지 블로킹하면 같은 루프가 맡은 다른 채널도 지연된다.
디코더의 첫 역할은 메시지 경계를 정하는 것이다. TCP는 바이트 스트림이므로 길이 필드나 줄바꿈 같은 규칙 없이는 완성된 메시지를 판단할 수 없다. 다음 역할은 바이트를 의미 있는 타입으로 바꾸는 것이다. 바이너리 프로토콜이라면 길이 제한, 숫자 범위, 인코딩, 버전 필드 검증까지 해야 한다. 입력 검증을 업무 핸들러에서 뒤늦게 하면 이미 큰 버퍼를 할당했거나 잘못된 객체를 만들 수 있다.
| 구성 요소 | 잘못됐을 때의 증상 | 확인할 내용 |
|---|---|---|
ServerBootstrap | 시작 로그만 있고 접속 불가 | bind의 Future, 주소, 포트 |
| EventLoop | 모든 연결의 지연 증가 | 핸들러의 블로킹 코드와 작업 시간 |
| 프레임 디코더 | 메시지가 합쳐지거나 잘림 | 송수신 양쪽의 경계 규칙 |
| 인코더 | 보낸 쪽에서 응답을 해석 못함 | 필드 순서, 길이, 문자 인코딩 |
| ByteBuf | 메모리 누수 또는 이미 해제된 버퍼 오류 | 소비·전달·해제의 소유자 |
SimpleChannelInboundHandler<T>는 소비한 메시지를 자동 해제할 수 있다. ChannelInboundHandlerAdapter를 쓰면 버퍼를 직접 해제하거나 다음 핸들러로 넘기는 책임을 명확히 해야 한다. 이 규칙은 기능 테스트에서는 숨어 있다가 장시간 부하에서 메모리 문제로 나타날 수 있으므로 두 핸들러의 선택을 의식해야 한다.