Spring/Spring Framework

[Netty] Netty가 뭐에요? - 6탄 : 공식 문서 - Echo 테스트 학습

공대키메라 2025. 11. 8. 15:46

지난 시간에는 Netty 학습 5탄에서 ByteBuf에 대해서 상세히 알아보았다.

(이전 내용은 여기 클릭!)

 

이번에는 Netty 학습 4탄에서 미처 이어가지 못했던 예시 학습을 이어서 하려고 한다.

(4탄 내용은 여기 클릭!)


1. Composite Pattern과 Netty

우선 예시를 더 보기 전에 필자는 해당 클래스들을 알게 되었다.

 

EventExecutor 가 EventExecutorGroup을 상속하고

EventExecutorGroup은 내부에 Iterator에 EventExecutor를 담고 있는 구조다.

 

무언가 Composite Pattern과 익숙하다.

 

Composite Pattern 

개별 객체와 객체 그룹을 똑같은 방식으로 다루는 디자인 패턴이다.

 

https://en.wikipedia.org/wiki/Composite_pattern

 

공통된 operation을 선언하고 Composite가 Leaf가 되고, 그 Leaf가 다시 Composite가 된다.

 

나무의 뿌리처럼 뻣어나간다.

 

코드 예시는 다음과 같다.

 

// Component: 공통 인터페이스
interface Organization {
    int getSalary();  // 모두 같은 메소드!
}

// Leaf: 더 이상 나눌 수 없는 개별 요소
class Employee implements Organization {
    String name;
    int salary;
    
    Employee(String name, int salary) {
        this.name = name;
        this.salary = salary;
    }
    
    @Override
    public int getSalary() {
        return salary;  // 자기 월급 반환
    }
}

// Composite: 여러 Component를 포함하는 그룹
class Team implements Organization {
    String name;
    List<Organization> members = new ArrayList<>();  // Component 리스트
    
    Team(String name) {
        this.name = name;
    }
    
    // 멤버 추가 (Employee든 Team이든 다 됨!)
    void add(Organization org) {
        members.add(org);
    }
    
    @Override
    public int getSalary() {
        int total = 0;
        // 모든 멤버의 getSalary() 호출
        for (Organization member : members) {
            total += member.getSalary();
        }
        return total;
    }
}

// main 클래스
public class CompositeMain {
    public static void main(String[] args) {
        // 개별 직원들 (Leaf)
        Employee 김철수 = new Employee("김철수", 300);
        Employee 이영희 = new Employee("이영희", 350);
        Employee 박민수 = new Employee("박민수", 500);
        Employee 최지훈 = new Employee("최지훈", 400);

        // 프론트엔드팀 (Composite)
        Team 프론트엔드팀 = new Team("프론트엔드팀");
        프론트엔드팀.add(김철수);
        프론트엔드팀.add(이영희);

        // 백엔드팀 (Composite)
        Team 백엔드팀 = new Team("백엔드팀");
        백엔드팀.add(박민수);

        // 개발팀 (Composite 안에 Composite!)
        Team 개발팀 = new Team("개발팀");
        개발팀.add(프론트엔드팀);  // Team을 추가!
        개발팀.add(백엔드팀);      // Team을 추가!

        // 디자인팀 (Composite)
        Team 디자인팀 = new Team("디자인팀");
        디자인팀.add(최지훈);

        // 회사 (최상위 Composite)
        Team 회사 = new Team("우리회사");
        회사.add(개발팀);    // Team을 추가!
        회사.add(디자인팀);  // Team을 추가!

        // 모두 같은 방법으로 호출!
        System.out.println("김철수: " + 김철수.getSalary());      // 300
        System.out.println("프론트엔드팀: " + 프론트엔드팀.getSalary());  // 650
        System.out.println("개발팀: " + 개발팀.getSalary());      // 1150
        System.out.println("회사 전체: " + 회사.getSalary());      // 1550


    }
}

 

이렇게 Composite Pattern을 사용하면 팀안에 팀안에 팀 구조로 작성이 가능하다.

 

단일 객체와 복합 객체를 동일하게 취급하여 클라이언트가 구조를 단순하게 다룰 수 있으며

 

