2019년 4월 2일 화요일

[Netty] 클라이언트 예제

Client 초기화시엔 read/write를 처리할 worker thread만 지정해 주면 된다. 빌더 패턴으로 부트스트래핑 하는데 인코더/디코더를 어떤걸 쓸건지, 어떤 옵션을 택할건지를 빼면 코드는 거의 비슷하다.

아래 예제에선 채널 파이프라인에 ObjectEncoderObjectDecoder를 지정해 주었다. ObjectEncoder는 Java 객체를 ByteBuf로 serialize 하는 역할을 수행하고, ObjectDecoder는 수신한 ByteBuf를 Java 객체로 deserialize 해준다.

싱글턴으로 XXXXClient를 구현하고 핸들러에 메시지를 전송할 수 있는 메서드를 열어두면 간단하게 소켓 클라이언트를 구현할 수 있다. Handler로 전달된 참조형 객체(ByteBuf)의 해제는 핸들러의 책임인 것으로 알고 있다. 읽는 쪽의 어딘가에서 ReferenceCountUtil.release() 해주어야 한다. 메시지를 쓰기 위해 필요한 ChannelHandlerContextchannelActive 시점에 셋 해주면 될 것이다.


public class XXXXClient {
    private EventLoopGroup workerGroup = new NioEventLoopGroup();
    private XXXXHandler handler = new XXXXHandler(...);

    // Singleton.
 
    public void start(...) {
        new Thread(() -> {
            try {
                Bootstrap b = new Bootstrap();
                ChannelFuture f = b.group(workerGroup)
                    .channel(NioSocketChannel.class)
                    .option(...)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(
                                new ObjectEncoder(),
                                new ObjectDecoder(ClassResolvers.cacheDisabled(null)),
                                handler);
                        }
                    })
                    .connect(ip, port).sync();
                f.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                // ...
            } finally {
                workerGroup.shutdownGracefully();
            }
        }).start();
    }

    public void stop() {
            workerGroup.shutdownGracefully();
    }

    public void sendMessage(Object msg) {
         handler.sendMessage(msg);
    }
}

public class XXXXHandler extends ChannelInboundHandlerAdapter {
    private ChannelHandlerContext context;

    public void channelActive(...) {
         super.channelActive(ctx);
         this.context = ctx;
    }

    public void channelRead(...) {
        if (msg instanceof YYYY) {
            ...
        }

        ReferenceCountUtil.release(msg);
    }

    public void channelReadComplete(...) {
        ctx.flush();
    }

    public void sendMessage(Object msg) {
        if (context != null && context.channel().isWritable()) {
            context.writeAndFlush(msg);
        } 
    }
}

동작은 하는데 이렇게 하는게 맞는진 잘 모르겠다. :(

댓글 없음:

댓글 쓰기