[Netty] Netty란?

Welcome to Seoyun Dev Log·2023년 6월 25일
1

[네트워크] Netty

목록 보기
1/2

Netty

자바 네트워크 애플리케이션 프레임워크.
비동기 이벤트 기반 네트워크 응용프로그램 프레임워크

사용하는 이유

  • 자바 소켓 프로그래밍에 비해 네티의 추상화로 인해 편하고 간결하게 네트워크 프로그래밍 가능
  • 추상화
    • 네티는 소켓의 모드와 상관없이 개발할 수 있도록 추상화된 전송 API를 제공. 따라서 소켓 모드를 바꾸기 위해서 데이터 송수신 부분의 로직을 고치지 않아도 된다.
  • 안정적
  • 빠른 속도

특징

  • 비동기
  • 이벤트 기반
  • 고성능
  • 추상화

1. 데이터 이동의 방향성

  • 🚩 Inbound 이벤트: 클라이언트로부터 데이터 수신할 때 발생하는 이벤트에 관심
    • Inbound 이벤트 중 하나인 "데이터 수신 이벤트"를 담당하는 메서드에 로직 넣기
  • 🚩 Outbound 이벤트:
  • 데이터 송수신
    • 데이터 송신 -> 아웃바운드 이벤트
    • 데이터 수신 -> 인바운드 이벤트

2. 이벤트 기반 프로그래밍

이벤트를 정의하고 이벤트가 발생했을 떄 실행될 코드를 준비해둔다.
논 블로킹 소켓의 Selector를 사용한 I/O 이벤트 감지 및 처리도 이 종류 중 하나이다.

이벤트를 발생시키는 객체와 발생될 이벤트의 종류를 정의
네트워크 프로그램에서 이벤트 발생의 주체는 소켓이며 이벤트 종류는 소켓 연결, 데이터 송수신이다.

  • 추상화 수준
    • 이벤트 추상화가 너무 고수준이면 세부적 제어가 힘들고, 저수준이면 한 동작에 대해 너무 많은 이벤트가 발생하여 성능에 악영향
    • 서버에 연결될 클라이언트의 수는 매우 가변적이기 때문에 예측이 불가능하다. 이런 관점에서 서버에서 사용하는 이벤트 기반 프레임워크의 적절한 추상화 단위는 매우 중요

소켓 통신을 이용해서 클라이언트와 서버의 연결을 유지한다
netty에서는 소켓통신을 하기 때문에 IP, port 번호가 필요하다.

  1. Bootstrap을 이용해서 서버와 클라이언트간 연결 시도
    : 클라이언트와 서버는 초기 설정시 bootstrap, 이벤트처리 쓰레드 설정이 다르다.
  • 서버: serverBootstrap 이벤트 처리 스레드 2개 생성하여 클라이언트에 대한 연결과 데이터처리용으로 2개 생성
  • 클라이언트: bootstrap 이벤트 처리 스레드 1개

데이터를 송수신 할 때 사용되는 통로인 channelContextHandler 인터페이스 클래스 사용.
클라이언트와 서버간의 송수신은 bootstrap을 이용하여 연결이 수립된 뒤 부터 channelContextHandler 클래스를 이용하여 데이터를 송수신할 수 있음.

channelContextHandler 클래스에는 데이터가 오가는 Channel 클래스가 포함되어있는데 이 채널에 클래스에 데이터가 들어오는 인바운드, 데이터가 나가는 아웃바운드 설정을 할 수 있다.

  1. 채널 설정방법
    channel.channel().pipeline().addLast( "핸들러 이름" , 핸들러를 상속받은 클래스 );

bootstrap
: netty의 스레드를 생성하고, 소켓을 오픈하는 등 netty를 구동하기 위한 부트 스트래핑을 위해 사용하는 클래스

EventLoopGroup
: netty의 eventLoopGroup은 eventLoop들의 그룹이다. 여러개의 이벤트 루프는 하나의 그룹으로 모아질 수 있다. 같은 그룹에 속한 이벤트 루프들은 스레드와 같은 몇몇 리소스들을 공유하게 된다.

EventLoop
: netty의 eventLoop은 새로운 이벤트를 반복적으로 확인하는 루프이다. 예를 들어 soketChannel로부터 새로운 데이터가 들어오는 것 같은 이벤트를 매번 확인한다.
이벤트가 발생하면 적당한 이벤트 핸들러에 전달.

socketChannel
: netty의 소켓채널은 tcp 연결을 대표한다. 네트워크 프로그램에서 server나 clinet가 netty를 사용한다면 머신 사이에서 데이터를 전달하는 과정은 소켓 채널을 통해서 이루어진다.

