IOCP란
- IOCP는 I/O Completion Port의 약자로, 비동기 I/O 처리를 효율적으로 처리하기 위한 모델.
IOCP의 동작 원리
- IOCP의 동작 원리는 다음과 같다.
1. Listen Socket을 만든다. (클라 접속 신호를 수신하는 소켓)
(Overlapped 모델과 다른 점은 이 소켓을 비동기 소켓으로 변경해줄 필요가 없다)
2. IOCP 큐를 만든다 (CreateIoCompletionPort 함수를 사용, 인자를 어떻게 넘기느냐에 따라 동작이 달라짐)
// 작업이 담기는 큐를 만든다.
HANDLE iocpHandle = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
3. while루프 내부에서 accept를 통해 클라이언트가 접속하는지 대기한다 (이번 예제에선 Blocking 방식으로 동작함)
4. 클라이언트 접속 시 세션을 생성하고, 세션 매니저에 추가한다. (*세션은 해당 클라이언트에 대한 정보를 관리하느 구조체)
5. 이미 생성된 IOCP 큐에 방금 accept한 클라이언트 소켓을 감시하고 발생하는 이벤트를 수신하고 예약한다.
// CreateIoCompletionPort 함수를 사용하지만 위에서 사용한거랑 인자가 다르다!
CreateIoCompletionPort((HANDLE)clientSocket, iocpHandle, /*Key*/(ULONG_PTR)session, 0);
// iocpHandle이라는 큐에다가 clientSocket을 관찰 대상으로 넣어 주겠다!
// 3번째 인자는 키인데, 세션의 주소가 유니크하므로 넣어주게 된다.
6. 버퍼와 버퍼 길이, Overlapped 구조체를 만들고, 해당 구조체와 각종 인자들을 엮어서 WSARecv를 호출한다.
WSABUF wsaBuf;
wsaBuf.buf = session->recvBuffer;
wsaBuf.len = BUF_SIZE;
OverlappedEx* overlappedEx = new OverlappedEx;
overlappedEx->type = IO_TYPE::READ; // 스레드에서 어떤 목적으로 사용할 건지 구분하기 위해
DWORD recvLen = 0;
DWORD flags = 0;
WSARecv(clientSocket, &wsaBuf, 1, &recvLen, &flags, /*받는쪽에서 이 주소를 이용해서 OverlappedEx로 되돌릴 수 있다!*/&overlappedEx->overlapped, NULL);
// 그런데 플레이어가 나갔을 때 session, overlapped는 정리가 될 텐데...?
// 그럼 댕글링포인터가 되는 문제가 생긴다.
// 이를 처리하는 것은 Reference Count가 가장 깔끔하다.
- WSARecv를 실행하면 실제로 IOCP 큐에 작업이 들어가게 되고, WorkerThread를 만들어 이 작업들을 처리한다.
Worker Thread
- Worker Thread는 IOCP큐의 핸들을 인자로 받는데, 이 핸들을 통해 현재 IOCP 큐에서 완료된 작업들을 수신할 수 있다.
- 수신하는 방법은 아래와 같다.
// 이 큐에 완료된 정보들을 끄집어와라
DWORD bytesTransferred = 0;
Session* session = nullptr;
OverlappedEx* overlappedEx = nullptr;
bool ret = GetQueuedCompletionStatus(iocpHandle, &bytesTransferred,
(ULONG_PTR*)&session, (LPOVERLAPPED*)&overlappedEx, INFINITE);
// Session과 overlapped는 주스레드에서 넣어준 것인데,
// 여기서 전달한 데이터를 기반으로 해야 할 일들을 판별해야 한다.
- GetQueuedCompletionStatus 함수를 통해 주 스레드에서 넘겨준 session과 overlapped를 꺼내올 수 있다.
- 여기서 중요한 것은 Overlapped를 우리가 만든 구조체 OverlappedEx로 변환할 수 있다는 것인데,
예제에는 나오지 않았지만 이로써 OverlappedEx의 다른 멤버들에도 접근이 가능하다.
왜냐하면 Overlapped와 OverlappedEx의 첫번째 주소가 같기 때문이고, 메모리 Offset을 이용하여 접근하는 것이기 때문에 메모리 오염을 우려하지 않아도 된다.
// 원래는 switch문으로 IO_TYPE에 따라 분기처리
cout << "Recv Data Len = " << bytesTransferred << endl;
cout << "Recv Data IOCP = " << session->recvBuffer << endl;
// 아래 부분은 Thread-safe 한가?
// 주스레드에서 해당 소켓애 대해 하나만 던졌기 때문에 지금 코드는 안전하다
WSABUF wsaBuf;
wsaBuf.buf = session->recvBuffer;
wsaBuf.len = BUF_SIZE;
// 일을 끝내고 다시 낚시대를 던진다 (해당 세션에 소켓의 작업을 모니터링할 수 있도록 큐에 작업을 넣는다)
DWORD recvLen = 0;
DWORD flags = 0;
WSARecv(session->socket, &wsaBuf, 1, &recvLen, &flags, &overlappedEx->overlapped, NULL);
IOCP 방식은 논 블로킹 + 비동기의 대표적인 예시이며 네트워크에 국한되지 않고 몬스터 인공지능 등 비동기로 처리할 수 있는 많은 일들에 적용 가능하다.
'게임 서버 > [Inflearn_rookiss]올인원_클라&서버 연동' 카테고리의 다른 글
16. Service (0) | 2023.12.11 |
---|---|
15. IocpCore (0) | 2023.12.11 |
13. Overlapped 방식 (0) | 2023.12.11 |
12. Select 방식 (0) | 2023.12.11 |
11. 논 블로킹 소켓 (1) | 2023.12.11 |
댓글