Java Blocking I/O
전통적인 Java Blocking I/O는 방식에서는 클라이언트의 요청을 처리하는 주요 메서드들이 모두 동기적으로 작동합니다.
클라이언트 연결 수락 ( ServerSocket.accept() )
서버는 ServerSocket.accept() 호출하여 커널에서 클라이언트의 연결 요청이 도착할 때까지 블로킹됩니다.
연결이 수립되면 커널은 Socket 객체를 생성하여 서버에 반환합니다.
Socket 객체 : 클라이언트와 서버의 연결 정보이자 통로
Socket 객체와 Stream
Socket 객체가 제공하는 Stream을 통해 데이터를 송수신합니다.
InputStream : 클라이언트가 보낸 데이터를 서버가 읽는 통로
OutputStream : 서버가 클라이언트에게 데이터를 보내는 통로
스트림은 단방향으로 동작하며, 실제 TCP 기반 데이터 송수신은 이 스트림을 통해 이루어집니다.
데이터 송수신

InputStream.read()을 통해 클라이언트의 데이터를 읽으며, OutputStream.write()를 통해 클라이언트에 데이터를 전송합니다.
두 메서드는 모두 작업이 완료될 때까지 스레드가 블로킹됩니다.
[ 전체 BIO 서버 흐름 ]
서버가 accept() 호출
클라이언트가 접속할 때까지 블로킹
커널이 연결을 생성하고 Socket 반환
서버는 해당 Socket의 InputStream / OutputStream 으로 통신
read() 와 write() 도 모두 블로킹
Blocking I/O의 문제는 요청마다 전용 스레드가 하나씩 필요한 Request-per-Thread 구조이기 때문에 동시 요청이 많아질수록 스레드 수가 급격히 증가하고, 이에 따른 컨텍스트 스위칭 비용도 커져 CPU 사용량이 불필요하게 높아집니다.
또한 accept(), read(), write()와 같은 I/O 작업이 완료될 때까지 스레드가 블로킹되어 다른 작업을 수행하지 못하는 것 역시 문제입니다.
Java NIO ( New Input/Output )

Java NIO는 Non-blocking을 지원하고, Selector, Channel을 도입하여 높은 성능을 보장합니다.
또한 Blocking I/O에서 스트림은 단방향으로 데이터를 처리하지만, Java NIO에서는 양방향의 Channel을 사용하여 처리합니다
[ Channel ]

Channel은 I/O 작업을 수행하기 위한 핵심 컴포넌트로, 클라이언트와 서버 사이의 연결 정보를 나타내며 그 위에서 하나 이상의 입출력 작업을 처리할 수 있습니다.
양방향 통신을 지원하며, Selector와 연동하여 단일 스레드에서 여러 채널을 관리할 수 있습니다.
ServerSocketChannel : 서버 측에서 클라이언트의 연결 요청을 처리
SocketChannel : 클라이언트와 서버 간의 데이터 송수신을 처리
위 두 Channel은 SelectableChannel를 상속하는데, 이 SelectableChannel는 아래와 같은 두 가지 메서드를 지원합니다.
configureBlocking
register
[ Selector ]

하나의 스레드가 여러 Channel의 I/O 이벤트를 감시하고 처리할 수 있게 해주는 Java NIO의 핵심 컴포넌트
이를 사용함으로써, 단일 스레드에서 여러 요청을 처리할 수 있습니다.
Selector는 자신에게 등록된 채널에서 I/O 이벤트가 발생했는지 감시하며, 이벤트가 발생한 채널을 조회하여 해당 채널에 접근할 수 있도록 해줍니다.
이는 적은 수의 스레드로 더 많은 연결을 처리할 수 있으므로 메모리 관리와 컨텍스트 전환에 따르는 오버헤드가 감소하며, 입출력을 처리하지 않을 때는 스레드를 다른 작업에 활용할 수 있습니다.
[ Java NIO에서의 요청 처리 과정 ]
ServerSocketChannel 생성 및 논블로킹 모드 설정
서버는 ServerSocketChannel을 생성한 뒤 논블로킹 모드로 전환하여, I/O 작업에서 스레드가 블로킹되지 않도록 합니다.
Selector 생성 및 ServerSocketChannel을 OP_ACCEPT로 등록
Selector를 생성하고, ServerSocketChannel을 OP_ACCEPT 이벤트와 함께 등록합니다.
OP_ACCEPT 이벤트로 등록한다는 것은 새로운 연결이 가능한 상태가 되었을 때만 이벤트를 받고 싶다는 의미입니다.
즉, TCP 3-way handshake가 모두 완료된 상태
Selector의 select() 호출
selector.select()는 등록된 채널들 중에서 준비된 I/O 이벤트가 발생할 때까지 대기합니다.
selectedKeys()로 준비된 이벤트 조회 및 처리
selectedKeys()는 준비된 이벤트들의 목록을 Set 형태로 제공하며, 이를 iterator로 순회하면서 채널별로 필요한 처리 로직을 실행합니다.
OP_ACCEPT 처리 → SocketChannel 생성 및 등록
Selector가 OP_ACCEPT 준비됨 이벤트를 감지하면, 서버가 serverSocketChannel.accept()를 호출하여 새로운 SocketChannel이 생성됩니다. ( 요청 하나 당 SocketChannel 1개 생성 )
생성된 소켓 채널은 논블로킹 모드로 설정한 뒤, OP_READ 이벤트로 Selector에 다시 등록합니다.
이후 클라이언트가 데이터를 보낼 준비가 되면 Selector가 이를 감지하여, OP_READ 이벤트를 통해 읽기 작업을 수행할 수 있게 됩니다.
ServerSocketChannel은 서버에 하나만 존재하며, 클라이언트 요청이 들어올 때마다 이를 통해 새로운 SocketChannel이 생성하여 실제 데이터 송수신을 담당하게 합니다.
즉, NIO는 하나의 스레드가 여러 요청의 I/O 이벤트를 감시하고 처리할 수 있는 구조이며, 내부 I/O 작업도 논블로킹 방식으로 동작하기 때문에 매우 많은 요청을 효율적으로 처리할 수 있습니다.