socketChannel은 항상 같은 eventLoop에 의해 관리가 된다. 같은 eventLoop은 항상 같은 스레드에서 실행되기 때문에 socketChannel은 항상 같은 스레드에서 접근이 된다.

이 때문에 순서가 보장된다.
다라서 같은 소켓 채널에서 동시에 데이터가 읽히는 것에 대해서는 걱정하지 않아도 된다.

channelInitializer
: 네티의 channelInitializer는 socketChannel이 생성될 때 channelPipeline에 추가되는 특별한 channelHandler다. 이 객체는 socketChannel을 초기화하는 역할

socketChannel이 초기화되면 channelPipeline에서 channelInitializer가 제거된다.

channelPipeline
: 각각의 socketChannel은 channelPipeline을 가지고 있다.
채널파이프라인은 channelHandler 인스턴스의 리스트이다.
eventLoop가 데이터를 socketChannel에서 읽으면 데이터는 파이프라인에 있는 첫번째 채널 핸들러에게 넘겨진다. 첫 번째 핸들러는 넘겨받은 데이터를 처리하고 필요한 경우 파이프라인의 다음 핸들러로 데이터를 넘길 수 있다

데이터를 socketChannel로 쓰는 경우도 마찬가지로 channelPipeline을 타게되며 핸들러들을 거친다음 socketChannel로 쓰여지게 된다.

https://hbase.tistory.com/116


Channel

네트워크 연결을 나타내며 NIO의 SelectableChannel을 기반으로 구현되어있다.
채널을 사용하여 네트워크 연결을 설정하고 데이터를 읽고 쓰고 이벤트를 수신하고 속성을 설정할 수 있다.

제공 기능

  • 읽기/쓰기 작업을 수행
  • 이벤트를 수신
  • 연결을 설정하고 종료
  • 속성을 설정

주요 메서드

  • read(): 읽기 작업을 수행
  • write(): 쓰기 작업을 수행
  • register(): 이벤트를 수신할 수 있도록 채널을 등록
  • connect(): 연결을 설정
  • close(): 연결을 종료
  • config(): 속성을 설정

ChannelOption (netty) : option - 서버 소켓 채널의 소켓 옵션 설정
: 소캣의 동작 방식 지정, 소켓의 송수 신 패킷 크기 설정

TCP_NODELAY : 데이터 송수신에 네이글 알고리즘 비활 성화 여부 지정
SO_KEEPALIVE : 운영체제에서 지정된 시간에 한번씩 keepalive 패킷을 상대 방에게 전송
SO_BACKLOG : 동시에 수용 가능한 소켓 연결 요청수
TCP_NODELAY: Nagle 알고리즘 비활성화 여부 설정: 네트워크 통신에서 발생하는 작은 패킷들을 하나의 큰 패킷으로 묶어서 전송함으로써 효율성을 향상
SO_SNDBUF: 커널 송신 버퍼 크기
SO_RCVBUF: 커널 수신 버퍼 크기
SO_REUSEADDR: TIME_WAIT 상태의 포트에도 bind 가능해짐
SO_LINGER: 소켓을 닫을 때 송신 버퍼에 남은 데이터 전송 대기 시간

ChannelHandler

Netty의 핵심 요소
Netty의 I/O 이벤트를 처리하는 인터페이스
ChannelInboundHandlerAdapter
ChannelOutboundHandlerAdapter

ChannelHandlerContext

ChannelHandler 는 ChannelHandlerContext 를 통해
다음 ChannelHandler 에게 이벤트를 넘기거나,
동적으로 ChannelPipeline 를 변경할 수 있음

각 이벤트 핸들러당 하나씩 생성됩니다. 이벤트 핸들러는 Netty의 파이프라인을 통해 연결된 Channel로 들어오는 이벤트를 처리하고, ChannelHandlerContext를 통해 해당 Channel과 관련된 작업을 수행합니다.

ChannelHandlerContext의 인스턴스는 Netty의 파이프라인을 통해 자동으로 생성되며, 이벤트 핸들러가 파이프라인에 추가될 때마다 생성됩니다. 각 이벤트 핸들러에 대해 ChannelHandlerContext는 해당 핸들러와 관련된 Channel 및 파이프라인 정보를 갖고 있습니다.

SimpleChannelInboundHandler

네티는 해제되지 않은 리소스를 WARN 수준 로그 메시지로 로깅하므로 코드에 문제가 되는 인스턴스가 있으면 쉽게 발견할 수 있다
그러나 이렇게 매번 리소스를 관리하기 번고웁기 때문에 해당 핸드퍼를 사용하면 쉽게 리소스를 관리할 수 있다.