이를 통해 새로운 객체를 쉽게 추가하고 유연하게 대처할 수 있다.

 

 

Netty에서 EvenExecutorGroup 이 Iterator<EventExecutor> 를 상속하고 있고

 

EventExecutor 가 EvenExecutorGroup를 상속하는 구조라서 이게 composite 패턴으로 보인다.

 

실제 구현체를 통해서 이는 판단해야한다.

 

EventExecutorGroup interface의 구현체들 캡쳐

 

글로만 보기에는 너무 헷갈린다 예시를 보도록 하자.

 

NettyCompositeDemo.java

import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.concurrent.*;

import java.util.concurrent.TimeUnit;

public class NettyCompositeDemo {
    public static void main(String[] args) throws Exception {
        // 1) Composite: NioEventLoopGroup(그룹), 내부엔 여러 NioEventLoop(Leaf)
        EventExecutorGroup group = new NioEventLoopGroup(3); // children = 3개의 NioEventLoop

        // 2) next(): 그룹이면 자식 중 하나, Leaf면 항상 자기 자신을 반환
        EventExecutor e1 = group.next(); // Leaf (예: NioEventLoop #1)
        EventExecutor e2 = group.next(); // Leaf (예: NioEventLoop #2)
        EventExecutor e3 = group.next(); // Leaf (예: NioEventLoop #3)
        EventExecutor e4 = group.next(); // 다시 #1 (라운드로빈)

        // 3) 같은 코드로 작업 제출: 단일/다중 모두 동일 API
        for (int i = 0; i < 6; i++) {
            final int id = i;
            group.submit(() -> {
                String th = Thread.currentThread().getName();
                System.out.println("task#" + id + " on " + th);
            });
        }

        // 4) Iterable<EventExecutor>: 그룹은 리프들을 순회, Leaf는 자기 자신 하나만
        System.out.println("---- iterate leaves in the group ----");
        for (EventExecutor leaf : group) { // leaf는 실제 NioEventLoop 인스턴스들
            System.out.println("leaf = " + leaf);
        }

        // 5) Leaf도 '1인 그룹'처럼 동작함을 확인: next() == this
        System.out.println("e1.next() == e1 ? " + (e1.next() == e1)); // true

        // 6) 스케줄링도 동일 인터페이스로 사용 가능
        group.schedule(() -> System.out.println("scheduled on " +
                Thread.currentThread().getName()), 200, TimeUnit.MILLISECONDS);

        // 7) 생명주기: 한 번의 호출로 전체 리프에 브로드캐스트
        group.shutdownGracefully();
        group.terminationFuture().sync();
        System.out.println("Shutdown complete.");
    }
}

 

이를 통해서 얻을 수 있는 장점은 다음과 같다.

 

해당 장점은 필자가 직접 떠올릴 수 없어서 claude에게 질문해서 얻은 답변이다.

 

// Group으로 처리
EventExecutorGroup group = new NioEventLoopGroup(4);
group.submit(task);

// 단일 Executor도 동일하게 처리 가능
EventExecutor single = group.next();
single.submit(task); // 같은 인터페이스 사용 가능

/**
	1. 단일 executor도 group처럼 취급
    2. 클라이언트는 그룹인지 단일 executor인지 신경 쓸 필요가 없음.
    3. EventExecutor가 next()에서 자기 자신을 반환하므로, 
    	체이닝 호출이 무한히 이어지지 않고 안전하게 종료된다.
**/

 

사실 클래스들의 구조를 먼저 정리를 하고 싶었는데, 구조를 백날 알아보면 뭐하나! 

 

직접 예시를 보면서 파악하는게 좋다는 생각이 들어서 이번에도 다른 Netty의 예시를 볼 것이다.

 

지난번에는 Discard 예시였지만, 이번에는 Echo 서버 예시이다.

 

2. Netty 공식문서 예시 - Echo 서버 수정  : Modified By 공대키메라

해당 예시는 본인이 잘 모르겟어서... 예시를 좀 주석을 달고, 실제로 여러번 요청을 보내도록 수정했다.

 

물론 잘 몰라서 claude 에게 부탁을 해서 주석도 달고 수정해 보았다.

 

