spring과 netty

이번엔 spring을 이용해서 netty의 서버를 만들어보자

내용은 비슷하다.
기존에 했던거와 비슷하다. 스프링의 설정만 사용했다.
main부터 보자

  public static void main(String[] args) {
    try(AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class)){
      context.registerShutdownHook();
      NettyServer nettyServer = context.getBean(NettyServer.class);
      nettyServer.start();
    }
  }

다들 아시다 시피 AnnotationConfigApplicationContext 에 SpringConfig을 설정했다.
다음 으로 SpringConfig를 보자

@Configuration
@ComponentScan("me.wonwoo.spring")
@PropertySource("classpath:server.properties")
@Getter
public class SpringConfig {

  @Value("${tcp.port}")
  private int port;

  @Bean
  public InetSocketAddress tcpPort() {
    return new InetSocketAddress(port);
  }

  @Bean
  public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
    return new PropertySourcesPlaceholderConfigurer();
  }
}

이거 역시 스프링을 할 줄 아는 사람이라면 다 아는 내용이다.
젠장 딱히 할 얘기가 없네..
다음으로는 NettyServer를 보자 실제 netty의 서버를 올리는 곳이다.

@Component
public class NettyServer {

  @Value("${boss.thread.count}")
  private int bossCount;

  @Value("${worker.thread.count}")
  private int workerCount;

  @Autowired
  private InetSocketAddress port;

  private static final ServiceHandler SERVICE_HANDLER = new ServiceHandler();

  public void start() {
    EventLoopGroup bossGroup = new NioEventLoopGroup(bossCount);
    EventLoopGroup workerGroup = new NioEventLoopGroup(workerCount);

    try {
      ServerBootstrap b = new ServerBootstrap();
      b.group(bossGroup, workerGroup)
        .channel(NioServerSocketChannel.class)
        .childHandler(new ChannelInitializer<SocketChannel>() {
          @Override
          protected void initChannel(SocketChannel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
            pipeline.addLast(SERVICE_HANDLER);
          }
        });

      ChannelFuture channelFuture = b.bind(port).sync();
      channelFuture.channel().closeFuture().sync();
    } catch (InterruptedException e) {
      e.printStackTrace();
    } finally {
      bossGroup.shutdownGracefully();
      workerGroup.shutdownGracefully();
    }
  }
}

여기 역시 할얘기가 딱히 없다. NettyServer를 스프링의 빈으로 설정 했다.
그래야 Context에 저 아이가 있다.
다음으로는 handler를 보자

@ChannelHandler.Sharable
public class ServiceHandler extends ChannelInboundHandlerAdapter {

  Logger logger = LoggerFactory.getLogger(this.getClass());

  private final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

  @Override
  public void channelActive(ChannelHandlerContext ctx) throws Exception {
    channels.add(ctx.channel());
  }

  @Override
  public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    ByteBuf byteBuf = (ByteBuf) msg;
    logger.debug("message : {} ",byteBuf.toString(Charset.defaultCharset()));
    channels.writeAndFlush(msg);
  }
}

이제부터는 완전 netty다
우리가 이제껏 배워온 네티다.
한번 텔넷으로 접속을 해보자. 채팅을 할 수 있게 만들었다. 에코서버는 너무 식상하니까.
아마두 잘 돌아 갈 것으로 예상된다.
spring과 함께 API서버도 만들 수 있다.
혹은 어떤 사람이 Spring Boot에 내장 서버로 Netty를 구현한 것도 있다.
https://github.com/DanielThomas/spring-boot-starter-netty.git

이것으로 netty는 모두 마치겠다.
vertx도 해야되는데..
흠흠

netty (5) netty의 ByteBuf

ByteBuf