리소스를 자동으로 해제하므로 메시지의 참조도 무효화된다. 즉 메시지의 참조를 저장해 나중에 사용하려고 하면 안된다.

메시지가 ChannelRead0()에서 소비되면 자동으로 메시지를 해제

ChannelDuplexHandler


: ChannelHandler는 들어온 이벤트를 처리하는 ChannelInboundHandler와 나가는 이벤트를 처리하는 ChannelOutboundHandler가 있고, ChannelDuplexHandler는 나가는것과 들어오는것 이벤트를 두개다 처리하는 핸들러

  • userEventTriggered()
    : 채널에 특정 이벤트가 발생할 때마다 호출된다. 이 메서드의 인수에는 이벤트가 포함된다
    해당 메서드를 사용하여 채널에 발생하는 특정 이벤트에 대해 작업을 수해할 수 있는데 채널이 연결되었을 때 특정 작업을 수행하거나 채널이 연결이 끊겼을 때 다른 작업을 수행할 수 있다.

pipeline

채널의 이벤트를 처리하는 담당

채널에 연결된 핸들러의 목록을 포함하고 핸들러는 채널이 발생하는 이벤트를 처리한다

제공 기능

  • 이벤트 처리를 순차적으로 수행
  • 이벤트를 전달
  • 핸들러는 추가하고 제거
  • 핸들러의 순서를 변경

주요 메서드

  • addLast(): 핸들러 추가
  • removeLast(): 핸들러 제거
  • fireChannelRegistered(): 채널이 등로되었을 때 이벤트를 발생
  • fireChannelUnregistered(): Channel이 등록 해제되었을 때 이벤트를 발생시킵니다.
  • fireChannelActive(): Channel이 활성화되었을 때 이벤트를 발생시킵니다.
  • fireChannelInactive(): Channel이 비활성화되었을 때 이벤트를 발생시킵니다.

ServerBootstrap

네티에서 서버를 생성하고 시작하는데 사용되는 클래스로 서버를 생성하고 시작할 때 지정된 포트에서 서버를 바인드하거나 지정된 주소로 연결할 수 있다.
또한 서버에 핸드러를 추가하고 속성을 설정할 수 있다.

  • group(): EventLoopGroup를 가져옵니다.
  • bind(): 지정된 포트에서 서버를 바인드합니다.
  • bind(int port): 지정된 포트에서 서버를 바인드합니다.
  • bind(SocketAddress address): 지정된 주소에서 서버를 바인드합니다.
  • bind(SocketAddress address, ChannelHandler... handlers): 지정된 주소에서 서버를 바인드하고, 지정된 핸들러를 추가합니다.
  • connect(): 지정된 주소로 연결합니다.
  • connect(SocketAddress address): 지정된 주소로 연결합니다.
  • connect(SocketAddress address, ChannelHandler... handlers): 지정된 주소로 연결하고, 지정된 핸들러를 추가합니다.
  • disconnect(): 연결을 끊습니다.
  • close(): 서버를 종료합니다.
  • sync(): 서버를 시작하거나 연결을 완료할 때까지 블로킹합니다.
  • awaitUninterruptibly(): 서버를 시작하거나 연결을 완료할 때까지 블로킹합니다.
  • group(): EventLoopGroup를 가져옵니다.
  • channelFactory(): ChannelFactory를 가져옵니다.
  • childHandler(): 채널에 추가할 핸들러를 가져옵니다.
  • childOption(): 채널에 설정할 옵션을 가져옵니다.
  • childAttr(): 채널에 설정할 속성을 가져옵니다.
  • option() 메서드는 서버 소켓의 옵션을 설정합니다.
  • childOption() 메서드는 서버 소켓에서 생성되는 클라이언트 소켓의 옵션을 설정합니다.
  • handler와 childHandler의 차이
    The main difference between the handler and childHandler methods in Netty's ServerBootstrap class is that the handler method is used to configure the pipeline for the bootstrap itself, while the childHandler method is used to configure the pipeline for each individual client connection that is accepted by the server.

In other words, the handler method is used to configure the handlers that will be executed for all events that occur on the bootstrap itself, such as when the bootstrap is started or when it encounters an error. The childHandler method, on the other hand, is used to configure the handlers that will be executed for all events that occur on each individual client connection, such as when a message is received from the client or when the connection is closed.

Here is an example of how the handler and childHandler methods can be used to configure a Netty server:

ServerBootstrap bootstrap = new ServerBootstrap();

