ONE QUESTION AT A TIME.

천천히 생각하고, 분명하게.

TODAY’S QUESTION709 / 1500
네트워크난이도 하5분 답변 목표 · 보강본

epoll은 읽기 가능 알림을, IOCP는 제출한 읽기의 완료를 줍니다. 데이터를 읽는 시점과 버퍼 관리는 어떻게 달라지나요?

잠깐, 답변을 보기 전에
먼저 내 말로 설명해 볼까요?

답변과 점검 포인트 펼치기답변 접기
01

구두 답변

정의를 외우기보다, 원리와 조건을 연결해 설명해 보세요.

준비 통지는 지금 읽거나 써 볼 수 있다는 알림이고, 완료 통지는 앞서 요청한 읽기나 쓰기의 결과가 나왔다는 알림입니다. 보통 준비 통지를 이용하는 구조를 Reactor, 완료 통지를 이용하는 구조를 Proactor라고 부릅니다. 차이는 이름보다 알림을 받은 다음 실제 I/O를 할지, 이미 수행된 I/O의 결과를 처리할지에 있습니다.

준비 통지에서는 ‘이제 읽어 보라’는 알림 뒤에 애플리케이션이 실제 읽기를 호출합니다. 완료 통지에서는 버퍼를 먼저 맡기고 ‘그 작업이 이만큼 끝났다’는 결과를 받습니다. 아래 표는 이 차이를 설명하는 흐름이지 API별 모든 오류 경로를 나타낸 코드는 아닙니다.

모델 순서 알림 후 할 일
epoll 기반 준비 통지 감시 → 준비 이벤트 → 읽기 시도 읽기 반환값으로 데이터·종료·재대기를 판정
IOCP 기반 완료 통지 버퍼와 읽기 제출 → 완료 패킷 완료 결과를 처리하고 버퍼 수명을 정리

epoll에서는 읽기 가능 이벤트를 받고 나서 애플리케이션이 소켓을 읽습니다. 그런데 알림이 왔다고 원하는 양을 다 읽을 수 있는 것은 아닙니다. 일부만 있을 수도 있고 연결이 끝났을 수도 있어 반환값을 확인해야 합니다. 특히 에지 트리거에서는 보통 더 읽을 것이 없다는 EAGAIN이 나올 때까지 읽습니다. 일부만 읽고 새 이벤트를 기다리면 남은 데이터에 대해 새 알림이 오지 않아 처리가 멈출 수 있기 때문입니다.

그렇다고 한 연결만 끝없이 읽으면 다른 연결이 밀립니다. 저는 연결별로 한 번에 처리할 양을 제한하되, 데이터가 남아 있는 상태로 멈췄다면 그 연결을 자체 실행 대기열에 다시 넣겠습니다. 이렇게 해야 처리 누락을 막으면서 다른 연결에도 실행 기회를 줄 수 있습니다.

IOCP와 중첩 I/O에서는 버퍼와 작업 정보를 준비해 먼저 수신을 요청하고 완료된 바이트 수나 오류를 받습니다. 이쪽에서 특히 신경 쓸 것은 수명입니다. 사용자가 연결을 끊었다고 연결 객체와 버퍼를 바로 해제하면, 아직 끝나지 않은 I/O가 그 메모리를 사용하고 있을 수 있습니다. 저는 새 I/O 제출을 막고 미완료 작업의 수를 추적해서, 실제 완료가 확인된 뒤 마지막 참조를 정리하겠습니다. 취소 요청을 보낸 것만으로 완료됐다고 처리하지는 않겠습니다.

두 구조 모두 워커를 늘리는 것만으로 안전해지지 않습니다. 여러 워커가 같은 연결을 동시에 처리한다면 연결별 상태 변경을 직렬화해야 합니다. 검증할 때는 빠른 정상 통신만 보지 않고, 데이터가 계속 들어오는 연결과 종료·취소가 겹치는 연결을 섞어 보겠습니다. 준비 통지에서는 남은 작업을 놓치지 않는지, 완료 통지에서는 아직 사용 중인 자원을 먼저 정리하지 않는지를 중심으로 보겠습니다.

알림이 보장하지 않는 일을 구분합니다

준비 이벤트는 현재 어떤 동작을 시도할 수 있다는 신호이지 그 데이터가 특정 워커에게 예약됐다는 뜻은 아닙니다. 다른 실행 흐름이 먼저 읽거나 연결 상태가 바뀔 수 있으므로 실제 recv 반환값이 판단의 기준입니다. 대기하지 않고 당장 가능한 만큼만 읽고 다시 기다려야 함을 오류 코드로 알리는 성질이 논블로킹 I/O입니다. 준비 통지를 받아 애플리케이션이 동작을 실행하는 구조를 리액터(Reactor), 제출한 비동기 동작의 결과를 받아 후속 처리를 실행하는 구조를 프로액터(Proactor)라고 정리할 수 있습니다. 이는 구조를 설명하는 모델이지 모든 운영체제 API가 순수하게 한 종류만 제공한다는 뜻은 아닙니다.