이번엔 netty의 버퍼를 알아볼 차례다.
네티의 버퍼는 flip 메서드를 호출하지 않아도 읽기도 쓰기 인덱스가 분리되어 사용 가능하다.
소스를 보자.

  private void testBuffer(ByteBuf buf, boolean isDirect){
    assertEquals(11, buf.capacity());

    assertEquals(isDirect, buf.isDirect());

    buf.writeInt(65537);
    assertEquals(4, buf.readableBytes());
    assertEquals(7, buf.writableBytes());

    assertEquals(1, buf.readShort());
    assertEquals(2, buf.readableBytes());
    assertEquals(7, buf.writableBytes());

    assertEquals(true, buf.isReadable());
    buf.clear();

    assertEquals(0, buf.readableBytes());
    assertEquals(11,buf.writableBytes());
  }

  @Test
  public void createUnpooledHeapBufferTest(){
    ByteBuf byteBuf = Unpooled.buffer(11);
    testBuffer(byteBuf,false);
  }

테스트로 netty의 ByteBuf 11개를 생성하였다.
첫번째는 capacity가 11인지 확인하였다. 그리고 direct인지 확인한다.
다음으로는 65537이라는 int형 정수를 썼다.
그리고나서 읽은 바이트수를 확인한다 4바이트가 맞다. 쓸 수 있는 바이트수는 7바이트가 남았다.
65537을 16진수로 표현하면 0x10001이고 이값에 4바이트 패딩을 하면 0x00010001이 된다. 2바이트를 읽으면 1이 된다.
2바이트를 읽었으니 읽을 수 있는 바이트수는 2바이트이다. 쓸 수 있는 바이트는 아직 7바이트다.
버퍼에 데이터가 있는지 확인한다.
clear를 하면 읽을수 있는 버퍼는 없고 남은 바이트는 11이 되었다.

네티 버퍼의 장점은 동적으로 변하는 버퍼다. 소스를 보자 우리개발자니까 백마디 말보다 하나의 소스만 보면된다.

  private void testBuffer1(ByteBuf buf, boolean isDirect) {
    assertEquals(11, buf.capacity());
    assertEquals(isDirect, buf.isDirect());

    String sourceData = "hello world";
    buf.writeBytes(sourceData.getBytes());
    assertEquals(11, buf.readableBytes());
    assertEquals(0, buf.writableBytes());

    assertEquals(sourceData, buf.toString(Charset.defaultCharset()));

    buf.capacity(6);
    assertEquals("hello ", buf.toString(Charset.defaultCharset()));
    assertEquals(6, buf.capacity());

    buf.capacity(13);
    assertEquals("hello ", buf.toString(Charset.defaultCharset()));

    buf.writeBytes("world".getBytes());
    assertEquals(sourceData, buf.toString(Charset.defaultCharset()));

    assertEquals(13, buf.capacity());
    assertEquals(2, buf.writableBytes());
  }

일단 퍼버에 hello world 라고 썼다.
그러면 읽을 수 있는 버퍼의 수는 11개이고 쓸수 있는 버퍼의 수는 없다.
capacity 사이즈를 6으로 바꾸었다. 그럼 ‘hello ‘ 사이즈가 6개로 바뀌었다.
그리고 나서 사이즈를 13으로 바꿔보면 ‘hello ‘는 아직 여전히 남아있다. 저장된 데이터가 보존 되어 있다.
‘world’를 쓰면 기존의 ‘hello world’와 똑같이 남아 있고 쓸 수 있는 버퍼는 2개의 사이즈가 남아있다.
이것으로 얼추 네티의 버퍼를 알아봤다.

네티의 버퍼 생성

네티는 버퍼의 특징은 바이트 버퍼풀을 제공한다는 것이다. 이를 통해 생성된 바이트 버퍼를 재사용 할 수 있다. 바이트 버퍼 생성 방법을 알아보자

PooledByteBufAllocator.DEFAULT.heapBuffer()
PooledByteBufAllocator.DEFAULT.directBuffer()

풀링을 할 수 있는 버퍼를 생성하는 방법이다. 위 에는 힙 버퍼이고 아래는 다이렉트 버퍼이다.

