0Pricing
C Academy · 강의

이벤트 루프

읽을 수 있는 소켓에 반응해 보세요.

이벤트 루프은(는) CoddyKit의 무료 C Academy 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 C Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. C Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

서버의 핵심

이벤트 루프는 epoll 서버를 작동시키는 엔진입니다. 준비된 디스크립터를 반복해서 기다린 다음 작업을 처리기에 전달합니다.

구조는 항상 같습니다. epoll_wait()를 호출하고, 반환된 이벤트를 순회하며 각각에 대응한 뒤, 다시 반복합니다. 나머지 모든 기능은 이 뼈대에 연결됩니다.

for (;;) {
    int n = epoll_wait(epfd, events, MAX_EVENTS, -1);
    for (int i = 0; i < n; i++) handle(&events[i]);
}

epoll_wait 호출하기

epoll_wait()에는 epoll fd, epoll_event의 출력 배열, 배열의 용량, 밀리초 단위의 제한 시간이 필요합니다.

하나 이상의 디스크립터가 준비되거나 제한 시간이 만료될 때까지 차단하고, 배열에 채워진 이벤트 수를 반환합니다. 반환된 개수만큼의 항목만 유효합니다.

struct epoll_event events[MAX_EVENTS];
int n = epoll_wait(epfd, events, MAX_EVENTS, -1);
if (n == -1) { perror("epoll_wait"); }

제한 시간 인수

-1의 제한 시간은 이벤트가 도착할 때까지 무기한 차단합니다. 0은 즉시 반환되어 대기 없이 상태를 확인합니다.

양수 값은 대기할 수 있는 최대 시간을 정합니다. 로그를 비우거나 유휴 연결을 만료시키는 등의 주기적 작업도 실행해야 할 때 유용합니다.

int n = epoll_wait(epfd, events, MAX_EVENTS, 1000); /* up to 1s */
if (n == 0) run_periodic_tasks();

이벤트 배열 크기 정하기

배열 용량은 한 번의 epoll_wait() 호출이 보고할 수 있는 준비된 이벤트 수의 상한입니다. 준비된 이벤트가 더 많으면 나머지는 다음 호출에서 반환됩니다.

64 또는 1024 정도의 적당한 크기면 충분합니다. epoll은 호출마다 대기 중인 이벤트를 공정하게 번갈아 처리하므로 어떤 이벤트도 계속 밀려나지 않습니다.

#define MAX_EVENTS 64
struct epoll_event events[MAX_EVENTS];

수신 대기 소켓에서 처리 분기하기

루프 안에서는 먼저 준비된 디스크립터가 수신 대기 소켓인지 확인합니다. 그렇다면 새 연결이 대기 중이라는 뜻입니다.

연결을 수락하고, 논블로킹으로 설정한 뒤, epoll에 등록합니다. 그 밖의 디스크립터는 읽을 데이터가 있거나 쓸 공간이 있는 기존 클라이언트입니다.

if (events[i].data.fd == listen_fd) {
    accept_new_connections(epfd, listen_fd);
} else {
    handle_client(epfd, &events[i]);
}

루프에서 연결 수락하기

수신 대기 소켓의 준비 이벤트 하나가 여러 연결의 대기를 의미할 수 있습니다. accept()가 EAGAIN을 반환할 때까지 반복 호출합니다.

이는 에지 트리거 방식에서 필수이며 어디서나 좋은 관행입니다. 그래야 다음 깨우기까지 클라이언트를 기다리게 하지 않습니다.

for (;;) {
    int c = accept(listen_fd, NULL, NULL);
    if (c == -1) { if (errno == EAGAIN) break; else break; }
    set_nonblocking(c);
    add_to_epoll(epfd, c);
}

읽을 수 있는 이벤트 처리하기

클라이언트에서 EPOLLIN이 발생하면 데이터를 읽습니다. 수준 트리거 방식에서는 한 번 깨어날 때 recv()를 한 번만 호출해도 됩니다.

반환값이 0이면 클라이언트가 연결을 닫은 것이므로 정리합니다. 음수 반환값과 함께 EAGAIN이 발생하면 현재 읽을 수 있는 데이터를 모두 읽었다는 뜻입니다.

