본문 바로가기
프로그래밍/네트워크 프로그래밍

네트워크 I/O 모델

by eteo 2026. 2. 10.
반응형

 

 

네트워크 I/O 모델

 

네트워크 시스템의 성능을 결정짓는 핵심 요소 중 하나가 I/O(Input/Output) 모델이다. 이번 글에서는 UNIX 시스템에서 전통적으로 나누는 5가지 I/O 모델에 대해 알아보고, 각 모델의 차이를 정리하고자 한다.

 

먼저 5가지 기본 I/O 모델은 다음과 같다.

 

  1. Blocking I/O
  2. Nonblocking I/O
  3. I/O Multiplexing (select, poll)
  4. Signal Driven I/O (SIGIO)
  5. Asynchronous I/O (aio_)

 

보통 I/O 모델에 대해 논할 때는 출력보다는 입력 위주로 설명한다. 출력(Write)의 경우 커널의 송신 버퍼에 빈공간이 있으면 대부분 즉시 커널로 데이터를 복사하고 리턴되지만, 입력(Read)의 경우 데이터가 언제 도착할지는 전적으로 상대방에게 달려 있어서 차단되는 상황이 훨씬 더 자주 일어나기 때문이다.

 

 

입력 작업은 다음의 두 단계로 나뉜다.

  • 1단계 (Wait for data) : 네트워크를 통해 데이터가 도착하기를 기다림
  • 2단계 (copy data from kernel to user) : 커널 수신 버퍼에 있는 데이터를 애플리케이션 버퍼로 복사함

아래 I/O 모델을 설명하는 그림에 이 두 단계는 계속 언급된다.

 

 

 

 

 

 

 


1. Blocking I/O

 

 

가장 기본적인 소켓의 동작 방식이다. 프로세스가 recv 시스템 콜을 호출하면, 데이터가 도착해서 유저 버퍼로 복사될 때까지 리턴되지 않고 프로세스가 멈춘다.

 

1:1 통신이거나 동시 연결 수가 매우 제한된 경우에는 Blocking I/O 모델을 멀티스레딩과 함께 사용하는 경우가 존재한다.

 

ssize_t n = recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);

 

 

 

 

 

 

 


2. Non-blocking I/O

 

 

먼저 소켓을 non-blocking 모드로 설정하면, 커널에 I/O 작업을 완료할 수 없을 경우 프로세스를 sleep 시키지 말고 즉시 에러를 반환하라고 지시하는 것이다.

 

이후 해당 소켓 디스크립터에 대해 recv 함수를 호출하면, 데이터가 준비되지 않은 경우 즉시 -1을 반환하며 errno는 EWOULDBLOCK으로 설정된다. 반면, 데이터가 준비된 경우는 유저 버퍼로 복사한 뒤 정상적으로 반환된다.

 

이 모델은 애플리케이션이 성공할 때까지 recv를 반복 호출해서 주기적으로 상태를 확인하는 polling 구조로, CPU 사용량이 증가하기 때문에 흔히 쓰이는 방식은 아니다.

 

fcntl(sockfd, F_SETFL, O_NONBLOCK);

while (1) {
    ssize_t n = recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);

    if (n > 0) {
        process(buf, n);
        break;
    }

    if (errno != EWOULDBLOCK && errno != EAGAIN) {
        perror("recvfrom");
        break;
    }
}

 

 

 

 

 

 

 


3. I/O Multiplexing

 

 

I/O Multiplexing 모델은 실제 I/O 시스템 콜(recv)에서 차단되는 대신 select나 epoll 시스템 콜에서 차단되는 방식이다.


select를 호출해서 여러 소켓 중 하나라도 데이터가 준비될 때까지 block된 채로 대기하고, select가 리턴하여 특정 소켓이 readable하다는 신호를 받으면, 그 때 recv 함수를 호출해서 데이터를 유저 버퍼로 복사한다.

 

이 모델은 select(또는 epoll)과 recv라는 두 번의 시스템 콜을 사용하며, 하나의 스레드에서 여러 소켓을 동시에 감시할 수 있다는 장점이 있다.

 

대용량 트래픽 환경을 포함에 현재 가장 널리 사용되는 I/O 모델이고, 이벤트 통지 기반이기 때문에 비동기 I/O처럼 인식되기도 하지만 실제는 동기 방식이다.

 

fd_set rfds;
FD_ZERO(&rfds);
FD_SET(sockfd, &rfds);

select(sockfd + 1, &rfds, NULL, NULL, NULL);

if (FD_ISSET(sockfd, &rfds)) {
    recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);
}

 

 

 

 

 

 

 


4. Signal-driven I/O

 

 

Signal Driven I/O 모델은 소켓에 데이터가 도착했을 때, 커널이 프로세스에게 시그널(SIGIO) 을 보내 I/O 이벤트를 통지하는 방식이다. 

 