// Configure the pipeline for the bootstrap
bootstrap.handler(new LoggingHandler());

// Configure the pipeline for each client connection
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        // Add a logging handler to the pipeline
        ch.pipeline().addLast(new LoggingHandler());

        // Add an echo handler to the pipeline
        ch.pipeline().addLast(new EchoHandler());
    }
});

In this example, the handler method is used to add a LoggingHandler to the pipeline for the bootstrap. This handler will log all events that occur on the bootstrap, such as when the bootstrap is started or when it encounters an error. The childHandler method is then used to add a ChannelInitializer to the pipeline for each client connection. This ChannelInitializer will add a LoggingHandler and an EchoHandler to the pipeline for each client connection. The LoggingHandler will log all events that occur on the client connection, and the EchoHandler will echo back any messages that it receives from the client.

I hope this helps! Let me know if you have any other questions.

IdleStateEvent

: 채널이 특정 시간동안 활동이 없을 때 발생하는 이벤트로 이벤트는 채널이 연결되어 있지만 데이터가 수신되지 않거나 전송되지 않을 때 발생

  • ReaderIdleEvent: 채널이 읽기 작업을 수행하지 않았을 때 발생합니다.
  • WriterIdleEvent: 채널이 쓰기 작업을 수행하지 않았을 때 발생합니다.
  • AllIdleEvent: 채널이 읽기 작업과 쓰기 작업을 모두 수행하지 않았을 때 발생합니다.

BOSS / WORKER

  • BOSS

    • boss 그룹은 클라이언트의 연결을 수락하는 부모 스레드 그룹 이고 port 가 두개 있으면 두개의 부모 쓰레드가 생성
    • boss 그룹이 1 이면 하나의 스레드가 두 개의 포트를 listen
  • WORKER

    • worker 그룹은 연결된 클라이언트 소켓으로부터 데이터 입출력(I/O) 및 이벤트처리를 담당하는 자식 쓰레드 그룹

ChannelHandlerContext writeAndFlush()

Channel은 ChannelPipeline으로 메시지를 전달한다. ChannelPipeline은 기본적으로 TailContext와 HeadContext를 가진다. Pipeline의 시작과 끝이라 할 수 있다. Tail과 Head 사이에는 사용자가 등록한 ChannelHandlerContext가 체인 구조로 연결되고, 전달된 메시지가 체인을 따라 Outbound 방향으로 흘러간다.


bootstrap 메서드

  • bootstrap.bind( port ).sync();
    : socket 주소와 port binding

Future

비동기 작업의 결과를 나타내는 개념으로 비동기 작업이 완료되었는지 여부를 체크하고 비동기 작업의 결과를 가져올 수 있다

제공 기능

  • 비동기 작업의 완료 여부
  • 비동기 작업의 결과
  • 비동기 작업 취소

주요 메서드

  • isSuccess(): 비동기 작업이 성공적으로 완료되었는지 여부를 확인합니다.
  • isCanceled(): 비동기 작업이 취소되었는지 여부를 확인합니다.
  • get(): 비동기 작업의 결과를 가져옵니다.
  • get(long timeout, TimeUnit unit): 비동기 작업의 결과를 지정된 시간 동안 대기하고, 결과를 가져옵니다.
  • cancel(): 비동기 작업을 취소합니다.

ChannelFuture

closeFuture = channelFuture.channel().closeFuture();

closeFuture()
: 채널의 CloseFuture를 얻고 완료 될때 까지 현재 스레드를 블로킹한다.


수신

(클라이언트)
1. bootstrap 생성
2. eventLoopGroup 생성
3. bootstrap 체이닝을 통한 그룹, 옵션, 핸들러 설정
4. bootstrap.connect(ip, port) : 연결 부분

하나의 부트스트랩에 하나의 이벤트 처리 스레드를 설정한다.
수신된 데이터는 inboundHandler를 상속받은 클래스에서 decode() 동작
decode() 메소드를 동작시킨 뒤 나온 데이터는 channelRead()메소드 동작


송신

  1. ChannelHandlerContext 이용해서 메시지를 상대방에게 전송하기 전 보낼 메시지의 길이만큼 ByteBuf에 할당해준 뒤 ByteBuf 객체에 그 만큼의 메시지 내용을 싣는것을 확인할 수 있다.
  2. ChannelHandlerContext 메소드를 사용하여 바이트버프 객체를 전송한다.
    해당 객체의 writeAndFlush(Object obj) 보내는 것

참고

profile
하루 일지 보단 행동 고찰 과정에 대한 개발 블로그

0개의 댓글

관련 채용 정보