IT

Netty 전송 API

코딩하는 너구리 2020. 12. 28. 23:07
반응형

Ch04. 전송 API

네트워크를 통해 전송되는 데이터는 모두 바이트 형식이다.

 

네티는 모든 전송 구현에 공통 API를 기반 레이어로 활용하므로 JDK를 직접 이용할 때보다 블로킹-논블로킹 변환 작업이 훨씬 간단하게 이루어진다.

코드가 구현 세부사항으로 오염될 우려가 적고, 전체 코드 기반을 광범위하게 리팩터링할 필요가 없다.

 

 

연결을 수락한 다음, 클라이언트로 "Hi!" 라는 메시지를 전송한 뒤 연결을 닫는 간단한 애플리케이션을 작성해보자.

NettyNioServer

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.CharsetUtil;

import java.net.InetSocketAddress;
import java.nio.charset.Charset;

public class NettyNioServer {
    public static void main(String[] args) throws Exception{
        int port = 8888;
        final ByteBuf buf = Unpooled.copiedBuffer("Hi! \n", Charset.forName("UTF-8"));
        EventLoopGroup group = new NioEventLoopGroup();    // 논블로킹 모드를 위해 NioEventGroup을 이용
        try {
            ServerBootstrap b = new ServerBootstrap();    // ServerBootstrap 생성
            b.group(group).channel(NioServerSocketChannel.class)
                    .localAddress(new InetSocketAddress(port))
                    .childHandler(new ChannelInitializer<SocketChannel>() {    // 연결이 수락될 때마다 호출될 ChannelInitializer를 지정
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){    // 이벤트를 수신하고 처리할 ChannelInboundHandlerAdapter를 추가
                                @Override
                                public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                    ctx.writeAndFlush(buf.duplicate())
                                        .addListener(ChannelFutureListener.CLOSE);
                                }
                            });
                        }
                    });
            ChannelFuture f = b.bind().sync();
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }
}

 

일반적으로 Netty를 사용하지 않은 구현에 비해 가독성이 높고, 간편하다는 것을 알 수 있다.

모든 구현들이 Channel, ChannelPipeline, ChannelHandler를 기준으로 정의되고 있음을 기억해두자.

 


 

 

Channel 인터페이스는 모든 입출력 작업에 이용되므로 전송 API의 핵심이라고 볼 수 있다.

Channel에는 ChannelPipelineChannelConfig 등이 할당되는데,

ChannelConfig는 Channel에 대한 모든 구성 설정을 포함하며, 임시 변경(hot change)을 지원한다.

 

또, Channel은 고유하므로 정렬 순서를 보장하기 위해 java.lang.Comparable을 이용한다. 즉, 고유한 두 Channel 인스턴스가 동일한 해시코드를 반환하는 경우에는 Error가 발생한다.

 

 

ChannelPipeline은 인바운드와 아웃바운드 데이터와 이벤트에 적용될 ChannelHandler 인스턴스를 모두 포함한다.

일반적인 ChannelPipeline의 용도는 다음과 같다.

  • 데이터를 한 포맷에서 다른 포맷으로 변환
  • 예외에 대한 알림 제공
  • Channel의 활성화 또는 비활성화에 대한 알림 제공
  • Channel을 EventLoop에 등록할 때 또는 등록 해제할 때 알림 제공
  • 사용자 정의 이벤트에 대한 알림 제공

 

 

ChannelHandler 인스턴스를 필요에 따라 추가하거나 제거해 ChannelPipeline을 즉석으로 수정할 수 있으며, 이러한 네티의 특징을 활용하면 아주 유연한 애플리케이션을 개발할 수 있다는 것을 기억하자.

 

 

 

원격 피어로 데이터를 기록하고 플러시하는 일반적인 Channel 작업의 예를 보자.

Channel channel = ...
ByteBuf buf = Unpooled.copiedBuffer("your data", CharsetUtil.UTF_8);
ChannelFuture cf = channel.writeAndFlush(buf);    // 데이터를 기록하고 플러시
cf.addListerner(new ChannelFutureListener(){    // 기록이 완료되면 알림을 받을 ChannelFutureListener를 추가
    @Override
    public void operationComplete(ChannelFuture future) {
        if(future.isSuccess()){    // 기록이 완료될 경우
            System.out.println("Write success!");
        } else {
            System.out.println("Write error");    // 오류를 로깅
            future.cause().printStacktrace();
        }
    }
})

 

 

네티의 Channel 구현은 스레드에 대해 안전하므로 여러 스레드를 이용하는 경우에도 Channel의 참조를 저장하고 원격 피어에 뭔가를 출력할 때 이용할 수 있다. 다음 예제에는 여러 스레드로 기록을 수행하는 간단한 예를 보자.

 

 

Channel channel = ...
ByteBuf buf = Unpooled.copiedBuffer("your data", CharsetUtil.UTF_8).retain();
Runnable writer = new Runnable() {    // 데이터를 채널로 기록하는 Runnable 생성
    @Override
    public void run() {
        channel.write(buf.duplicate());
    }
};

Executor executor = Executors.newCachedThreadPool();    //  스레드풀 Executor에 대한 참조를 얻음

// 한 스레드에 기록
executor.execute(writer);

// 다른 스레드에 기록
executor.execute(writer);

...

 

 

 


 

 

 

NIO

NIO는 모든 입출력 작업의 완전한 비동기 구현을 제공하며 셀렉터 기반 API를 활용한다.

셀렉터의 기본 개념은 Channel의 상태가 변경되면 요청이 알림을 받을 수 있는 레지스트리 역할을 하는 것이다.

지원되는 상태 변경은 다음과 같다.

  • 새로운 Channel이 수락되고 준비됨.
  • Channel 연결이 완료됨
  • Channel에 읽을 데이터가 있음
  • Channel을 이용해 데이터를 기록할 수 있음

 

 

애플리케이션이 상태 변경에 반응한 후에는 셀렉터가 재설정되며 해당 스레드에서 변경을 검사하고 적절하게 반응하는 프로세스가 반복된다.

 

 

 

 

 

논블로킹 코드 기반의 환경에서..

코드 기반에 블로킹 호출이 없거나 블로킹 호출을 제한할 수 있다면 NIO 또는 epoll(리눅스의 경우)을 이용하는 것이 좋다. 다수의 동시 연결을 처리하기 위한 것이지만 동시 연결이 많지 않은 경우에도 아주 잘 작동한다. 특히 여러 연결에 스레드를 공유할 수 있다는 것이 장점이다.

 

 

 

 

 

출처 : 네티인 액션

https://book.naver.com/bookdb/book_detail.nhn?bid=10462610

 

네티 인 액션

네티는 복잡한 네트워킹, 멀티스레드, 동시성을 관리하는 자바 기반 네트워킹 프레임워크로서, 반복적인 저수준 코드를 내부로 감춤으로써 비즈니스 논리를 분리하고 쉽게 재사용할 수 있게 해

book.naver.com

 

반응형

'IT' 카테고리의 다른 글

Netty ChannelHandler와 ChannelPipeline  (0) 2021.01.03
Netty ByteBuf - 바이트 버퍼  (0) 2020.12.29
Netty 컴포넌트와 설계  (2) 2020.12.10
Java Netty 에코 서버-클라이언트 구현하기  (1) 2020.12.09
Netty의 개념과 아키텍처  (0) 2020.12.09