코드가 너무 긴 관계로 접어놓도록 하겠다.

 

더보기

EchoClient.java

/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 * 
 * Modified by: 공대키메라
 * Original File Location : https://github.com/netty/netty/blob/4.1/example/src/main/java/io/netty/example/echo/EchoClient.java
 */
package io.netty.example.echo;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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;
import io.netty.example.util.ServerUtil;
import io.netty.handler.ssl.SslContext;

/**
 * Echo Client: 서버에 연결하여 메시지를 보내고, 서버로부터 받은 데이터를 다시 에코(echo)받는 클라이언트
 * 
 * 동작 방식:
 * 1. 서버에 연결
 * 2. 첫 메시지 전송 (ping-pong 트래픽 시작)
 * 3. 서버로부터 에코된 데이터 수신
 */
public final class EchoClient {
    // 연결할 서버의 호스트 주소 (시스템 프로퍼티로 변경 가능, 기본값: 127.0.0.1)
    static final String HOST = System.getProperty("host", "127.0.0.1");
    
    // 연결할 서버의 포트 번호 (시스템 프로퍼티로 변경 가능, 기본값: 8007)
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));
    
    // 전송할 메시지 크기 (시스템 프로퍼티로 변경 가능, 기본값: 256 bytes)
    static final int SIZE = Integer.parseInt(System.getProperty("size", "256"));

    public static void main(String[] args) throws Exception {
        // ============================================
        // 1. SSL 설정 (보안 통신을 위한 SSL/TLS 컨텍스트 생성)
        // ============================================
        final SslContext sslCtx = ServerUtil.buildSslContext();

        // ============================================
        // 2. EventLoopGroup 생성 (I/O 이벤트를 처리할 스레드 풀)
        // ============================================
        // NioEventLoopGroup: Non-blocking I/O를 처리하는 멀티스레드 이벤트 루프
        // 클라이언트는 보통 단일 그룹만 사용 (서버와 달리 boss/worker 구분 없음)
        EventLoopGroup group = new NioEventLoopGroup();
        
        try {
            // ============================================
            // 3. Bootstrap 설정 (클라이언트 부트스트랩 구성)
            // ============================================
            Bootstrap b = new Bootstrap();
            b.group(group)  // EventLoopGroup 설정: I/O 작업을 처리할 스레드 그룹 지정
             
             // NioSocketChannel: Non-blocking 소켓 채널 클래스 지정
             // TCP 소켓 연결을 위한 NIO 기반 채널
             .channel(NioSocketChannel.class)
             
             // TCP_NODELAY 옵션: Nagle 알고리즘 비활성화
             // true로 설정하면 작은 패킷도 즉시 전송 (지연 감소, Echo 애플리케이션에 적합)
             .option(ChannelOption.TCP_NODELAY, true)
             
             // ============================================
             // 4. ChannelInitializer 설정 (채널 파이프라인 구성)
             // ============================================
             // 새로운 연결이 생성될 때마다 호출되어 파이프라인을 초기화
             .handler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     // 파이프라인: 인바운드/아웃바운드 데이터를 처리하는 핸들러 체인
                     ChannelPipeline p = ch.pipeline();
                     
                     // SSL이 설정되어 있으면 파이프라인의 첫 번째에 SSL 핸들러 추가
                     // SSL 핸들러는 암호화/복호화를 담당
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT));
                     }
                     
                     // 로깅 핸들러 (주석 처리됨)
                     // 활성화하면 네트워크 이벤트를 로그로 출력
                     //p.addLast(new LoggingHandler(LogLevel.INFO));
                     
                     // EchoClientHandler: 실제 비즈니스 로직을 처리하는 커스텀 핸들러
                     // 서버에 메시지를 보내고 에코된 응답을 받아 처리
                     p.addLast(new EchoClientHandler());
                 }
             });

            // ============================================
            // 5. 서버 연결 (비동기 연결 시도)
            // ============================================
            // connect(): 서버에 연결을 시도하고 ChannelFuture를 반환
            // sync(): 연결이 완료될 때까지 현재 스레드를 블로킹
            ChannelFuture f = b.connect(HOST, PORT).sync();

            // ============================================
            // 6. 연결 유지 (종료 대기)
            // ============================================
            // closeFuture(): 채널이 닫힐 때 완료되는 Future 반환
            // sync(): 채널이 닫힐 때까지 현재 스레드를 블로킹
            // 즉, 서버와의 통신이 끝날 때까지 애플리케이션을 유지
            f.channel().closeFuture().sync();
            
        } finally {
            // ============================================
            // 7. 리소스 정리 (Graceful Shutdown)
            // ============================================
            // shutdownGracefully(): EventLoopGroup의 모든 스레드를 우아하게 종료
            // - 진행 중인 작업이 완료될 때까지 대기
            // - 새로운 작업은 거부
            // - 모든 리소스 해제
            group.shutdownGracefully();
        }
    }
}

 

 