애플리케이션은 미리 시그널 핸들러를 등록하고, 소켓을 signal-driven I/O 모드로 설정해 둔다. 이후 데이터가 준비되면 커널이 SIGIO 시그널을 발생시키며, 애플리케이션은 recv를 호출해 데이터를 유저 버퍼로 복사한다. 즉, I/O 가능 여부는 시그널로 통지받지만, 실제 데이터 복사는 여전히 동기적으로 수행된다.

 

이 모델은 epoll의 등장 이후 현재는 거의 활용되지 않는 방식이다.

 

void sigio_handler(int signo) {
    recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);
}

struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = sigio_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;

sigaction(SIGIO, &sa, NULL);

fcntl(sockfd, F_SETOWN, getpid());
fcntl(sockfd, F_SETFL, O_ASYNC);

 

 

 

 

 

 

 


5. Asynchronous I/O

 

 

Asynchronous I/O 모델에서는 애플리케이션이 커널에 I/O 작업을 요청한 뒤, 데이터가 커널 버퍼에서 유저 버퍼로 복사되는 과정까지 모두 커널이 처리하며, 작업이 완전히 종료된 시점에 이를 통지받는다.


앞서 본 I/O Multiplexing 모델이나, Signal Driven I/O 모델에서는 커널이 “I/O를 시작할 수 있음” 을 통지하는 반면, Asynchronous I/O 모델은 “I/O 작업이 완전히 완료되었음” 을 통지한다는 점이 가장 큰 차이점이다.


이 모델은 POSIX 표준에 정의된 유일한 비동기 I/O에 해당하지만, 주로 대용량 파일 I/O와 같이 지연 시간이 큰 작업에 제한적으로 사용되며, 실제로 리눅스에서 배포되는 대부분의 네트워크 서비스들은 I/O Multiplexing 모델의 epoll을 사용한다.

 

int fd = open("test.txt", O_RDONLY);

char buf[128];
struct aiocb cb;
memset(&cb, 0, sizeof(cb));

cb.aio_fildes = fd;
cb.aio_buf    = buf;
cb.aio_nbytes = sizeof(buf);
cb.aio_offset = 0;

aio_read(&cb);

while (aio_error(&cb) == EINPROGRESS) {
    printf("doing other work...\n");
    sleep(1);
}

int n = aio_return(&cb);
write(STDOUT_FILENO, buf, n);

close(fd);

 

 

 

 

 

 

 


모델 비교 및 요약

 

 

POSIX에서는 동기 I/O와 비동기 I/O를 다음과 같이 정의한다.

  • 동기(Synchronous) I/O : I/O 작업이 완료될 때까지 요청한 프로세스를 차단한다. (I/O 완료까지 커널이 제어권 반환 안함)
  • 비동기(Asynchronous) I/O : I/O 작업을 요청한 뒤에도 요청한 프로세스를 차단하지 않는다. (즉시 커널이 제어권 반환)

 

이 정의에 따르면 처음 네 가지 모델은 다 동기 I/O에 해당한다. 실제 I/O 작업을 수행하는 recv 호출에서 프로세스가 차단되기 때문이다. 이 중 3번과 4번 모델은 I/O 가능 여부를 먼저 통지받은 뒤 recv를 호출하는 구조이므로, 일반적인 경우 호출이 거의 즉시 반환되어 비차단처럼 느껴질 수 있다. 그러나 실제로는 I/O 요청 시점 커널이 제어권을 갖고, I/O 작업이 완료될 때까지 커널이 제어권을 반환하는 동기 방식이라는 점에는 변함이 없다.

 

입력 작업의 단계별 차단 여부에 따라 표로 정리하면 다음과 같다.

 

I/O 모델 1단계 2단계 분류
Blocking I/O 차단 차단 동기
Non-blocking I/O 비차단 차단 동기
I/O Multiplexing 차단 (select/epoll) 차단 동기
Signal-driven I/O 비차단 (시그널 통지) 차단 동기
Asynchronous I/O 비차단 비차단 비동기

 

 

즉, 이 중 Asynchronous I/O 모델만이 1단계(데이터 대기 단계)와 2단계(데이터 복사 단계) 모두에서 프로세스를 차단하지 않는 유일한 비동기 모델이다.

 

 

 

I/O모델을 낚시로 비유해보면 다음과 같다.

  • Blocking I/O : 낚싯대 하나를 들고 물고기가 잡힐 때까지 가만히 기다림
  • Non-blocking I/O : 낚싯대를 계속 던졌다 뺐다 하면서 물고기가 걸렸는지 직접 확인함
  • I/O Multiplexing : 낚싯대를 여러 개 펼쳐 놓고 기다리다가, 신호가 오면 그 낚싯대를 들어서 잡음
  • Signal-driven I/O : 낚싯대 하나에 알람을 달아두고 다른 일을 하다가 알림이 오면 와서 잡음
  • Asynchronous I/O : 낚싯대를 다른 사람한테 맡겨두고 자리를 떠난 뒤, 그 사람이 물고기를 잡고 통에 넣는 것 까지 다 끝냈다는 연락이 오면 돌아와서 확인함

 

 

 

참고 문서 :

https://notes.shichao.io/unp/ch6/#io-models

 

반응형