TCP 소켓을 사용할 때 connect, accept, send, receive 함수를 호출하면 블로킹이 발생하며 송신량이 많아질 경우 수신 버퍼가 꽉 차 블로킹 되는 경우에 발생합니다.
소켓이 여러개일 경우 각 소켓에 대해 블로킹이 걸려 CPU 자원이 낭비되게 됩니다.
소켓 함수가 블로킹되지 않도록 운영체제에서 지원하는 기능입니다. 소켓을 논블록 소켓으로 바꾸고, 기존처럼 송수신, 연결 등 함수를 호출해서 사용합니다. 논블로킹 소켓은 무조건 함수 호출하면 바로 즉시 리턴을 하게되고 리턴되는 값은 성공 혹은 would block 둘 중 하나의 값입니다. would block은 블로킹이 걸릴 뻔했다는 의미로 나중에 다시 송신 함수를 호출해 주어야 합니다.
스레드 블로킹이 없어 도중에 취소가 가능하고 적은 스레드의 개수로 여러개의 소켓을 다룰 수 있고 CPU 자원과 스택 메모리가 낭비되지 않는 다는 장점이 있습니다.
하지만, 소켓 I/O 함수의 리턴 값이 would block이라면 재시도 호출 낭비가 발생하고, 소켓 I/O 함수를 호출할 때 입력하는 데이터 블록에 대한 복사 연산이 발생하여 메모리 낭비가 발생합니다. 복사 연산은 송신할 때, 사용자 프로세스에서 송신 버퍼로 수신할 때, 수신 버퍼에서 사용자 프로세스로 발생합니다.
논블로킹 소켓의 단점을 해결하기 위한 기법입니다.
Overlapped I/O는 소켓에 대해 Overlapped 접근을 먼저 걸고, Overlapped 접근이 성공했는지 확인한 다음, 성공했다면 결과를 얻어와 나머지를 처리하는 방식입니다.
주의할 점은 Overlapped I/O 전용 함수가 비동기로 하는 일이 완료될 때까지 소켓 API로 넘긴 데이터 블록과 Overlapped status 객체를 제거하거나 변경해서는 안됩니다.
최대 소켓 개수의 Overlapped status 객체에 대해 GetOverlappedStatus를 실행해 I/O 완료 상태인지 확인하고, 완료 상태라면 나머지 처리를 수행합니다.
이러한 로직으로 인해 소켓의 개수가 많아지게 되면 성능 저하 문제가 발생할 수 있게 됩니다. 그 대안으로 제시된 것이 IOCP와 epoll입니다.
epoll은 여러개의 소켓중 한 소켓이 I/O 가능한 상태가 되면 이를 감지해 사용자에게 알림을 해 주는 이벤트 방식입니다.
epoll은 I/O 가능한 소켓에서만 루프를 돌면 되지만, 사실 소켓의 송신 버퍼가 빈 공간이 없는 순간을 유지하는 시간이 상대적으로 짧아 대부분 송신 가능한 상태이므로 큰 성능 향상은 기대할 수가 없습니다.
이 문제를 해결하기 위해 레벨 트리거 대신, 엣지 트리거
를 사용하게 되었습니다. 먼저 레벨 트리거는 특정 상태가 유지되는 동안 감지하는 것으로 "소켓이 I/O 가능하다"는 뜻이고 엣지 트리거는 특정 상태가 변화하는 시점을 감지하는 방식으로 "I/O 가능이 아니었다가 이제 I/O 가능이 되었다"라는 뜻으로 해석합니다.
엣지 트리거를 사용하면 루프를 도는 횟수를 줄일 수 있지만, UDP 데이터그램이 2개이상 있고 데이터 하나를 꺼냈어도 엣지의 변화가 없어 꺼낸 데이터를 제외한 나머지 데이터들을 꺼낼 수 없게 됩니다. 이를 예방하려면 소켓은 논블록이여야 하며 I/O 호출은 한 번만 하지 말고 would block
이 발생할 때까지 반복해야 합니다.
IOCP는 소켓의 Overlapped I/O가 완료되면 이를 감지해 사용자에게 알려주는 역할을 합니다. 사용자는 "I/O가 완료되었다"라는 완료 신호를 받아 여러개의 소켓이 있더라도 I/O 완료
된 것들만 IOCP를 통해 바로 얻을 수 있어 모든 소켓에 대해 루프를 돌지 않아도 됩니다.
IOCP는 Overlapped I/O를 사용해 블로킹을 없앱니다. 처리 순서는 Overlapped I/O를 먼저 걸고 완료 신호만 꺼냅니다. 그 완료 신호에 대한 처리 작업을 하고 다시 Overlapped I/O를 겁니다.
epoll의 경우 TCP 소켓으로 수신을 한 후 데이터 수신을 하려면 소켓 수신 함수를 호출해 주어야 했지만 IOCP는 연결 받기와 수신을 소켓 함수 호출 한번으로 끝낼 수 있어 프로그램 최적화에 더 유리합니다.
IOCP는 epoll과 달리 스레드 풀을 쉽게 구현할 수 있습니다. epoll은 한 epoll에 대해 여러 스레드가 동시에 이벤트 발생을 기다리면 데이터 순서를 알기 어렵고 만약 순서를 안다해도 여러 스레드가 동시에 같은 일을 한다면 처리 순서 로직을 작성해 주어야 합니다. IOCP는 어떤 소켓에 대해 Overlapped I/O를 하지 않는 이상 그 소켓에 대한 완료 신호가 전혀 발생하지 않아 소켓 하나에 대한 완료 신호를 스레드 하나만 처리할 수 있게 보장할 수 있습니다. 따라서 많은 소켓에 대한 I/O 처리를 동시다발적으로 수행할 때 여러 스레드가 완료 신호 처리를 균등하게 나눠서 처리할 수 있게 됩니다.