epoll의 레벨 트리거는 읽을 것이 남아 있는 조건을 반복해서 알릴 수 있고, 에지 트리거는 상태 변화에 반응하는 방식이라 처리 루프의 책임이 다릅니다. 에지 트리거에서 읽기 예산을 다 썼지만 EAGAIN까지 도달하지 않았다면 자체 대기열에 남겨 다시 실행해야 합니다. 반대로 쓰기 가능 감시를 항상 켜 두면 대부분 쓰기 가능한 소켓 때문에 이벤트 루프가 불필요하게 반복될 수 있습니다. 보낼 데이터가 있는데 바로 다 못 보냈을 때만 적절히 쓰기 감시를 켜고, 송신 큐가 비면 끄는 식으로 상태와 관심 이벤트를 연결하겠습니다.

버퍼의 소유권은 호출 순서에 드러나야 합니다

완료 모델은 이미 제출한 동작이 무엇인지 식별해야 하므로 연결 ID만이 아니라 개별 작업 정보가 필요합니다. IOCP에서는 completion key와 OVERLAPPED를 함께 사용해 연결과 작업을 구분할 수 있습니다. 버퍼를 제출한 뒤 완료 전까지 다른 요청이 재사용해서는 안 되는 계약을 버퍼 수명 관리와 소유권 이전의 관점으로 설명하겠습니다. 소켓을 닫았다는 사실과 미완료 작업이 더 이상 버퍼를 만지지 않는다는 사실은 다릅니다.

준비 모델도 버퍼 수명 문제가 없는 것은 아닙니다. recv가 반환한 뒤 파서나 다른 워커에 버퍼의 참조를 넘겼다면 그 소비가 끝날 때까지 유지해야 합니다. 또 TCP의 읽기 한 번이나 완료 하나가 메시지 한 개와 일치하지 않으므로, 일부 헤더·여러 메시지·연결 종료를 처리하는 조립 상태가 필요합니다. 이 논리 메시지 경계를 복원하는 작업을 메시지 프레이밍(message framing)이라고 부릅니다. 완료 바이트 수만 맞게 읽었다고 상위 프로토콜 처리까지 끝난 것은 아닙니다.

이식할 때는 API 이름만 교체하지 않고 연결 상태를 미수신·처리 대기·송신 대기·종료 중처럼 나눠 어떤 이벤트가 어떤 상태를 바꾸는지 확인하겠습니다. 계속 데이터가 들어오는 한 연결이 전체 실행 예산을 차지하는지, 종료 뒤 완료가 도착해 이중 반환되는지, 일부 송신 뒤 남은 데이터가 다시 처리되는지를 검증합니다. 준비와 완료 모델 모두 진행성·공정성·소유권을 별도로 설계해야 한다는 것이 최종 판단입니다.

02

득점 포인트

이해가 드러나는 지점을 확인해 보세요.

  • 통지 후 애플리케이션의 책임을 설명한다. 직접 I/O를 시도하는 단계와 완료 결과를 처리하는 단계를 구분한다.
  • 진행성과 공정성을 함께 해결한다. 에지 트리거에서 처리 예산을 소진하면 자체 재스케줄링해야 하는 이유를 말한다.
  • 종료 순서를 구체적으로 정한다. 새 제출 차단, 미완료 작업 추적, 완료 후 자원 해제를 연결한다.
  • 검증할 경합 상황을 제시한다. 다중 워커, 지속적인 입력, 취소·종료 경쟁으로 정상 경로 밖의 오류를 확인한다.
03

감점 포인트

놓치기 쉬운 조건과 오해를 짚어 보세요.

  • “epoll 이벤트가 오면 사용자 버퍼에 데이터가 들어 있다”고 말한다. 준비 통지를 받은 뒤 읽기 호출을 해야 한다.
  • “에지 트리거는 일부만 읽고 기다려도 다시 알려 준다”고 말한다. 남은 데이터를 스스로 재처리하지 않으면 정체될 수 있다.
  • “취소 함수가 반환되면 I/O 버퍼를 해제한다”고 말한다. 취소 요청과 실제 완료는 다를 수 있다.
  • “완료 이벤트가 순서대로 나오면 워커 처리도 순서대로 끝난다”고 말한다. 여러 실행 흐름의 처리 순서는 별도로 보장해야 한다.
04

더 파고들 거리

하나 더 질문하며 이해의 범위를 넓혀 보세요.

득점·감점 포인트는 학습용 자기 점검 기준이며, 특정 회사의 실제 채점표가 아닙니다.

원리부터 공부하고 싶다면

답변을 보충하는 개념·예제·슈도코드를 읽어 보세요.

← 전체 질문 보기