부트스트랩
부트스트랩이란
네티 애플리케이션 작성할 때 가장 기본이 되는 애플리케이션의 수행 동작과 설정을 지정할 수 있다.
설정할 수 있는 요소들은 다음과 같다.
전송 계층 (소켓 모드 및 I/O 종류)
이벤트 루프 (단일/다중 스레드)
채널 파이프라인 설정
소켓 주소와 포트
소켓 옵션
프로토콜
소켓 요청을 보내는 서버 애플리케이션을 위한 ServerBootstrap과 소켓 대기하는 클라이언트 애플리케이션을 위한 Bootstrap이 존재한다. AbstractBootstrap에서는 두 애플리케이션에서 공통된 작업을 수행하는 기능을 제공한다.
부트스트랩 클래스들은 모두 Cloneable 인터페이스를 구현하고 있는데, 이는 여러 채널을 비슷하거나 동일한 설정으로 생성해야 할 때
clone()
메서드를 이용해 즉시 동일한 내용을 가진 다른 객체를 생성하기 위함이다.네트워크 애플리케이션의 성능은 부트스트랩의 설정을 잘하냐에 따라 달라지기보다는, 애플리케이션 성격에 따라 알맞게 설정해야 좋다.
클라이언트의 요청에 따라 DB 질의 결과를 돌려주는 애플리케이션은 네트워크 처리량보다 DB 질의 결과를 대기하는 시간이 더 길 것이다.
반면 캐시 서버에서 데이터를 조회하여 전송하는 애플리케이션은 데이터 조회 시간보다 네트워크 입출력이 많은 서비스이므로 NIO, Epoll 입출력을 사용하여 더 나은 성능을 낼 수 있다.
ServerBootstrap
ServerBootstrap 클래스는 서버 애플리케이션에 대한 수행 동작과 설정을 지정할 수 있도록 여러 메서드를 제공한다.
아래는 ServerBootstrap을 사용하는 예제이다.
bind 메서드가 호출되면
ServerChannel
이 생성되고,ServerChannel
은 다수의자식 Channel
을 관리하게 된다.
group
클라이언트로부터 연결이 완료된 후 데이터 송수신 처리를 위해 하나의 이벤트 루프를 사용한다.
연결 요청 수락을 위한 루프 그룹과 데이터 송수신 처리를 위한 루프 그룹을 따로 지정할 수 있다.
bossGroup: 클라이언트의 연결을 수락하는 역할 (위 예제 코드에서는 단일 스레드로 동작하도록 했다.)
workerGroup: 클라이언트 소켓과 연결된 소켓의 데이터 송수신 및 이벤트 처리를 담당 (위 예제 코드에서는 하드웨어 CPU 코어 * 2개의 스레드로 동작하도록 했다.)
channel
서버 소켓이 사용할 네트워크 입출력 모드를 설정한다.
부트스트랩 클래스를 통해 생성된 채널의 입출력 모드를 설정할 수 있다.
channel 메서드에 등록된 소켓 채널 생성 클래스가 소켓 채널을 생성한다.
설정가능한 클래스 목록
Channel과 EventLoopGroup는 동일한 모드로 설정해주어야 한다. 만약 그렇지 않다면 호환성이 깨져 IllegalStateException이 발생할 것이다.
LocalServerChannel
하나의 JVM에서 가상 통신을 위한 서버 소켓 채널 생성
OioServerSocketChannel
블로킹 모드의 서버 소켓 채널 생성
NioServerSocketChannel
논블로킹 모드의 서버 소켓 채널 생성
EpollServerSocketChannel
리눅스 커널의 epoll 입출력 모드를 지원하는 서버 소켓 채널 생성
OioDatagramSocketChannel
비연결 프로토콜에 사용할 수 있는 데이터그램 채널 생성
connect 메서드를 호출하지 않고 bind 메서드만 호출한다.
handler
서버 소켓 채널의 이벤트를 처리할 핸들러를 설정한다.
ChannelHandler 인터페이스를 구현한 클래스를 지정해야 한다.
데이터 송수신에 대한 이벤트는 여기서 처리되지 않고, childHandler에서 등록된 핸들러에 의해 처리된다.
LoggingHandler를 등록하여 이벤트 루프에 등록, 포트 바인딩, 포트 활성화, 클라이언트 접속 등의 로그를 남길 수 있다.
childHandler
자식 채널의 초기화 방법을 지정한다.
클라이언트 소켓 채널로 송수신되는 데이터를 가공하는 데이터 핸들러를 설정한다.
서버에 연결된 클라이언트 소켓 채널에서 발생하는 이벤트를 수신하여 처리한다.
서버 소켓 채널로 연결된 클라이언트 채널에 파이프라인을 설정하는 역할을 수행할 수 있다.
위 코드에서 childHandler로 지정된 ChannelInitializer는 클라이언트로부터 연결된 채널이 초기화될 때의 기본 동작이 지정된 추상 클래스이다. 채널 파이프라인에 handler를 등록해 클라이언트의 연결이 생성되었을 때 데이터 처리를 담당하도록 한다.
채널 파이프라인에는 ChannelHandler 인터페이스를 구현한 클래스를 핸들러로 추가할 수 있다.
여러 개의 ChannelHandler를 추가할 수도 있다.
여러 프로토콜을 지원해야하는 복잡한 애플리케이션의 경우 여러 ChannelHandler를 이용하는 것이 편리하다.
ChannelInboundHandlerAdapter#initChannel 메서드를 사용하여 여러 ChannelHandler를 ChannelPipeline에 추가할 수 있다.
option
서버 소켓 채널의 소켓 옵션(동작 방식)을 설정한다.
부트스트랩에서 생성하는 모든 채널에 자동으로 소켓 옵션을 적용하여 커널에서 해당 옵션이 사용되도록 한다.
애플리케이션에서 socket.send() 호출 시 커널의 시스템 함수를 호출해 애플리케이션에서 수신한 데이터를 데이터 송신용 커널 버퍼에 쌓아두었다가 인터넷으로 전송한다.
이렇게 전달된 데이터는 데이터 수신용 커널 버퍼에 쌓이고, 애플리케이션에서 socket.read() 호출 시 데이터를 읽을 수 있다.
네티는 JVM 기반으로 동작하므로 자바에서 설정할 수 있는 소켓 옵션을 모두 사용 가능하다.
주요 소켓 옵션은 다음과 같으며, 책 출간 시점과 현재의 JDK 소켓 옵션에 차이가 있을 수 있다.
애플리케이션 서버의 강제 종료 또는 비정상 종료로 인해 재시작하는 상황에서, 사용하던 포트의 상태가 TIME_WAIT라면 bind가 실패할 수 있다. 이 경우를 방지하기 위해 SC_REUSEADDR를 활성화할 수 있다.
SC_BACKLOG 값이 너무 작으면 클라이언트가 연결 생성을 못할 수 있으며, 너무 크면 클라이언트 연결 요청이 폭주할 때 대기 시간이 길어져 클라이언트에서 타임아웃이 발생할 수 있다.
아래와 같이 소켓 옵션을 지정할 수 있다.
childOption
클라이언트 소켓 채널의 옵션을 설정한다.
SO_LINGER 옵션을 사용하면, 커널 버퍼의 데이터를 모두 상대방에 전송하고 ACK을 기다린다. 그리고 타임아웃 값을 0으로 주어 포트 상태가 TIME_WAIT로 전환하는 것을 방지할 수 있다.
attr
ServerChannel의 attribute를 적용하기 위한 메서드로, bind 메서드가 호출될때 실제로 설정된다.
일반적인 속성과 데이터 중 일부를 이용할 수 없는 경우 AttributeMap, AttributeKey를 사용할 수 있도록 제공한다.
childAttr
클라이언트 소켓 채널에 attribute를 적용한다.
Bootstrap
비연결 프로토콜을 이용하는 애플리케이션이나 클라이언트에서 이용되는 클래스이다.
클라이언트 애플리케이션에 대한 수행 동작과 설정을 지정하는 메서드를 제공한다.
bind나 connect 메서드를 호출하기 전 group, channel, handler 메서드를 반드시 호출해 필수 컴포넌트를 설정해야 한다.
클라이언트에서 사용하는 단일 소켓 채널에 대한 설정이므로 부모와 자식 관계의 설정은 없다. 따라서 ServerBootstrap과 달리 childHandler, childOption 등의 메서드가 없다.
대부분의 API는 ServerBootstrap API와 유사하다.
connect / bind
일반적인 TCP 연결의 경우 원격 피어에 연결하는 connect 메서드를 사용하고, UDP와 같은 비연결형 통신을 할 경우 채널을 생성하고 바인드하는 bind 메서드를 사용하기를 권장한다.
localAddress
채널이 바인딩될 로컬 주소를 지정한다. 이를 지정하지 않으면 운영체제에서 임의의 주소를 생성한다.
group
소켓 채널의 모든 이벤트 처리를 위한 이벤트 루프 객체를 설정한다.
클라이언트 애플리케이션은 서버에 연결한 소켓 채널 하나만 가지기 때문에 단 하나의 이벤트 루프만 설정할 수 있다.
channel
클라이언트 소켓 채널의 입출력 모드를 설정한다.
handler
클라이언트 소켓 채널에서 발생하는 이벤트에 대한 알림을 받고 처리하는 핸들러를 설정할 수 있다.
ChannelInitializer 클래스를 통해 등록할 수 있다.
option
클라이언트 소켓 채널의 소켓 옵션을 설정한다. 사용하는 채널 유형에 따라 옵션도 다르게 구성되며, bind / connect 메서드에 의해 채널의 옵션이 설정된다.
채널이 이미 생성된 후에는 영향을 미치지 않는다.
부트스트랩의 이벤트 루프 공유
서버가 다른 시스템에 대해 클라이언트처럼 동작해야 하는 경우 ServerChannel도 두고 클라이언트 Channel도 두어야 한다.
새로운 Bootstrap을 만들어 이를 해결할 수 있지만, 새로운 클라이언트 채널에 대한 EventLoop를 정의해야 하므로 새로운 스레드가 필요하다. 이 경우 ServerBootstrap의 자식 채널과 클라이언트 채널 간 데이터 교환 시 컨텍스트 스위칭이 필요하다.
자식 채널의 EventLoop를 부트스트랩의 group 메서드로 전달해 공유하면 컨텍스트 스위칭을 방지할 수 있다.
EventLoop를 최대한 재사용하여 스레드 생성 비용을 줄이는 것이 네티 애플리케이션 가이드에서 권장되고 있다.
아래 그림과 같이 ServerChannel으로 생성된 자식 채널의 EventLoop를 클라이언트로써의 통신을 위해 생성한 Bootstrap과 채널에서 공유할 수 있다.
아래는 EventLoop를 공유하는 예시 코드이다. ChannelInboundHandler에서 새로운 부트스트랩을 만들고 이벤트 루프는 기존의 이벤트 루프를 사용한다.
종료
애플리케이션 종료 시 부트스트랩도 리소스를 모두 해제하여 정상적으로 종료시켜주어야 한다.
EventLoopGroup을 종료해 대기중인 이벤트와 작업을 모두 처리한 후 모든 활성 스레드를 해제해야 한다.
shutdownGracefully 메서드를 제공하여 비동기로 종료 작업을 진행한다.
아래는 graceful하게 종료하는 예제이다.
Last updated