Unpooled.buffer();
Unpooled.directBuffer();

풀링을 하지 않는 버퍼이다. 위 에는 힙 버퍼 이고 아래는 다이렉트 버퍼이다.

바이트 버퍼 풀을 사용함으로써 가장 큰 장점은 버퍼를 빈번히 할당하고 해제 할 때 일어나는 가비지 컬렉션 횟수의 감소이다.
이것으로 네티의 버퍼에 대해서 알아봤다.

다음에는 서드파티(Spring) 연동하는 작업을 해보자!

netty (4) java의 ByteBuffer

netty (4) java의 ByteBuffer

이번 포스팅은 netty의 ByteBuf를 알아보기전에 자바의 ByteBuffer에 대해서 알아보겠다.
자바의 ByteBffer는 java 1.4 에 추가 NIO 바이트 버퍼이다.

자바의 Buffer에는 ByteBuffer, CharBuffer, IntBuffe, ShortBuffer, …등등 여러가지가 있다. 이런 버퍼클래스는 내부의 배열 상태를 관리하는 속성이 있다.

capacity

버퍼에 저장할 수 있는 데이터의 최대 크기 한번 저장하면 변경 할 수 없다.

position

읽기 또는 쓰기가 작업 중인 위치를 나타낸다. 객체가 생성될 때 0으로 초기화 되고 입력 혹은 읽을때 자동으로 증가한다.
limit 와 capacity 값보다 작거나 같다.

limit

읽고 쓸 수 있는 공간의 최대치를 나타낸다. limit값은 조절 가능하다. capacity보다 크게 지정 할 수 없다.

자바의 버퍼를 생성하는 방법을 보자.
버퍼를 생성 할 때도 3가지 메서드가 있다.

allocate

JVM의 힙 영역에 바이트 버퍼를 생성한다. 인수는 생성할 바이트 버퍼 크기다. capacity 속성값이다.

allocateDirect

JVM의 힙 영역이 아닌 운영체제의 커널 영역에 버퍼를 생성한다. 앞서 말한 allocate와 동일하다.

wrap

입력된 바이트 배열을 사용하여 버퍼를 생성한다. 입력에 사용된 바이트 배열이 변경되면 wrap를 사용해서 생성한 바이트 버퍼도 변경된다.

코드를 살펴보자

  @Test
  public void createTest() {
    CharBuffer charBuffer = CharBuffer.allocate(11);
    assertEquals(11, charBuffer.capacity());
    assertEquals(false, charBuffer.isDirect());

    ByteBuffer directBuffer = ByteBuffer.allocateDirect(11);
    assertEquals(11, directBuffer.capacity());
    assertEquals(true, directBuffer.isDirect());

    int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0};
    IntBuffer intBuffer = IntBuffer.wrap(array);
    assertEquals(11, intBuffer.capacity());
    assertEquals(false, intBuffer.isDirect());
  }

allocate, allocateDirect, wrap를 생성하는 코드이다.
소스를 보면 더 이해가 빠를것이다.

이제는 생성한 버퍼를 사용해보자.

  @Test
  public void byteBufferTest1() {
    ByteBuffer byteBuffer = ByteBuffer.allocate(11);
    System.out.println(String.format("바이트 버퍼 초기 값 : %s", byteBuffer));
    assertEquals(0, byteBuffer.position());

    byte[] source = "Hello world".getBytes();
    byteBuffer.put(source);
    System.out.println(String.format("11 바이트 기록 후 : %s", byteBuffer));
    assertEquals(11, byteBuffer.position());
  }

최초에 11개의 데이터를 저장 할 수 있는 버퍼를 만들었다.
현재 포지션이 0 인지 알아왔다.
그리고 Hello world라는 byte배열을 생성해서 버퍼에 담았다.
버퍼에 저장한 후에 보면 포지션이 11인걸 알 수 있다. 출력된 내용을 보면 더 자세히 알수 있다.