EchoClientHandler.java

/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 * Modified by: 공대키메라
 */
package com.example.demo.testing.echo;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.util.concurrent.TimeUnit;

/**
 * Handler implementation for the echo client.
 * 2초마다 메시지를 서버로 전송하고, 에코를 받아 출력합니다.
 */
public class EchoClientHandler extends ChannelInboundHandlerAdapter {

    private final ByteBuf firstMessage;
    private int messageCount = 0;
    private static final int MAX_MESSAGES = 10; // 최대 10개 메시지 전송

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

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        // 연결되면 첫 메시지 전송
        System.out.println("[Client] Connected to server. Starting to send messages...");
        sendMessage(ctx);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 서버로부터 에코 받으면 출력하고 해제
        ByteBuf received = (ByteBuf) msg;
        System.out.println("[Client] Received echo #" + messageCount +
                ": " + received.readableBytes() + " bytes");
        received.release();

        // ✅ 최대 개수 체크 후 2초 뒤 다음 메시지 전송 스케줄링 (한 번만!)
        if (messageCount < MAX_MESSAGES) {
            ctx.executor().schedule(() -> {
                sendMessage(ctx);
            }, 2, TimeUnit.SECONDS);
        } else {
            System.out.println("[Client] Reached max messages (" + MAX_MESSAGES + "). Closing connection.");
            ctx.close();
        }
    }

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

    private void sendMessage(ChannelHandlerContext ctx) {
        messageCount++;

        // 원본 메시지 복사 (재사용 위해)
        ByteBuf message = firstMessage.retainedDuplicate();
        System.out.println("==========================================");
        System.out.println("[Client] Sending message #" + messageCount +
                ": " + message.readableBytes() + " bytes");
        ctx.writeAndFlush(message);
        System.out.println("==========================================");
    }

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

 

 EchoServer.java

/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package com.example.demo.testing.echo;

import com.example.demo.testing.util.ServerUtil;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;

/**
 * Echoes back any received data from a client.
 */
public final class EchoServer {

    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));

    public static void main(String[] args) throws Exception {
        // Configure SSL.
        final SslContext sslCtx = ServerUtil.buildSslContext();

        // Configure the server.
        EventLoopGroup group = new NioEventLoopGroup();
        final EchoServerHandler serverHandler = new EchoServerHandler();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(group)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 100)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc()));
                     }
                     //p.addLast(new LoggingHandler(LogLevel.INFO));
                     p.addLast(serverHandler);
                 }
             });

            // 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.
            group.shutdownGracefully();
        }
    }
}

 

EchoServerHandler.java

/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package com.example.demo.testing.echo;

import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * Handler implementation for the echo server.
 */
@Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ctx.write(msg);
        System.out.println("ctx = " + ctx);
        System.out.println("msg = " + msg);
        System.out.println("-------------------------------------------");
    }

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

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}

 

ServerUtil.java

/*
 * Copyright 2022 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package com.example.demo.testing.util;

import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;

import javax.net.ssl.SSLException;
import java.security.cert.CertificateException;

/**
 * Some useful methods for server side.
 */
public final class ServerUtil {

    private static final boolean SSL = System.getProperty("ssl") != null;

    private ServerUtil() {
    }