Java 기반으로 구현된 비동기/논블로킹 방식의 Event-Driven 네트워크 애플리케이션 프레임워크
프레임워크이지만 자체적으로 서버 기능을 수행할 수 있는 구조이며, Spring WebFlux에서도 기본 내장 서버로 Netty를 사용합니다.
Netty는 이벤트 루프 모델을 사용하기 때문에, Spring WebFlux가 논블로킹 방식으로 높은 효율성과 확장성을 제공할 수 있도록 하는 기반이 됩니다.
이벤트 루프 모델은 요청마다 스레드를 생성하거나 할당하는 Tomcat과 달리, 단일 비차단 스레드로 여러 요청을 동시에 처리할 수 있는 구조를 갖습니다.
→ 최소한의 리소스로 수많은 동시 요청을 효율적으로 처리할 수 있습니다.
Event Driven
이벤트 발생에 반응하여 동작하도록 설계된 방식 ( 이벤트 발생 → 처리의 흐름 )
EventLoop Group
여러 EventLoop를 관리하는 컨테이너
BossGroup
클라이언트의 연결 요청을 수락하고, 새로운 연결을 생성한 뒤 처리를 WorkerGroup으로 위임합니다.
BossGroup은 일반적으로 하나의 EventLoop( 단일 스레드 )로 구성되며, 이 EventLoop가 ServerSocketChannel을 감시하여 OP_ACCEPT 이벤트( 연결 요청 )를 처리합니다.
WorkerGroup
실제 I/O 작업 및 채널과 관련된 이벤트를 처리합니다.
일반적으로 CPU 코어 수만큼의 EventLoop가 생성되며, 각 EventLoop( 스레드 )는 여러 Channel을 동시에 관리하고 처리합니다.
즉, BossGroup은 연결 수락 역할을 하며, WorkerGroup은 실제 비즈니스 로직과 I/O 처리를 담당합니다.
각 Channel은 WorkerGroup 내의 하나의 EventLoop에 고정적으로 바인딩되며, 해당 Channel로 들어오는 모든 I/O 이벤트는 항상 동일한 EventLoop에서 처리됩니다.
EventLoop
Netty의 핵심 구성 요소이자 실행 단위이며, 하나의 전용 스레드 + Selector + TaskQueue로 구성됩니다.
( EventLoop는 객체 )

EventLoop 객체 내에 스레드가 포함된게 아니라 바인딩 되어있는 것
Selector
EventLoop는 내부적으로 Selector가 존재하며, 이 Selector는 EventLoop에 바인딩된 Channel들의 I/O 이벤트를 감지합니다.
이후 이 이벤트를 EventLoop의 단일 스레드가 처리합니다.
TaskQueue
I/O 외의 사용자 애플리케이션 코드와 같은 일반적인 작업인 task를 저장하는 큐로, TaskQueue에 저장하였다가 EventLoop의 스레드가 순차적으로 실행합니다.
I/O 작업은 TaskQueue에 저장하여 처리하지 않고 즉시 처리합니다.

단일 스레드로 동작하는 반복 루프 구조로, 해당 스레드가 Selector를 통해 특정 Channel에서 발생한 I/O 이벤트를 감지하고 이를 내부 이벤트 큐에 저장한 뒤 순차적으로 처리합니다.
자세한 요청 처리 과정
요청이 들어오면 BossGroup의 EventLoop 스레드가 연결 요청을 accept 하여 새로운 Channel을 생성하고, 이를 WorkerGroup의 특정 EventLoop에 할당 합니다.
이후 WorkerGroup에 바인딩된 Channel에서 I/O 이벤트가 발생하면, 해당 EventLoop의 Selector가 이벤트를 감지하고, 같은 EventLoop 스레드가 해당 이벤트를 처리합니다.
처리 과정에서 이벤트는 ChannelPipeline으로 전달되며, Pipeline에 등록된 ChannelHandler들이 체인 형태로 순서대로 실행되어 요청/응답을 처리합니다.