IT

Netty ByteBuf - 바이트 버퍼

코딩하는 너구리 2020. 12. 29. 17:08
반응형

 

 

앞서 언급한 것처럼 네트워크 데이터의 기본 단위는 항상 바이트이다.

 

자바 NIO는 ByteBuffer라는 자체 바이트 컨테이너를 제공하지만 이 클래스는 사용법이 너무 복잡해 사용하기 부담스럽다.

 

네티에는 네트워크 개발자에게 더 나은 API를 제공하는 강력한 구현인 ByteBuf가 있다.

 

네티는 데이터 처리를 위한 API를 ByteBuf 추상 클래스와 ByteBufHolder 인터페이스라는 두 컴포넌트를 통해 노출한다.

 

ByteBuf API의 장점은 다음과 같다.

  • 사용자 정의 버퍼 형식으로 확장할 수 있음
  • 내장 복합 버퍼 형식을 통해 투명한 제로 카피를 달성할 수 있음.
  • 용량을 필요에 따라 확장할 수 있음(JDK의 StringBuilder와 비슷)
  • ByteBuffer의 flip() 메서드 호출 없이도 리더와 라이터 모드 전환이 가능함
  • 읽기와 쓰기에 고유 인덱스를 적용함
  • 메서드 체인이 지원됨
  • 참조 카운팅이 지원됨
  • 풀링이 지원됨

 

 

ByteBuf

모든 네트워크 통신은 직접 다루기 까다로운 바이트 시퀀스를 주고받는 방식으로 이뤄지므로 효율적이고 사용하기 쉬운 데이터 구조가 반드시 필요하다. ByteBuf에 대해 알아보자.

 

작동 방식

ByteBuf는 읽기와 쓰기를 위한 고유한 두 인덱스를 유지한다. ByteBuf에서 데이터를 읽으면 ByteBuf의 readerIndex가 읽은 바이트 수만큼 증가한다. 비슷하게 데이터를 기록하면 writerIndex가 증가한다.

ByteBuf 메서드 중 이름이 read나 write로 시작하는 메서드는 해당 인덱스를 증가시키지만,

이름이 set이나 get으로 시작하는 메서드는 인덱스를 증가시키지 않으며, 메서드의 인수로 전달한 인덱스를 기준으로 작업한다.

 

 

사용 패턴

네티를 사용하는 동안 ByteBuf의 몇 가지 공통적인 사용 패턴을 접하게 된다. 세 가지의 사용 패턴에 대해 알아보자.

 

 

힙 버퍼

보조 배열이라고 하는 가장 자주 이용되는 ByteBuf 패턴이며, JVM의 힙 공간에 데이터를 저장한다. 이 패턴은 풀링이 사용되지 않는 경우 빠른 할당과 해제 속도를 보여준다.

ByteBuf heapBuf = ...;
if (heapBuf.hasArray()) {    // ByteBuf에 배열이 있는지 확인
    byte[] array = heapBuf.array();    // 있는 경우 배열의 참조를 얻음
    int offset = heapBuf.arrayOffset() + heapBuf.readerIndex();    // 첫 번째 바이트에 대한 오프셋을 계산
    int length = heapBuf.readableBytes();    // 읽을 수 있는 바이트를 얻음
    handleArray(array, offset, length);    // 배열, 오프셋, 길이를 매개변수로 지정하고 메서드를 호출
}

 

 

 

다이렉트 버퍼

ByteBuf의 Javadoc을 보면 "다이렉트 버퍼의 내용은 일반적인 가비지 컬렉션이 적용되는 힙 바깥에 위치한다"라고 명시되어 있다. 다이렉트 버퍼가 네트워크 데이터 전송에 이상적이기 때문이다. 데이터가 힙 할당 버퍼에 있는 경우 JVM은 소켓을 통해 전송하기 전에 내부적으로 버퍼를 다이렉트 버퍼로 복사해야 한다.

 

다이렉트 버퍼주요 단점은 힙 기반 버퍼보다 할당과 해제의 비용 부담이 약간 더 크다는 것이다. 또한 레거시 코드를 이용하는 경우 다른 단점을 경험할 수 있는데, 데이터가 힙에 있지 않기 때문에 다음 예제에 나오는 것처럼 복사본을 만들어야 한다.

 

