본문 바로가기
슬기로운 자바 개발자 생활/모던 자바와 Reactive

네티 Netty 프레임워크 공부 02 양방향 통신 (예제 첨부)

by 슬기로운 동네 형 2022. 12. 21.
반응형

네티 Netty 프레임워크 공부 02 양방향 통신 (예제 첨부)


2022.12.21 - [슬기로운 자바 개발자 생활/모던 자바와 Reactive] - 네티 Netty 프레임워크 공부 01 (예제 첨부)

 

네티 Netty 프레임워크 공부 01 (예제 첨부)

네티(Netty)란? 네티 홈페이지에서 네티를 아래와 같이 소개하고 있다. 네티는 빠르게 개발가능한 유지보수, 고성능 서버, 클라이언트 비동기 이벤트드리븐 네트워크 애플리케이션 프레임워크다.

ecolumbus.tistory.com


저번 포스팅에 이어 두 번째 시간

이번 포트팅에서는 양방형 통신 예제를 만들어 본다.

 

네티서버 구현

서버는 저번 시간에 작성한 서버 자바 프로그램과 크게 다르지 않다.

  • EchoServer.java
  • EchoServerHandler.java

EchoServer.java

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class EchoServer {
    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        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 p = ch.pipeline();
                            p.addLast(new EchoServerHandler()); //1
                        }
                    });
            ChannelFuture f = b.bind(8888).sync();
            System.out.println("서버시작");
            f.channel().closeFuture().sync();
        }finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}

EchoServerHandler.java

public class EchoServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // (2)
        String readMessage = ((ByteBuf) msg).toString(Charset.defaultCharset()); // (3)
        StringBuilder builder = new StringBuilder();
        builder.append("수신한 문자열 [");
        builder.append(readMessage);  // (4)
        builder.append("]");
        System.out.println(builder.toString());

        // 응답으로 돌려주기
        ByteBuf msgBuffer = Unpooled.buffer();
        msgBuffer.writeBytes("Server Response => received data : ".getBytes());

        ctx.write(msgBuffer); // (5)
        ctx.write(msg);
    }
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { // (6)
        ctx.flush();
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("오류 발생");
        cause.printStackTrace();
        ctx.close();
    }
}

EchoServer 클래스는 8888번 포트를 사용해 클라이언트의 연결을 기다린다.

클라이언트가 접속하면 소켓 채널을 생성하고, 생성된 소켓 채널을 통해 데이터를 수신한 뒤, EchoServerHandler 클래스의 ChaanelRead 메서드에서 " Server Response => received data : " 라는 문구와 함께 받은 데이터를 다시 클라이언트에게 되돌려 주는 역할을 한다.

 

EchoServerHandler 의  (2)번 클라이언트에게 수신된 데이터를 자동으로 호출하는 이벤트 메서드다. 추후, 이곳에 서버의 특정 작업을 구현하면 된다.

(3) 수신된 데이터를 네티의 바이트 버퍼 객체로부터 문자열 데이터를 읽어온다.

(4) 문자열을 출력한다. (5)채널 파이프라인에 대한 이벤트 처리이며, 서버에서 데이터를 수신 받았다는 글자를 덧붙여서 클라이언트에게 그대로 전송한다.

 

서버를 잘만들었는지 점검차 Window Telnet이나 Putty로 글자를 전송해봐도 된다.


네티로 클라이언트 측 코드 구현

네티 프레임워크로 코드를 구현해 본다.

  • EchoClient.java
  • EchoClientHandler.java

EchoClient.java

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class EchoClient {
    public static void main(String[] args) throws Exception{
        EventLoopGroup group = new NioEventLoopGroup();

        try{
            Bootstrap b = new Bootstrap();
            b.group(group) // (1)
                    .channel(NioSocketChannel.class) // (2)
                    .handler(new ChannelInitializer<SocketChannel>() { // (3)
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline p = ch.pipeline();
                            p.addLast(new EchoClientHandler());
                        }
                    });

            ChannelFuture f = b.connect("localhost", 8888).sync(); // (4)

            f.channel().closeFuture().sync();
        }finally {
            group.shutdownGracefully();
        }
    }
}

EchoClientHandler.java

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.nio.charset.Charset;

public class EchoClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) { // (1)
        String sendMessage = "Hello Netty";

        ByteBuf msgBuffer = Unpooled.buffer();
        msgBuffer.writeBytes(sendMessage.getBytes());

        StringBuilder builder = new StringBuilder();
        builder.append("Client 전송한 문자열[");
        builder.append(sendMessage);
        builder.append("]");

        System.out.println(builder.toString());

        ctx.writeAndFlush(msgBuffer);  // (2) 중요
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) { // (3)
        String readMessage = ((ByteBuf) msg).toString(Charset.defaultCharset()); // (4)

        StringBuilder builder = new StringBuilder();
        builder.append("Client 수신한 문자열[");
        builder.append(readMessage);
        builder.append("]");

        System.out.println(builder.toString());

    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx)   { // (5)
        ctx.close(); // (6)
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)   {
        cause.printStackTrace();
        ctx.close();
    }
}

 클라이언트 프로그램은 8888 서버에 접속해서 Hello Netty 문자열을 서버에 전송하고 결과를 수신한다.

 클라이언트 프로그램도 서버 프로그램과 동일하게 두 부분으로 나뉜다.

 main 메서드가 있는 EchoClinet.java 와 서버에 데이터를 보내고 응답을 받는 EchoClinetHandler.java

 

EchoClinet 의 소스의 표시 (1) 클라이언트 프로그램은 이벤트 루프 그룹을 하나만 설정한다.

(2) 클라이언트 애플리케이션이 생성하는 채널의 종류 하나를 설정. NioSocketChannel.

(3) 클라이언트니까 패털 파이프라인에 일반 소켓 채널 클래스인 SoketChannel을 설정.

(4) 비동기 입출력 메서드인 connect 를 호출한다.

 

EchoClinetHandler

서버 측 핸들러와는 약간 다르지만, (1) channelActive는 소켓채널이 최초 활성화 될 때 실행된다. 서버에 데이터를 보내는 역할을 한다.

(2) writeAndFlush 메서드는 이름에서 유추할 수 있듯이 데이터의 기록과 전송 두 가지 메서드를 호출한다.

  • 채널에 데이터를 기록하는 write
  • 채널에 기록된 데이터를 서버로 전송하는 flush

(3) channelRead() 메서드는 서버로부터 데이터가 수신된 데이터가 있을 때 호출되는 이벤트 메서드다.

(4) 수신된 데이터를 네티의 바이트 버퍼 객체로 읽어 들인다.

(5) 수신된 데이터를 모두 읽었을 때, 호출된다.  channelRead() 완료된 후.

(6) 송수신 채널은 닫히고 클라이언트 프로그램은 종료된다.


실행 및 결과

1. 서버 실행 EchoServer.java

EchoServer

2. 클라이언트 프로그램 실행 EchoClient.java

EchoClient

3. EchoServer의 데이터 수신

EchoServer


마치며...

네티는 네트워크 개발자가 쉽게 개발할 수 있도록 이벤트를 논리적으로 구분하여 고수준의 추상화 이벤트 모델을 제공한다.

 예) 클라이언트로부터 데이터가 수신될 때 어떤 작업이 필요하다면 인바운드 이벤트 중 하나인 데이터 수신 이벤트 메서드에 특정작업 <코딩>을 작성하면 된다.

클라이언트와 서버 별 인바운드와 아웃바운드 구분

 

포스팅에서 사용한 예제 첨부

nettyEdu.zip
0.02MB

반응형

댓글