바이트 버퍼 초기 값 : java.nio.HeapByteBuffer[pos=0 lim=11 cap=11]
11 바이트 기록 후 : java.nio.HeapByteBuffer[pos=11 lim=11 cap=11]

이번에는 버퍼를 읽고 써보자!

  @Test
  public void byteBufferTest3() {
    ByteBuffer byteBuffer = ByteBuffer.allocate(11);
    System.out.println(String.format("바이트 버퍼 초기 값 : %s", byteBuffer));

    byteBuffer.put((byte) 1);
    assertEquals(0, byteBuffer.get());
    assertEquals(2, byteBuffer.position());
    System.out.println(String.format("바이트 버퍼 값 : %s", byteBuffer));
  }

이번에는 1이라는 숫자를 버퍼에 썼다.
그리고나서 다시 get메서드를 사용해서 읽었다. get메서드를 호출 할 때도 position값이 증가한다.
그래서 포지션이 2가 되었다.
출력된 버퍼

바이트 버퍼 초기 값 : java.nio.HeapByteBuffer[pos=0 lim=11 cap=11]
바이트 버퍼 값 : java.nio.HeapByteBuffer[pos=2 lim=11 cap=11]

조금더 이해가 가도록 한개 더 살펴 보겠다.

  @Test
  public void byteBufferTest4() {
    ByteBuffer byteBuffer = ByteBuffer.allocate(11);
    System.out.println(String.format("바이트 버퍼 초기 값 : %s", byteBuffer));

    byteBuffer.put((byte) 10);
    byteBuffer.put((byte) 20);
    assertEquals(2, byteBuffer.position());

    byteBuffer.rewind();
    assertEquals(0, byteBuffer.position());

    assertEquals(10, byteBuffer.get());
    assertEquals(1, byteBuffer.position());

    System.out.println(String.format("바이트 버퍼 값 : %s", byteBuffer));
  }

버퍼에 10과 20를 담았다. 그러면 포지션은 2가 될 것이다.
rewind 함수는 position값이 0이 된다.
그러고 나서 get함수를 호출하면 처음에 들어있는 10이 나오게된다.
get함수를 호출 하였으니 position은 1증가하게 된다.

바이트 버퍼 초기 값 : java.nio.HeapByteBuffer[pos=0 lim=11 cap=11]
바이트 버퍼 값 : java.nio.HeapByteBuffer[pos=1 lim=11 cap=11]

이번엔 flip이란걸 알아보자

  @Test
  public void writeTest() {
    ByteBuffer byteBuffer = ByteBuffer.allocateDirect(11);

    System.out.println(String.format("바이트 버퍼 초기 값 : %s", byteBuffer));

    assertEquals(0, byteBuffer.position());
    assertEquals(11, byteBuffer.limit());

    byteBuffer.put((byte) 10);
    byteBuffer.put((byte) 20);
    byteBuffer.put((byte) 30);
    byteBuffer.put((byte) 40);

    assertEquals(4, byteBuffer.position());
    assertEquals(11, byteBuffer.limit());

    byteBuffer.flip();

    assertEquals(0, byteBuffer.position());
    assertEquals(4, byteBuffer.limit());

    System.out.println(String.format("바이트 버퍼 값 : %s", byteBuffer));
  }

flip은 limit 속성값이 마지막에 기록한 데이터의 위치로 변경된다.
4개의 숫자를 버퍼에 담고 처음에는 limit가 11이 나왔다.
그런후에 flip을 호출하고 limit를 확인하면 4가 나온다.

바이트 버퍼 초기 값 : java.nio.DirectByteBuffer[pos=0 lim=11 cap=11]
바이트 버퍼 값 : java.nio.DirectByteBuffer[pos=0 lim=4 cap=11]

이것으로 자바의 buffer를 알아봤다.
다음에는 netty의 buffer를 알아보겠다.뿅