ByteBuf directBuf = ...;
if (!directBuf.hasArray()) {    // ByteBuf에 보조 배열이 있는 중 확인, 없는 경우 다이렉트 버퍼임
    int length = directBUf.readableBytes();
    byte[] array = new byte[length];
    directBuf.getBytes(directBuf.readerIndex(), array);    // 바이트를 배열로 복사
    handleArray(array, 0, length);
}

 

 

 

복합 버퍼

세 번째이자 마지막 패턴은 복합 버퍼이다. 이 패턴에서는 ByteBuf 인스턴스를 필요에 따라 추가 및 삭제할 수 있다.

 

헤더본문의 두 부분으로 구성되는 메시지를 HTTP를 통해 전송하는 경우를 예로 들어보자. 두 부분은 애플리케이션의 각기 다른 모듈에서 생성되며, 메시지를 전송할 때 조립된다. 애플리케이션에는 동일한 메시지 본문을 여러 메시지에 재사용하는 옵션이 있으며, 이 옵션을 이용하는 경우 각 메시지에 새로운 헤더가 생성된다.

 

CompositeByteBuf를 이용하면 메시지마다 두 버퍼를 다시 할당할 필요가 없어 아주 편리하며, 공통 ByteBuf API를 노출할 때 불필요하게 복사할 필요가 없게 해준다.

 

 

 

 

CompositeByteBuf messageBuf = Unpooled.compositeBuffer();
ByteBuf headerBuf = ...;
ByteBuf bodyBuf = ...;
messageBuf.addComponents(headerBuf, bodyBuf);
...
messageBuf.removeComponent(0);    // 헤더를 제거
for (ByteBuf buf : messageBuf) {
    System.out.println(buf.toString());
}

 

 

 

 

 

 

ByteBuf 메소드

ByteBuf buffer = ...;

buffer.capacity()    // ByteBuf 인덱스는 [ 0 ~ capacity()-1 ]

buffer.discardReadBytes();    // 이미 읽은 바이트 공간 회수, 메모리 복사로 자주 사용하는건 좋지 않음

buffer.readByte();    // buffer에서 Byte를 읽음

buffer.isReadable();    // 읽을 수 있는 바이트가 있는지 확인

buffer.writableBytes();    // 버퍼에 공간이 충분한지 확인
// ex -> while(buffer.writableBytes() >= 4) ...

buffer.writeBytes(data);        // buffer에 data 내용을 씀

 

 

 

readerIndex, writerIndex 인덱스 관리

// 인덱스를 지정한 위치로 이동하려면 readerIndex(int)나 writerIndex(int)를 호출하면 된다.

// clear()를 호출하면 readerIndex와 writerIndex를 모두 0으로 설정할 수 있다.

// clear()는 메모리를 복사하지 않으므로 discardReadBytes()보다 훨씬 비용이 낮다.

// indexOf(val) 지정한 값의 인덱스를 알아낸다.

// forEachByte(ByteBufProcessor.FIND_NUL)    NULL을 찾아낸다.
// forEachByte(ByteBufProcessor.FIND_CR)    리턴문자(\n)를 찾아낸다.

 

 

 

파생 버퍼

파생 버퍼는 ByteBuf의 내용을 특수한 방법으로 나타내는 뷰를 제공한다. 파생 버퍼의 종류는 다음과 같다.

 

  • duplicate()
  • slice()
  • slice(int, int)
  • Unpooled.unmodifiableBuffer(...)
  • order(ByteOrder)
  • readSlice(int)

 

파생 버퍼는 생성하는 비용은 낮지만 파생 버퍼의 내용을 수정하면 원본 인스턴스까지 수정된다는 데 주의해야 한다.

 

ByteBuf 복사

기존 버퍼의 복사본이 필요하면 copy()나 copy(int, int)를 이용하면 된다. 파생버퍼와 달리 이 메서드에서 반환하는 ByteBuf는 데이터의 독립된 복사본이다.

 

 

 

 

 

출처 : 네티인 액션

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

 

네티 인 액션

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

book.naver.com

 

 

반응형

'IT' 카테고리의 다른 글

Netty 코덱  (0) 2021.01.05
Netty ChannelHandler와 ChannelPipeline  (0) 2021.01.03
Netty 전송 API  (0) 2020.12.28
Netty 컴포넌트와 설계  (2) 2020.12.10
Java Netty 에코 서버-클라이언트 구현하기  (1) 2020.12.09