ssize_t r = recv(fd, buf, sizeof buf, 0);
if (r == 0) { close_conn(epfd, fd); }
else if (r > 0) { process(buf, r); }

에지 트리거 방식에서 데이터 모두 읽기

EPOLLET을 사용하면 recv()가 EAGAIN을 반환할 때까지 읽어야 합니다. 커널은 읽을 수 있는 상태로 전환될 때만 알리므로, 남은 바이트는 다음 상태 변경 전까지 놓치게 됩니다.

읽기 작업을 루프로 감싸고 EAGAIN이 발생하거나 스트림이 끝날 때만 루프를 빠져나옵니다.

for (;;) {
    ssize_t r = recv(fd, buf, sizeof buf, 0);
    if (r > 0) process(buf, r);
    else if (r == 0) { close_conn(epfd, fd); break; }
    else { if (errno == EAGAIN) break; else { close_conn(epfd, fd); break; } }
}

쓸 수 있는 이벤트 처리하기

소켓이 더 많은 송신 데이터를 받을 수 있을 때 EPOLLOUT이 발생합니다. 앞서 완전히 전송되지 않은 바이트를 버퍼에 보관하고 있을 때만 이를 감시해야 합니다.

버퍼를 비우고, 버퍼가 비면 지속적으로 쓰기 가능 이벤트가 발생하는 것을 막기 위해 EPOLL_CTL_MOD로 관심 이벤트를 EPOLLIN만 감시하도록 되돌립니다.

if (events[i].events & EPOLLOUT) {
    flush_pending(fd);
    if (buffer_empty(fd)) watch_read_only(epfd, fd);
}

중단된 대기 처리하기

신호로 인해 대기가 중단되면 epoll_wait()가 errno == EINTR과 함께 -1을 반환할 수 있습니다. 이는 실제 오류가 아니므로 다시 시도하면 됩니다.

견고한 루프는 EINTR이 발생하면 계속하고, 다른 오류 코드만 치명적인 오류로 처리합니다. 이를 잊으면 서버가 신호를 처음 받는 순간 종료될 수 있습니다.

int n = epoll_wait(epfd, events, MAX_EVENTS, -1);
if (n == -1) {
    if (errno == EINTR) continue;
    perror("epoll_wait"); break;
}

하나의 루프로 여러 클라이언트 처리하기

이제 하나로 합치면 단일 스레드가 준비된 디스크립터를 영원히 순환하며 처리합니다. 수락하고, 읽고, 쓰고, 닫는 작업을 반복합니다.

이 설계에서는 유휴 상태일 때 스레드가 epoll_wait()에서 대기하고, 바쁠 때는 준비된 작업만 처리하므로 수만 개의 동시 연결도 여유 있게 처리할 수 있습니다.

빠른 확인

이벤트 루프를 얼마나 이해했는지 확인해 보세요.

요약

이벤트 루프는 epoll_wait()를 호출하고 준비된 이벤트를 순회하며 작업을 전달합니다. 수신 대기 소켓에서는 수락하고, EPOLLIN에서는 읽고, EPOLLOUT에서는 버퍼를 비우며, 연결 끊김에서는 정리합니다.

수락과 읽기를 반복하고, EINTR이 발생하면 다시 시도하며, EPOLLET에서는 데이터를 모두 읽어 내세요. 다음으로 완전한 에코 서버를 구성해 보겠습니다.

자주 묻는 질문

“이벤트 루프” 강의는 무료인가요?

네 — “이벤트 루프” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 C Academy 강의 전체를 잠금 해제할 수 있습니다. C Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“이벤트 루프”에서 뭘 배우나요?

읽을 수 있는 소켓에 반응해 보세요. 브라우저에서 직접 실행하는 실습 코드로 C Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

C Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 C Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.

“이벤트 루프” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 C Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 C Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 블로킹과 논블로킹 I/O
  2. epoll 설정하기
  3. 이벤트 루프
  4. 간단한 에코 서버
← C Academy(으)로 돌아가기