Netty | Simple TCP Server - Client

주싱·2022년 2월 1일
0

Netty

목록 보기
4/9
post-custom-banner

Netty 프레임워크를 활용해 TCP 서버, 클라이언트를 구성하고 간단한 메시지를 교환하는 테스트 코드를 작성합니다. GitHub

  • 클라이언트와 서버를 하나의 프로세스에서 실행하고, Loopback 주소(127.0.0.1)로 통신하게 합니다.
  • 고정된 4바이트 길이 메시지를 주고 받도록 합니다.
  • 클라이언트가 메시지를 전송하고, 서버가 고정된 응답 메시지로 응답하는지 확인합니다.
  • 서버에서 10개의 메시지를 전송하고 클라이언트에서 10개의 메시지를 수신하는지 확인합니다.
@Slf4j
@TestInstance(TestInstance.Lifecycle.PER_METHOD)
@DisplayName("TCP 서버-클라이언트 간단한 통신 테스트")
public class SimpleTcpTest {
    // 서버
    ServerBootstrap serverBootstrap = new ServerBootstrap();
    NioEventLoopGroup serverAcceptGroup = new NioEventLoopGroup();
    NioEventLoopGroup serverServiceGroup = new NioEventLoopGroup();
    ConcurrentHashMap<SocketAddress, Channel> activeServerChannelMap = new ConcurrentHashMap<>();

    // 클라이언트
    Bootstrap clientBootstrap = new Bootstrap();
    NioEventLoopGroup clientEventLoopGroup = new NioEventLoopGroup();
    BlockingQueue<String> clientResponseQueue = new LinkedBlockingQueue<>();
    Channel clientChannel;

    // 테스트 데이터
    String fixedResponse = "RESP";

    @BeforeEach
    @SneakyThrows
    public void beforeEach() {
        serverSetupAndStart();
        clientSetupAndConnect();
        waitForServerServiceActive();
    }

    private void waitForServerServiceActive() throws InterruptedException {
        Thread.sleep(100);
    }

    @AfterEach
    @SneakyThrows
    public void afterEach() {
        clientEventLoopGroup.shutdownGracefully();
        serverServiceGroup.shutdownGracefully();
        serverAcceptGroup.shutdownGracefully();
    }

    @SneakyThrows
    private void serverSetupAndStart() {
        serverBootstrap.group(serverAcceptGroup, serverServiceGroup)
                .channel(NioServerSocketChannel.class)
                .localAddress("0.0.0.0", 12345)
                .option(ChannelOption.SO_REUSEADDR, true)
                .childHandler(new ChannelInitializer<>() {
                    @Override
                    protected void initChannel(Channel ch) {
                        ch.pipeline()
                                // Inbound
                                .addLast(new ActiveServerChannelUpdater(activeServerChannelMap))
                                .addLast(new FixedLengthFrameDecoder(4))
                                .addLast(new StringDecoder())
                                .addLast(new ServerResponseHandler(fixedResponse))

                                // Outbound
                                .addLast(new StringEncoder())

                                // Duplex
                                .addLast(new Logger("Server", true));
                    }
                });
        serverBootstrap.bind().sync();
    }

    @SneakyThrows
    private void clientSetupAndConnect() {
        clientBootstrap.group(clientEventLoopGroup)
                .channel(NioSocketChannel.class)
                .remoteAddress("127.0.0.1", 12345)
                .handler(new ChannelInitializer<>() {
                    @Override
                    protected void initChannel(Channel ch) {
                        ch.pipeline()
                                // Inbound
                                .addLast(new FixedLengthFrameDecoder(4))
                                .addLast(new StringDecoder())
                                .addLast(new ReceiveDataUpdater(clientResponseQueue))

                                // Outbound
                                .addLast(new StringEncoder())

                                // Duplex
                                .addLast(new Logger("Client", true));
                    }
                });
        clientChannel = clientBootstrap.connect().sync().channel();
    }

    @Test
    @SneakyThrows
    @DisplayName("메시지 전송-응답 테스트")
    void simpleResponseTest() {
        // Given : 연결된 서버, 클라이언트

        // When : 클라이언트에서 메시지 전송
        clientChannel.writeAndFlush("ABCD");

        // Then : 서버로부터 응답 수신
        String response = clientResponseQueue.poll(100, TimeUnit.MILLISECONDS);
        Assertions.assertEquals(fixedResponse, response);
    }

    @Test
    @SneakyThrows
    @DisplayName("메시지 N개 수신 테스트")
    void multipleReceiveTest() {
        // Given : 연결된 서버, 클라이언트

        // When : 서버에서 10개 메시지 전송
        Channel serverServiceChannel = activeServerChannelMap.get(clientChannel.localAddress());
        for (int i = 0; i < 10; i++) {
            serverServiceChannel.writeAndFlush(String.format("RES%d", i));
        }

        // Then : 클라이언트에서 10개 메시지 수신
        for (int i = 0; i < 10; i++) {
            String response = clientResponseQueue.poll(100, TimeUnit.MILLISECONDS);
            Assertions.assertEquals(String.format("RES%d", i), response);
        }
    }
}
profile
소프트웨어 엔지니어, 일상
post-custom-banner

0개의 댓글