1. 동기 vs 비동기 I/O 비교
동기 I/O 모델은 I/O 작업이 완료될 때까지 호출 스레드가 차단된다.
구현이 단순하지만 확장성에 제한이 있다.
ex) 동기 소켓 통신 코드
// 동기 소켓 통신 예시 (개념적 의사코드)
socket = createSocket();
connect(socket, serverAddress); // 연결될 때까지 차단
send(socket, data); // 데이터가 전송될 때까지 차단
response = receive(socket); // 응답을 받을 때까지 차단
비동기 I/O 모델은 I/O 요청 후 즉시 제어를 반환하고, 작업 완료 시 알림을 받는다.
복잡하지만 높은 확장성과 처리량을 제공한다.
ex) 비동기 소켓 통신 코드
// 비동기 소켓 통신 예시 (개념적 의사코드)
socket = createSocket();
// 연결 요청을 보내고 즉시 반환 (차단하지 않음)
connectAsync(socket, serverAddress, () => {
console.log("연결 완료!");
// 데이터 전송 요청을 보내고 즉시 반환
sendAsync(socket, data, () => {
console.log("데이터 전송 완료!");
// 데이터 수신 준비 후 즉시 반환
receiveAsync(socket, (response) => {
console.log("데이터 수신 완료!");
processResponse(response);
});
});
});
// 소켓 연결/전송/수신을 기다리지 않고 다른 작업 수행 가능
console.log("다른 작업 진행 중...");
doOtherTasks();
2. I/O 모델의 진화 (블로킹, 논블로킹, 이벤트 기반, IOCP)
1) 블로킹 I/O:
- 가장 기본적인 형태로, I/O 작업 완료까지 스레드가 대기한다.
- 간단하지만 리소스 사용 효율이 낮다.
- 다중 클라이언트 처리를 위해선 클라이언트당 스레드가 필요해 확장성 문제가 발생한다.
장점: 구현이 직관적이고 프로그래밍 모델이 단순하다.
2) 논블로킹 I/O:
- 즉시 반환되지만 완료 여부를 지속적으로 확인해야 한다.
- 폴링 오버헤드가 발생한다.
- CPU 사용량이 높아질 수 있으며, 폴링 간격에 따라 응답 지연이 발생할 수 있다.
장점: 단일 스레드로 여러 I/O 작업을 관리할 수 있다.
3) 이벤트 기반 I/O:
- select(), poll(), epoll(Linux) 등을 사용해 다수의 I/O 채널을 모니터링한다.
- 확장성이 개선되지만 이벤트 처리 로직이 복잡하다.
- 비동기 이벤트 처리를 위한 콜백 또는 이벤트 핸들러 구조가 필요하다.
- Node.js의 이벤트 루프, 리액터 패턴 등이 이 모델을 기반으로 한다.
장점: 적은 수의 스레드로 많은 연결을 효율적으로 처리할 수 있다.
4) IOCP (I/O Completion Ports):
- 완료 기반 모델로, I/O 작업이 완료되면 커널이 결과를 큐에 넣고 대기 중인 스레드에게 알린다.
- 최고의 확장성과 효율성을 제공한다.
- Windows에서 주로 사용된다.
- 스레드 풀과 결합하여 최적의 동시성을 제공한다.
장점: 높은 처리량과 효율적인 CPU 사용, 확장성이 뛰어나다.
3. 현실세계의 예시
각각의 방식을 현실세계의 식당에 비유해 개념을 간단하게 소개해보려고 한다.
여기서 웨이터는 서버의 스레드, 손님은 IO 작업이다.
1) 블로킹 I/O - 단일 웨이터, 순차 처리
상황: 식당에 손님들이 줄 서 있고, 웨이터가 한 명만 있다.
- 손님이 주문을 하면, 웨이터는 주방에서 음식을 받기까지 기다린다. 음식을 받은 후에야 다음 손님을 처리한다.
- 핵심: 웨이터가 한 명씩 순차적으로 손님을 처리하므로, 한 명의 손님이 음식을 받을 때까지 다른 손님들은 대기해야 한다.
- 장점: 시스템이 단순하고 직관적이다.
- 단점: 손님이 많아질수록 웨이터가 대기하는 시간이 많고, 동시에 여러 손님을 받기 위해서는 같은 수의 웨이터가 필요하다.
2) 논블로킹 I/O - 계속 주방을 확인하는 웨이터
상황: 식당에 손님들이 줄 서 있고, 웨이터가 한 명만 있다.
- 웨이터가 주문을 받고 주방에 전달한 후, 음식이 준비되는 동안 다른 손님을 응대하다가 중간중간 주방에 가서 음식이 준비되었는지 확인한다.
- 주방에 물어보면 "아직 준비 중이에요" 또는 "음식이 준비되었어요"라고 답변을 받는다.
- 핵심: 웨이터는 한 손님의 음식이 준비되는 동안 다른 손님을 응대할 수 있지만, 음식 상태를 계속 확인해야 한다.
- 장점: 웨이터가 하나여도 여러 손님을 처리할 수 있다.
- 단점: 웨이터가 계속 주방을 확인해야 하므로, 너무 자주 확인하면 시스템 자원(CPU)을 낭비할 수 있다.
3) 이벤트 기반 I/O - 모니터링 화면을 사용하는 대기 시스템
상황: 손님들이 줄 서서 기다리며, 한 명의 웨이터가 여러 주문을 관리한다.
- 웨이터는 주방과 연결된 모니터 화면을 보고 있다가, 화면에 음식이 준비되었다는 알림이 뜨면 해당 손님에게 음식을 서빙한다.
- 이 한 명의 웨이터는 여러 손님의 상태를 한 번에 모니터링하며 준비된 음식에만 반응한다.
- 핵심: 웨이터는 모니터링 시스템을 통해 여러 주문을 동시에 관찰하고, 준비된 음식이 있을 때만 행동한다.
- 장점: 웨이터는 여러 손님을 동시에 관리할 수 있으며, 불필요한 대기 시간을 최소화할 수 있다.
- 단점: 모니터링 시스템을 설정하고 관리해야 하므로 시스템이 다소 복잡할 수 있다.
4) IOCP (I/O Completion Ports) - 완료 목록(큐) 기반 웨이터 시스템
상황: 식당에 여러 웨이터가 있고, 중앙 관리 시스템이 웨이터들을 효율적으로 관리한다.
- 손님이 주문을 하면, 주문이 주방으로 전달된다.
- 음식이 준비되면 중앙 관리 시스템은 '완료된 주문' 목록에 해당 주문을 넣는다.
- 대기 중인 웨이터 중 한 명이 이 목록에서 완료된 주문을 가져가 서빙한다.
- 웨이터들은 다른 일을 할 필요 없이 목록에서 완료된 주문만 확인하면 된다.
- 핵심: 시스템이 자동으로 완료된 주문을 목록에 넣고, 웨이터들은 이 목록에서 효율적으로 작업을 가져간다.
- 장점: 많은 손님을 빠르고 효율적으로 처리할 수 있으며, 시스템 자원을 효율적으로 관리한다.
- 단점: 시스템 설정이 복잡하고, 최적화를 위해 많은 자원과 노력이 필요하다.
정리
- 블로킹 I/O: 한 명의 웨이터가 한 손님씩 순차적으로 처리하는 방식으로, 대기 시간이 길고 확장이 어렵다.
- 논블로킹 I/O: 웨이터가 주방을 계속 확인하여 대기 시간을 줄이는 방식으로, 리소스 낭비가 있을 수 있다.
- 이벤트 기반 I/O: 음식 준비 알림 시스템을 통해 웨이터가 준비된 음식에만 반응하는 방식으로, 효율적으로 손님을 처리하지만 시스템이 복잡하다.
- IOCP: 완료 큐 기반 시스템을 이용해 손님과 웨이터의 작업을 자동으로 최적화하여 빠르고 효율적인 처리가 가능하지만, 시스템 설계가 복잡하다.
'학습 > C++ 소켓 프로그래밍' 카테고리의 다른 글
IOCP (0) | 2025.03.30 |
---|---|
Overlapped I/O (0) | 2025.03.30 |
Non-Blocking 소켓 (0) | 2024.11.09 |
소켓 옵션 (0) | 2024.11.08 |
UDP 소켓 프로그래밍 (0) | 2024.10.31 |