오래간만에 Netty- (2)Writing an Echo Server

2017. 5. 22. 14:03Java/Netty

Writing an Echo Server

어떤 요청에도 응답없이 데이터를 소진하는 것을 구현해봤습니다.
서버, 그러나 일반적으로는 요청에 대한 응답을 지원합니다.
ECHO 프로토콜을 구현함으로, 받은 데이터를 되돌려 주는 클라이언트에게 응답메시지를 어떻게 작성하는 방법에 대해 알아보도록 합시다.

앞서서 구현한 폐기서버와 다른 점은 받은 데이터를 되돌려 주는 겁니다. 콘솔에 받은 데이터를 출력하는 것 대신 말이죠
그러므로, channelRead() 메소드만 수정하는 것으로 충분 합니다.

 @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ctx.write(msg); // (1)
        ctx.flush(); // (2)
    }

  1. ChannelHandlerContext 객체는 다양한 동작을 제공합니다. 다양한 I/O 이벤트들과 동작들을 트리거할수 있도록 말이죠.
    여기, verbatim 에 받은 메시지를 작성하기 위해 write(object)를 발생시킵니다.
    우리가 DISCARD 예제에서 했던것과 다르게 받은 메시지를 release 하지 않는 것을 기억해두세요.
    wire에 작성될때, Netty가 여러분을 위해 메시지를 release 하기 때문입니다. 
  2. ctx.write(Object) 는 wire 에 메시지를 작성하지 않습니다.
    내부적으로 임시저장(Buffered) 되고,  ctx.flush() 에 의해서 wire 에  flush 됩니다.
    간결하게 ctx.writeAndFlush(msg)를 호출해도 됩니다. 

다시 telnet 창에서, 무슨 메시지든지 보내면, 서버에서 보낸 메시 지를 볼수 있습니다.
풀 소스는 http://netty.io/4.0/xref/io/netty/example/echo/package-summary.html 에서 확인하도록 하세요.


EchoServerHandler

public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctxObject msg) throws Exception {

        ctx.write(msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctxThrowable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}


EchoServer


public class EchoServer {
    static final boolean SSL =  System.getProperty("ssl") != null;
    static final int PORT = Integer.parseInt(System.getProperty("port","8007"));

    public static void main(String[] args) throws Exception{
        //Configure SSL
        final SslContext sslContext;
        if(SSL){
            SelfSignedCertificate ssc = new SelfSignedCertificate();
            sslContext = SslContextBuilder.forServer(ssc.certificate()ssc.privateKey()).build();
        }else{
            sslContext = null;
        }

        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try{
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG,100)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline p = ch.pipeline();
                            if(sslContext != null){
                                p.addLast(sslContext.newHandler(ch.alloc()));
                            }
                            p.addLast(new EchoServerHandler());
                        }
                    })
                    .childOption(ChannelOption.SO_KEEPALIVE,true);
            // Start the Server
            ChannelFuture f = b.bind(PORT).sync();

            //Wait until the server socket is closed.
            f.channel().closeFuture().sync();
        }finally {
            //Shut down all event loops to terminate all threads.
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

}

위의 예제에서 알려준대로, client 도 만들어서 테스트 시도

EchoClientHandler

public class EchoClientHandler extends ChannelInboundHandlerAdapter {
    private static final Logger LOG = LoggerFactory.getLogger(EchoClientHandler.class);
    private final ByteBuf firstMessage;

    public EchoClientHandler(){
        firstMessage  = Unpooled.buffer(EchoClient.SIZE);
        for(int i =i< firstMessage.capacity();i++){
            firstMessage.writeByte((byte)i);
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        LOG.info("전송한 문자열 {}",firstMessage.toString());
        ctx.writeAndFlush(firstMessage);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctxObject msg) throws Exception {
        String readMessage = ((ByteBuf)msg).toString(Charset.defaultCharset());
        LOG.info("수신한 문자열 {} "readMessage);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctxThrowable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}


EchoClient
  • 서버와는 좀 다른 부분
    • 서버에 연결되는 채널이 하나만 존재하기 때문에 EventLoop는 하나만
    • 생성하는 채널종류는  NioSocketChannel (서버에 연결된 클라이언트 소켓 채널은 NIO 로 동작)
    • ChannelPipeLine 설정에는 SocketChannel
    • bind이 아니라, connect(비동기 입출력 메서드) 하는 것이면 되겠다. 



public class EchoClient {

    static final boolean SSL = System.getProperty("ssl") != null;
    static final String HOST = System.getProperty("host""127.0.0.1");
    static final int PORT = Integer.parseInt(System.getProperty("port""8007"));
    static final int SIZE = Integer.parseInt(System.getProperty("size""256"));

    public static void main(String[] args) throws Exception {

        //Configure SSL.git
        final SslContext sslContext;
        if(SSL){
            sslContext = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
        }else{
            sslContext = null;
        }

        EventLoopGroup bossGroup = new NioEventLoopGroup();
        try{
            Bootstrap b = new Bootstrap();
            b.group(bossGroup)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY,true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline p = ch.pipeline();
                            if(sslContext != null){
                                p.addLast(sslContext.newHandler(ch.alloc(),HOST,PORT));
                            }
                            //p.addList(new LoggingHandler(LogLevel.INFO)));
                            p.addLast(new EchoClientHandler());
                        }
                    });
            // Start the Client
            ChannelFuture f = b.connect(HOST,PORT).sync();
            //Wait until the connection is closed
            f.channel().closeFuture().sync();
        }finally {
            //Shut down the event loop the terminal all threads.
            bossGroup.shutdownGracefully();
        }
    }

}


Result 

SERVER 

13:59:37.340 [nioEventLoopGroup-2-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0x38688d7b] REGISTERED
13:59:37.343 [nioEventLoopGroup-2-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0x38688d7b] BIND: 0.0.0.0/0.0.0.0:8007
13:59:37.346 [nioEventLoopGroup-2-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0x38688d7b, L:/0:0:0:0:0:0:0:0:8007] ACTIVE

CLIENT

14:00:09.426 [nioEventLoopGroup-2-1] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.bytebuf.checkAccessible: true
14:00:09.430 [nioEventLoopGroup-2-1] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@773f4112
14:00:09.450 [nioEventLoopGroup-2-1] INFO io.netty.example.echo.EchoClientHandler - 전송한 문자열 UnpooledUnsafeHeapByteBuf(ridx: 0, widx: 256, cap: 256)
14:00:09.459 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxCapacity: 32768
14:00:09.459 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxSharedCapacityFactor: 2
14:00:09.460 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.linkCapacity: 16
14:00:09.513 [nioEventLoopGroup-2-1] INFO io.netty.example.echo.EchoClientHandler - 수신한 문자열     
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~��������������������������������������������������������������������������������������������������������������������������������

다시 서버 확인 

14:00:09.456 [nioEventLoopGroup-2-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0x38688d7b, L:/0:0:0:0:0:0:0:0:8007] RECEIVED: [id: 0xacf1245c, L:/127.0.0.1:8007 - R:/127.0.0.1:7947]
14:00:09.484 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxCapacity: 32768
14:00:09.485 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxSharedCapacityFactor: 2
14:00:09.485 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.linkCapacity: 16
14:00:09.501 [nioEventLoopGroup-3-1] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.bytebuf.checkAccessible: true
14:00:09.503 [nioEventLoopGroup-3-1] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@3b6aade
14:00:09.509 [nioEventLoopGroup-3-1] INFO io.netty.example.echo.EchoServerHandler - 수신 문자열     
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~��������������������������������������������������������������������������������������������������������������������������������


THE END