    public static SslContext buildSslContext() throws CertificateException, SSLException {
        if (!SSL) {
            return null;
        }
        SelfSignedCertificate ssc = new SelfSignedCertificate();
        return SslContextBuilder
                .forServer(ssc.certificate(), ssc.privateKey())
                .build();
    }
}

 

접은 글에는 4개의 파일이 있다.

 

EchoClient, EchoClientHandler, EchoServer, EchoServerHandle이다.

 

EchoClient.java 요약

EchoClient에서는 어느 HOST, 어느 PORT로 연결할 것인지 Bootstrap을 통해서 설정한다.

그 전에 어떤 타입의 Channel을 사용할지 지정하고(NioSocketChannel), Inbound/Outbound 작업을 처리하기 위한 Handler들을 ChannelPipeline에 등록하는 방법을 정의한다.

그리고 마지막에는 Bootstrap의 connect()를 통해 실제로 서버에 연결을 시도한다.

 

 

 

EchoServer.java 요약

EchoServer에서는 어느 PORT에서 클라이언트의 연결을 받을 것인지 ServerBootstrap을 통해서 설정한다.

그 전에 어떤 타입의 Channel을 사용할지 지정하고(NioServerSocketChannel), 서버 소켓 옵션(SO_BACKLOG)을 설정한다.

 

그리고 두 가지 Handler를 등록한다

  • handler(): 서버 소켓 자체를 위한 Handler (연결 수락 로깅)
  • childHandler(): 각 클라이언트 연결을 위한 Handler (실제 데이터 처리)

마지막에는 ServerBootstrap의 bind()를 통해 실제로 서버를 시작하고 지정된 포트에서 클라이언트 연결을 대기한다.

 

EchoClient와 EchoServer내에서 다음 코드가 생성이 되면, 우리가 설정한 channel, channelpipeline으로 작동하는 실제 소켓을 생성한다.

 

EchoClient.java - 소켓 생성

ChannelFuture f = b.connect(HOST, PORT).sync();

 

EchoServer.java - 소켓 생성

ChannelFuture f = b.bind(PORT).sync();
 

 

여기서 우리가 다시금 상기해야하는 부분은 결국 네트워크 통신은 소켓과 소켓간의 통신이라는 점이다.

 

다만 Netty 는 이러한 네트워크 통신을 쉽게 도와주는 프레임워크이다.

 

다음은 필자가 실행한 모습이다.

 

서버 handler

 

클라이언트 handler

 


 

이번 시간에는 Echo예시를 통해서 실제로 코드가 어떻게 작동하는지 알아보았다.

 

사실 구조를 한눈에 파악 수 있도록 그리고 싶었는데 그게 생각보다 어려워서

 

예시에 집중해서 학습을 진행하는 방향으로 틀었다.

 

무언가 이제는 반복이 되는 느낌도 들기에, 더 예시를 탐구해보고 나중에는 큰 구조를 다시 그려보고,

 

그 다음에는 실제로 유명한 WAS인 Tomcat을 사용하여 요청을 받는 경우과 Netty를 통해서 요청을 받는 경우의

 

성능 테스트를 할 예정이다.

 

 

사실 Netty를 학습하면서 필자는 어느 책도 구매하지 않고 직접 인터넷을 뒤지고 GPT와 Claude를 사용해 가면서

 

검증하고 알아보고 있다.

 

 

하면서 느낀 점은 네트워크에 대한 지식도 많이 부족하다는 느낌을 받았다.

 

TCP와 UDP 를 지원한다는데, 이게 내부적으로 어떻게 소켓을 생성해서 통신을 도와주는지를 봐야 한다.

 

하 공부할거 많네... ㅠㅠ

 

 

책을 구매하는 경우 내용이 깔끔하게 정리가 되어 있어서 그것을 기준으로 학습하고 살을 붙이면 되지만

 

직접 하는 경우에는 생각나는대로 찾아보고 무언가를 놓치게 되면 또 개념 설명이 중간에 이상해지기에 쉽지 않다.

 

그래도... 이번에도 열심히 해보았는데 이상하면 언제든지 피드백! 주면 감사한다 뀽~

 

 

 

출처

https://en.wikipedia.org/wiki/Composite_pattern

https://github.com/netty/netty/tree/4.1/example/src/main/java/io/netty/example/echo