지난 시간에는 Netty의 탄생 배경에 대해 알게 되었다. (이전 글 클릭!)
글을 시작하기 전에 Netty 의 창시자는 한국인으로, 이희승 씨인데 LINE에서 인터뷰를 한 내용이 있는데
관심있는 분은 읽어보길 바란다. (비동기를 사랑하는 오픈소스 개발자, 이희승)
이번 시간에는 Netty의 공식문서에서 드러낸 특징들에 대해서 읽고 NIO로 테스트를 해 볼 것이다.
1. Netty 특징(Features) 탐구
뭐니뭐니해도 공식문서를 읽어야 한다.
공식문서의 내용을 정리하면 다음과 같다.
Design(디자인)
- 블로킹/논블로킹을 모두 지원하는 통일된 API
- 유연하고 확장 가능한 이벤트 모델
- 커스터마이징 가능한 스레드 모델 (싱글 스레드부터 SEDA 같은 멀티 스레드 풀까지)
- UDP를 포함한 Connectionless 소켓 지원
Ease of use(사용의 편의)
- 잘 정리된 Javadoc과 예제
- 추가 라이브러리 의존성 없음 (JDK만 있으면 됨)
Performance(성능)
- 높은 처리량(throughput)과 낮은 지연시간(latency)
- 최소화된 메모리 복사로 리소스 효율 극대화
Security(보안)
- 완전한 SSL/TLS 지원
출처 - Netty 공식 문서: https://netty.io/wiki/user-guide-for-4.x.html
해당 글을 읽고 완! 벽! 하게 이해가 됐다면 내가 여기서 글을 마쳤을 건데, 그러지 못했다.
정확히 무슨 말인지 다 찾아보도록 하겠다.
2. Design 섹션 - SEDA
여기서 필자가 잘 모르는 부분은 SEDA, ture connectionless datagram 이라는 말이다.
SEDA? 쎄다? 이건 또 뭐가 쎄다는건다... 하고 찾아보니 다음과 같은 정의를 가지고 있다. 그놈의 약어...
SEDA(Staged Event-Driven Architecture)란?
복잡한 이벤트 기반 애플리케이션을 여러 단계(Stage)로 나누고, 각 단계를 큐로 연결하는 아키텍처 패턴입니다[2].
핵심 특징:
- 스레드 기반 동시성 모델의 오버헤드 회피 (락 경합, 폴링 등)
- 이벤트 처리와 스레드 스케줄링을 분리
- 각 단계의 큐에서 승인 제어를 통해 부하 조절 쉽게 말하면, 작업을 여러 단계로 나누고 각 단계마다 큐로 연결해서 과부하를 방지하는 방식입니다.
[2] Wikipedia - SEDA: https://en.wikipedia.org/wiki/Staged_event-driven_architecture
SEDA란 단계적 이벤트 기반 아키텍처로 이러쿵 저러쿵...
위키피디아에서 발췌한 내용인데, 여러개 짚어야할 부분이 많다.
이벤트 기반 어플리케이션 큐? 이것이 무슨 말인가?
높은 오버헤드? 왜 lock을 자주 하면 오버헤드가 높은가? 무엇을 오버헤드라고 표현하나?
이벤트 큐에서 승인 제어를 수행해서 시스템 자원의 과도한 소모 방지? 이게 무슨 말인가?
그럼 알아가보자.
3. SEDA 용어 탐구
SEDA(Staged Event-Driven Architecture)란?
복잡한 이벤트 기반 애플리케이션을 여러 단계(Stage)로 나누고, 각 단계를 큐로 연결하는 아키텍처 패턴입니다[2].
핵심 특징:
- 스레드 기반 동시성 모델의 오버헤드 회피 (락 경합, 폴링 등)
- 이벤트 처리와 스레드 스케줄링을 분리
- 각 단계의 큐에서 승인 제어를 통해 부하 조절
쉽게 말하면, 작업을 여러 단계로 나누고 각 단계마다 큐로 연결해서 과부하를 방지하는 방식입니다.
[2] Wikipedia - SEDA: https://en.wikipedia.org/wiki/Staged_event-driven_architecture
관련 글을 읽던중 이해가 안되는 것들이 있었다.
이벤트 기반 어플리케이션 큐?
이벤트 기반이란 정확히 무슨 말인가?
이벤트 기반? 이벤트에 기반했다는 거겟지? 그러면 뭐가 이벤트에 기반해서 뭘 한다는건가?
우선 이벤트가 명확하게 뭔지 알아야겟다.
컴퓨팅에서 이벤트(event)란 프로그램에 의해 감지되고 처리될 수 있는 동작이나 사건을 말한다.
우리 세상에는 다양한 이벤트가 있다고 할 수 있다.
- 사용자가 버튼 클릭 (UI 이벤트)
- 네트워크에서 패킷 도착 (네트워크 이벤트)
- 파일이 수정됨 (시스템 이벤트)
- 타이머가 만료됨 (시간 이벤트)
이 이벤트에 따라서 작동하는 것을 이벤트 기반(Event-Driven) 이라고 한다.
위키 사전의 깔끔할 정의를 사용하면,
이벤트에 반응하여 동작을 변경하는 방식을 이벤트 드리븐(event-driven) 방식이라고 한다.
그러면 이벤트 기반 어플리케이션 큐를 뭐라고 풀어 쓰면 될까?
프로그램에 의해 감지되고 처리될 수 있는 동작이나 사건에 따라 동작을 변경하는 어플리케이션 큐.
어플리케이션이란, 사용자가 필요로 하는 특정 기능(업무, 소통, 계산, 검색 등)을 제공하기 위해 운영체제 위에서 실행되는 소프트웨어 프로그램!
큐(대기열)는 선형 자료구조중에 하나로 First In, First Out 이라는 특징을 가지고 있다.
데이터를 순차적으로 담고, 담긴 순서대로 앞에서부터 꺼내어 처리하는 방식으로 동작한다.
자! 그럼 이벤트 기반 어플리케이션 큐를 다시 정리하면 다음과 같다.
이벤트 기반 어플리케이션 큐란, 프로그램에서 감지되고 처리될 수 있는 동작이나 사건(이벤트)이 발생했을 때, 그에 따른 동작 변화를 직접 즉시 수행하지 않고, 큐(대기열) 구조에 담아 순차적으로 처리하는 방식의 소프트웨어 구성 요소를 말한다.
높은 오버헤드?
어떤 명령어를 처리하는데 소비되는 간접적, 추가적인 컴퓨터 자원을 의미한다.
locking, unlocking, polling for locks에 대해 간단히 정리한다.
- Locking = 자원 점유할 때 OS 개입 + 스레드 전환 비용 발생
- Unlocking = 대기 스레드 깨우는 과정에서 스케줄링/전환 비용 발생
- Polling for Locks = CPU가 락 풀릴 때까지 계속 체크 → 낭비
이전에 필자가 "쓰레드는 많으면 좋나요?" 시리즈에서 위와 비슷한 내용을 많이 정리했다. (궁금하면 여기!)
오버헤드가 높다는 말은 결국 어떤 명령어를 처리하는데 소비되는 간접적, 추가적인 컴퓨터 자원을 많이 소모한다는 말이다.
이벤트 큐에서 승인 제어를 수행해서 시스템 자원의 과도한 소모 방지?
이벤트 큐... 이벤트가 큐에 쌓이는 것을 이벤트 큐라고 한다.
그러면 승인제어는 뭔가?
말 그대로 이벤트 큐가 쌓인다고 해서 모두를 실행하지 않고 어떤 큐를 승인해서 실행할 것인지 제어를 하는 것을 말한다.
그렇게 되면 자연스럽게 낭비되는 실행이 없을 것이다.
즉, 시스템 자원의 과도한 소모를 방지할 수 있다.
예를 들어, 요청 수를 제한하던지, 큐에 쌓이는 이벤트 갯수를 제한하건지 등으로 할 수 있을 것이다.
4. 그 외 용어 탐구
True Connectionless Datagram?
true connectionless datagram을 지원한다는데
Datagram... UDP(User Datagram Protocol)이 떠오른다.
TCP 와 UDP에 대한 차이점은 익히 들어서 다들 알 것이다. (필자는 잘 아는척을 하고 있다)
OSI 7 Layer에서 Transport Layer 에 속하는 TCP, UDP를 기억할 것이다.
True Connectionless Datagram은 결국 UDP를 지원한다는 말과 같다.
기존의 TCP는 Three hand shakes 를 통해서 통신을 열지면 UDP의 경우에는 그냥 보내면 받거나 말거나 그만!
Throughput(처리량)
무슨 처리량을 말하는 건가? 단위 시간당 처리되거나 전송된 데이터의 양을 말한다.
이게 높으면 높을수록 고성능이겠지?
5. Java NIO 분석 - 핵심 내용 파악
우선 기존 Java NIO 에 대해 이번 장에서 좀 더 알아가려고한다. 그래야 비교를 하면서, Netty에서 지원하는 클래스들이 더 잘 이해가 될 것이다.
이전에 Netty 설명 1탄에서 NIO 에 대해서 간단하게 알고 갔다. 그건 너무 간단해서... 좀 더 여기서 알아보려고 한다.
java.nio Package 내부에 다음과 같이 많은 클래스들이 선언되어 있다.
여기서의 핵심은 Buffer, Charset, Channel이다.
각각 Buffer관련 파일은 java.nio에 있고
Charset 은 charset package에 있으며
마지막으로 Channel은 channels packcage안 에 있다.
Buffers는 data 컨테이너로서 역할을 한다.
Charsets는 decoder 그리고 encode와 연돤이 있으며 bytes와 Unicode 문자들을 번역한다.
여러 종류의 채널은 입출력 작업(I/O operations)을 수행할 수 있는 개체와의 연결을 나타낸다.
Selectors와 selection keys는 선택 가능한 채널과 함께 사용되어, 다중화된(multiplexed) 비차단(non-blocking) I/O 기능을 정의한다.
여기서 이해가 안되는 것이, 다중화된 논블로킹 기능을 정의한다는 것이다.
이를 정리하면 다음과 같다.
- Non-blocking → 스레드가 I/O 때문에 멈추지 않음.
- Multiplexed → 여러 채널을 한 번에 감시해서, 준비된 채널만 골라서 처리 가능.
그래서 "다중화된 Non-blocking 기능"이라는 말을 풀어서 말하면?
하나의 스레드가 수많은 연결(채널)을 동시에 감시하면서, 준비된 채널에서만 블로킹 없이 I/O를 처리할 수 있게 해주는 메커니즘을 의미한다.
솔직히 필자는 위의 정의가 좀 불충분하다고 느껴서 다른 정보를 좀 더 찾아보았다.
그래서 정리하면 다음과 같다.
우선 세개의 핵심 구조의 관계를 보면 다음과 같다.
Thread / Event Loop
하나의 스레드가 이벤트 루프처럼 동작해서, Selector를 호출해 준비된 채널이 있는지 감시함.
Selector
여러 채널을 등록해놓고, 어떤 채널이 읽기/쓰기 가능 상태인지 감시. select() 등의 메소드를 통해 “준비된” 채널 집합을 리턴.
Channel (SelectableChannel 계열)
네트워크 소켓, 파일 등의 I/O 대상. 논블로킹(non-blocking) 모드로 설정 가능. 데이터를 읽거나 쓰려면 버퍼와 상호작용함.
Buffer
채널로부터 읽은 데이터를 저장하거나, 채널에 보낼 데이터를 담는 메모리 버퍼. position, limit, capacity 같은 속성을 통해 현재 읽기/쓰기 위치 관리.
6. NIO예시 코드
NIO로 구성된 서버에서 호출시, IO로 구성된 서버에서 호출시 실제 성능 차이를 보기 위해 GPT를 이용해서 코드를 받았다.
Oracle 의 공식 문서 예시는 내가 원하는 Selector, Channel, Buffer를 단번에 보기 불편했기에 GPT를 사용했다.
NIO를 사용해서 코딩을 해달라 햇고, NIO에도 blocking 혹은 non-blocking을 선택할 수 있다.
MiniNioSchoServer.java
// MiniNioEchoServer.java
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
public class MiniNioEchoServer {
public static void main(String[] args) throws IOException {
// 0) 포트 결정: 인자가 없으면 기본 9090
int port = args.length > 0 ? Integer.parseInt(args[0]) : 9090;
// 1) Selector 생성: 논블로킹 채널들의 준비 상태(accept/read/write)를 모아서 알려주는 이벤트 멀티플렉서
Selector selector = Selector.open();
// 2) 서버 소켓 채널 생성 및 논블로킹 전환
ServerSocketChannel server = ServerSocketChannel.open(); // 채널 생성
server.configureBlocking(false); // 논블로킹 모드(Selector에 등록하려면 필수)
server.bind(new InetSocketAddress(port)); // 리슨 소켓 바인딩(0.0.0.0:port)
// 3) Selector에 서버 채널 등록: "새 연결 수락 가능(ACCEPT)" 이벤트만 감시
// 반환되는 SelectionKey는 (server, selector) 등록을 대표하는 토큰
server.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("[start] NIO echo server on " + port + " (single-thread)");
// 4) 이벤트 루프 시작: 준비된 채널이 생길 때까지 select()로 대기 → 깨어나면 처리
while (true) {
System.out.println("checking request....");
// select(): 최소 하나의 채널이 '준비(ready)' 상태가 될 때까지 블록(타임아웃 없이 대기)
// 준비되면 selectedKeys()에 해당 키들이 담긴다.
selector.select();
// 선택된(준비된) 키들 순회
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove(); // 현재 키를 처리 목록에서 제거(중복 처리 방지)
try {
// 5-A) 새 연결 수락 가능 상태: ServerSocketChannel + OP_ACCEPT
if (key.isAcceptable()) {
// key.channel()은 등록 당시의 채널(여기서는 ServerSocketChannel)
SocketChannel ch = ((ServerSocketChannel) key.channel()).accept(); // 즉시 수락(논블로킹: 없으면 null)
if (ch != null) {
ch.configureBlocking(false); // 클라이언트 채널도 논블로킹으로
// 연결별로 읽기 버퍼를 하나 붙여서(OP_READ 관심) 등록
// attachment(여기서는 ByteBuffer)는 다음 이벤트 때 key.attachment()로 다시 꺼내 활용
ch.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(8 * 1024));
System.out.println("[ACCEPT] " + ch.getRemoteAddress());
}
// 5-B) 읽기 가능 상태: SocketChannel + OP_READ
} else if (key.isReadable()) {
SocketChannel ch = (SocketChannel) key.channel(); // 준비된 클라이언트 채널
ByteBuffer buf = (ByteBuffer) key.attachment(); // 등록 시 붙여둔 버퍼
// 논블로킹 read:
// - >0 : 읽은 바이트 수
// - 0 : 지금은 읽을 데이터가 없음(준비 안 됨)
// - -1 : 원격에서 스트림 종료(소켓 닫힘)
int n = ch.read(buf);
if (n < 0) { // 연결 종료
ch.close();
key.cancel(); // Selector 감시 해제
continue;
}
if (n == 0) continue; // 지금은 읽을 것 없음 → 다음 이벤트 대기
// 에코 처리(학습용 단순화)
buf.flip(); // write용으로 모드 전환 (limit=현재 데이터 끝, position=0)
ch.write(buf); // 보낼 수 있는 만큼 즉시 전송(부분쓰기 발생 가능)
buf.compact(); // 남은 데이터 앞당기고 position을 끝으로 이동(다음 read 준비)
// ※ 실무 팁:
// 부분쓰기로 buf에 데이터가 남았는지 확인해 남았다면
// key.interestOps(key.interestOps() | SelectionKey.OP_WRITE)로 OP_WRITE를 켜고
// isWritable() 이벤트에서 나머지를 이어 쓰는 방식 권장.
}
} catch (IOException e) {
// 채널 I/O 예외 시 안전 종료 및 키 해제
try { key.channel().close(); } catch (IOException ignore) {}
key.cancel();
}
}
// 여기서 selectedKeys().clear()를 따로 호출할 필요는 없음(위에서 it.remove()로 모두 제거)
}
}
}
NioSpammer.java
// NioSpammer.java
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
public class NioSpammer {
public static void main(String[] args) throws IOException, InterruptedException {
if (args.length < 2) {
System.out.println("Usage: java NioSpammer <host> <port> [clients=200] [messages=5] [delayMs=200]");
return;
}
String host = args[0];
int port = Integer.parseInt(args[1]);
System.out.println("port = " + port);
int clients = args.length > 2 ? Integer.parseInt(args[2]) : 10000;
int messages = args.length > 3 ? Integer.parseInt(args[3]) : 10;
int delay = args.length > 4 ? Integer.parseInt(args[4]) : 200;
long t0 = System.currentTimeMillis();
List<SocketChannel> sockets = new ArrayList<>(clients);
for (int i = 0; i < clients; i++) {
SocketChannel ch = SocketChannel.open();
ch.configureBlocking(true); // 클라쪽은 편의상 블로킹
ch.connect(new InetSocketAddress(host, port));
sockets.add(ch);
}
System.out.println("Connected " + clients + " clients.");
ByteBuffer buf = ByteBuffer.allocate(1024);
for (int m = 0; m < messages; m++) {
for (int i = 0; i < sockets.size(); i++) {
SocketChannel ch = sockets.get(i);
String msg = "msg#" + m + " from client#" + i + "\n";
ch.write(StandardCharsets.UTF_8.encode(msg));
// 에코 수신(간단 확인)
buf.clear();
ch.read(buf);
}
if (delay > 0) Thread.sleep(delay);
}
for (SocketChannel ch : sockets) { try { ch.close(); } catch (IOException ignore) {} }
long t1 = System.currentTimeMillis();
long total = (long) clients * messages;
double sec = Math.max(0.001, (t1 - t0) / 1000.0);
System.out.printf("Sent %,d messages in %.3f s (~%,.0f msg/s)%n", total, sec, total / sec);
}
}
BlockingEchoServer.java
// BlockingEchoServer.java
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class BlockingEchoServer {
public static void main(String[] args) throws IOException {
int port = args.length > 0 ? Integer.parseInt(args[0]) : 9091;
ServerSocketChannel server = ServerSocketChannel.open();
server.configureBlocking(true);
server.bind(new InetSocketAddress(port));
System.out.println("Blocking echo server on port " + port);
ExecutorService pool = Executors.newCachedThreadPool();
while (true) {
SocketChannel client = server.accept(); // block
pool.submit(() -> {
ByteBuffer buf = ByteBuffer.allocate(8 * 1024);
try (SocketChannel ch = client) {
while (true) {
buf.clear();
int n = ch.read(buf); // block
if (n < 0) break;
buf.flip();
ch.write(buf); // block
}
} catch (IOException ignored) {
System.out.println("ignored = " + ignored);
}
});
}
}
}
뭐... 필자가 NIO와 IO로 서버를 띄우고, 여기에 요청을 하는 코드를 GPT에세 작성해달라고 햇더니 이렇게 해줫다.
intelliJ에서 vm 옵션 잘 주고 해도 되고, 코드상에서 그냥 option 들을 변경해서 사용해도 될 듯 하다.
필자는 NioSpammer 에서 clients 수를 만명으로 하고 NIO(9090) 서버와 IO(9091) 서버를 실행했는데
NIO서버는 어찌되었든 성공했고, IO서버는 실패했다.
port = 9090
Connected 1000 clients.
Sent 5,000 messages in 1.489 s (~3,358 msg/s)
port = 9090
Connected 10000 clients.
Sent 100,000 messages in 9.676 s (~10,335 msg/s)
port = 9091
Connected 1000 clients.
Sent 5,000 messages in 1.552 s (~3,222 msg/s)
port = 9091
Exception in thread "main" java.net.ConnectException: Connection refused: connect
at java.base/sun.nio.ch.Net.connect0(Native Method)
at java.base/sun.nio.ch.Net.connect(Net.java:579)
at java.base/sun.nio.ch.Net.connect(Net.java:586)
at java.base/sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:853)
at cohttp://m.example.study.netty.niotest.example1.NioSpammer.main(NioSpammer.java:29)
블로킹(IO) + “연결당 스레드” 모델은 동시 연결이 확 늘면 스레드가 사실상 무제한으로 늘어날 수 있고, 그때 스택 메모리·문맥전환·FD(소켓 핸들) 등 자원 한계로 쉽게 무너진다.
반면 NIO + Selector(이벤트 루프) 는 소수(심지어 1개)의 스레드로 “준비된 채널만” 처리하기 때문에 스레드 폭증 → 메모리 고갈 패턴으로 터질 가능성이 크게 줄어든다.
왜 NIO가 훨씬 안전하게 버티는가
- 스레드 수 고정: 이벤트 루프 1개(또는 소수) → 스택 메모리·문맥전환 비용이 거의 일정.
- 준비된 I/O만 처리: select()가 ready일 때만 깨워주니 불필요한 블로킹이 없다.
- 백프레셔 제어 용이: OP_WRITE를 켜고 끄며 부분쓰기/버퍼 잔량을 제어할 수 있다
하여간 허졉하지만 직접 테스트를 해봤다.
Blocking IO의 경우 속도는 그렇게 차이가 난거처럼 보이지 않았는데, 조금만 요청이 많이 들어와도 터져버리는 상황이 발생했다.
근본적으로 Blocking I/O, Non-Blocking I/O 에 대한 글을 찾아보면 위 결과에 대해 이해가 잘 될 것 같다.
필자는 Netty를 봐야 하니, 그냥 맛만 봤음을 이해바란다. ㅠㅠ
8. Architectural Overview(아키텍처 개요) 알아보기
우선 Architectural Overview는 다음과 같은 순서로 설명한다.
1. Rich Buffer Data Structure
- 1.1. Combining and Slicing ChannelBuffers
2. Universal Asynchronous I/O API
3. Event Model based on the Interceptor Chain Pattern
4. Advanced Components for More Rapid Development
- 4.1. Codec framework
- 4.2. SSL / TLS Support
- 4.3. HTTP Implementation
- 4.4. WebSockets Implementation
- 4.5. Google Protocol Buffer Integration
5. Summary
필자가 모든 글을 다 읽기는 힘들 것 같고, 아무래도 이 Overview만 제대로 읽어도 어느정도 구조 파악은 될 것 같다는 판단!
그래서 해당 섹션을 자세히 읽어보기로 했다. 그래서 overview니까? 그치?
6. Rich Buffer Data Structure
Netty는 NIO ByteBuffer 대신 자체 ChannelBuffer API를 제공합니다[4].
ChannelBuffer의 주요 장점
- 커스텀 버퍼 타입 정의 가능
- Zero-copy를 지원하는 Composite Buffer
- StringBuffer처럼 동적으로 확장되는 버퍼
- flip() 호출 불필요
- ByteBuffer보다 빠른 성능
[4] Netty Architecture Guide: https://netty.io/3.8/guide/#architecture
Netty는 NIO에서 ByteBuffer 대신에 Netty만의 buffer api 를 사용한다.
이 접근은 ByteBuffer 사용하는 것 대신에 중요한 이점을 가진다.
ByteBuffer의 문제를 해결하기 위해 디자인된 ChannelBuffer를 사용하는데 몇가지 멋진 특징이 있다.
- 필요하다면 자신만의 버퍼를 정의할 수 있다.
- 투명한 zero copy 가 내장된 컴포지트 버퍼 타입에 의해 구현된다.
- 동적 버퍼 타입이 바로 제공된다. StringBuffer처럼 용량이 요구에 따라 확장될 수 있다.
- 더이상 flip()을 호출할 필요가 없다.
- ByteBuffer보다 자주 빠르다.
여기서 NIO의 ByteByffer와 Netty에서 제공하는 ByteBuffer가 다르다는데,
먼저 NIO에서 제공하는 ByteBuffer가 뭐고 사용법이 뭐고 마지막으로 문제점이 무엇인지 알아야 해당 글이 이해가 될 것 같다.
너무 글이 길어진 관계로 다음 글에서 다시 이어서 공부해보도록 하겠다.
이번에는 Netty의 특징에 대해 알아보았고, 구조 설명을 들어다 보았다. 그리고 NIO도 간단하게 사용해보았다.
참고한 글에서는 사실 나무위키 글도 아주 조금 있다. 필자는 해당 사이트를 제대로 신뢰할 수는 없으나, 오버헤드에 대한 간결한 설명이 마음에 들어서 참고하였다.
또한, 그 방대한 공식문서를 전부 다 읽고, 이것의 컨셉들에 대해서 명확한 근거를 찾는 것은 할 수 없었기에 GPT의 도움을 받아서
해당 정보들을 찾았다. (압도적 감사! GPT!) 물론 GPT에게 궁금한 부분을 물어봐서 긁어 넣을 수 있지만...
그것이 나에게 도움이 되는지도 의문이거니와, 실제 공식문서를 통해서 봐야지 믿음이 가기에 그렇게 진행했다.
물론, 예시는 그냥... gpt사용해서 물어보고 돌려보고 아 그런갑다... 했다.
마지막으로, 공식문서의 NIO부분을 일일히 다 분석하고 보기에는 너무 많은 노고(?)가 필요하기에 그렇게까지는 하지 못했다.
궁금한 것이 있으면 해당 사이트를 참고하면 된다.
기존의 코딩과도 좀 많이 다르거니와 생각보다 NIO라는가 아직도 나에게는 와닿지 않는것 같다. 관련 글에 대해 더 찾아보고 혼자 공부하도록 할 예정이다.
다음 시간에는 이번 장에서 이야기 하지 못했던 부분들에 대해서 이어서 설명하도록 하고, 공식문서에서 소개하는 구조에 대해 알아보고자 한다.
'Spring > Spring Framework' 카테고리의 다른 글
[Netty] Netty가 뭐에요? - 4탄 : Spring MVC 와 Spring Web Flux 그리고 Netty 예시 코드 탐방 (0) | 2025.10.07 |
---|---|
[Netty] Netty가 뭐에요? - 2탄 : Netty가 필요하기 까지 (2) | 2025.09.24 |
[Netty] Netty가 뭐에요? - 1탄 : Netty 란? - 용어 정리 (3) | 2025.09.18 |