나만의 작은 도서관

[TIL][C++] 250428 MMO 서버 개발 6일차: 다시 정리하는 IOCP 함수, WSA___함수, ___Ex 함수 본문

Today I Learn

[TIL][C++] 250428 MMO 서버 개발 6일차: 다시 정리하는 IOCP 함수, WSA___함수, ___Ex 함수

pledge24 2025. 4. 29. 16:40
주의사항: 해당 글은 일기와 같은 기록용으로, 다듬지 않은 날것 그대로인 글입니다. 

다시 정리하는 IOCP 함수

  • IOCP 함수는 크게 3가지로, CICP, GQCS, PQCS가 있다. 이 3가지 함수를 통해 IOCP의 대부분의 기능을 사용할 수 있다. (여기서 PQCS는 따로 다루지 않는다.)

CICP 함수

CreateIoCompletionPort(1, 2, 3, 4)
  1. 비동기 I/O 소켓 핸들: 클라이언트 소켓을 HANDLE로 캐스팅해서 넣는다.
  2. IOCP핸들: 만들어진 IOCP의 핸들을 넣는다.
  3. 키: 연결한 장치에 대한 고유 키를 넣는다. 아무거나 넣어도 되지만 보통 해당 클라이언트 소켓의 세션 주소를 넣는다.
  4. CP 처리 쓰레드 개수: CP에 접근하는 최대 쓰레드 수를 지정한다. 0으로 설정하면, 기본값으로 프로세서 수만큼 설정한다.
// IocpHandle 생성
HANDLE IocpHandle = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);

// 존재하는 IocpHandle에 clientSocket 등록.
HANDLE IocpHandle2 = CreateIoCompletionPort(clientSock, IocpHandle, 0, 0);
  • CICP의 용도는 2가지로, 첫 번째는 IocpHandle 생성, 두 번째는 존재하는 IocpHandle에 clientSocket을 등록하는 것이다.
  • 반환 값은 IocpHandle로, 위 코드에서 IocpHandle과 IocpHandle2은 같다.

GQCS 함수

GetQueuedCompletionStatus(1, 2, 3, 4, 5)
  1. IOCP 핸들: 이미 존재하는 IOCP 핸들을 넣는다.
  2. 처리 완료한 바이트 수: 처리 완료한 네트워크 이벤트의 바이트 수
  3. 키: CICP에서 넣어준 키값
  4. Overlapped 구조체: 내부에서 사용
  5. timeout: CP에 완료 패킷을 받는 최대 대기 시간.

클라이언트 소켓의 세션을 넘겨주고 받는 2가지 방법

  1. key인자로 session 넘겨준 다음, CICP에서 넘겨준 session을 GQCS에서 받는다.
CreateIoCompletionPort(1, 2, &session, 4)

ULONG_PTR session = 0;
GetQueuedCompletionStatus(1, 2, &session, 4, 5)
  • 정석적인 방법이다.
  1. 키를 사용하지 않는 대신, 확장된 Overlapped 구조체에 session 주소를 넣어 GQCS에서 Overlapped 구조체를 통해 session을 얻는다.
CreateIoCompletionPort(1, 2, 0, 4)
WSARecv(1, 2, 3, 4, 5, OverlappedEx, 7)

OverlappedEx* overlappedEx;
GetQueuedCompletionStatus(1, 2, 3, reinterpret_cast<LPOVERLAPPED*>(overlappedEx), INFINITE);
SessionRef = overlappedEx->session;
  1. 키값 없이 Overlapped 구조체 하나로 세션 주소를 얻을 수 있기 때문에 구조가 단순해진다는 장점이 있다.

다시 정리하는 비동기 네트워크 이벤트 함수(WSA___)

WSARecv()

WSARecv(1, 2, 3, 4, 5, 6, 7)
  1. 비동기 I/O 소켓: 클라이언트 소켓을 넣는다.
  2. 수신 버퍼 주소: 해당 소켓으로부터 수신된 데이터를 복사할 버퍼 주소를 넣는다.
  3. 수신 버퍼 개수: 수신 버퍼의 개수를 넣는다. (기본값 1)
  4. 수신된 데이터 길이: 수신된 데이터의 바이트 수
  5. 플래그: 상세 옵션 설정용
  6. Overlapped 구조체: 내부에서 사용하는 구조체
  7. 완료 루틴: 수신 완료 시 이어서 OS가 호출할 콜백함수

WSASend()

WSASend(1, 2, 3, 4, 5, 6, 7)
  1. 비동기 I/O 소켓: 클라이언트 소켓을 넣는다.
  2. 송신 버퍼 주소: 해당 소켓의 원격 주소에게 송신할 데이터를 복사할 버퍼 주소를 넣는다.
  3. 송신 버퍼 개수: 수신 버퍼의 개수를 넣는다. (기본값 1, Scatter-Gather과 관련 있음)
  4. 송신한 데이터 길이: 송신한 데이터 바이트 수
  5. 플래그: 상세 옵션 설정용
  6. Overlapped 구조체: 내부에서 사용하는 구조체
  7. 완료 루틴: 송신 완료 시 이어서 OS가 호출할 콜백함수

런타임에 API에서 가져온 확장 함수(___EX)

  • 해당 함수들은 런타임에 가져온 확장 함수들로, 비동기로 실행할 수 있는 함수들이며 작업 완료 시 CP의 완료 패킷이 들어간다.
  • 모든 네트워크 이벤트들을 GQCS를 통해 관리하기 위해 사용한다.

ConnectEx(클라이언트 전용)

ConnectEx(1, 2, 3, 4, 5, 6, 7)
  1. 비동기 I/O 소켓: 클라이언트 소켓을 넣는다.
  2. 타겟 서버 주소: 연결할 서버 주소를 넣는다.
  3. 주소 크기: 2번에 해당하는 변수 크기를 넣는다. ex. sizeof(sockAddr)
  4. connect 성공 시, 전송할 데이터가 담긴 버퍼 주소
    1. 연결이 되자마자 보내는 데이터로, 옵션이다.
  5. 4번의 버퍼 데이터 길이(바이트 수)
  6. 4번의 버퍼에 들어있는 데이터의 전송된 데이터 길이
  7. Overlapped 구조체

DisconnectEx

DisconnectEx(1, 2, 3, 4)
  1. 비동기 I/O 소켓: 연결된 소켓 핸들
  2. Overlapped 구조체
  3. 플래그: 상세 옵션 설정용
  4. 예약된? 변수: 0으로 설정해야 함

AcceptEx

AcceptEx(1, 2, 3, 4, 5, 6, 7, 8)
  1. listen 소켓: 수신 대기 소켓
  2. 연결을 수락할 소켓(accept socket): 미리 만든 소켓. accept 성공 시 해당 소켓에 바인딩된다.
  3. 서버의 로컬 주소 및 클라이언트 원격 주소를 저장할 버퍼 주소
// 3번 버퍼 생김새
// recvData: 연결 성공 시, 같이 날라온 데이터
// addr: 서버의 로컬 주소 및 클라이언트 원격 주소
// [[-----recvData-----][----addr-----]]
  1. 3번 버퍼의 수신 데이터(recvData) 바이트 수
    1. 0으로 설정 시, accept 성공 시 추가 수신 작업 발생 X
  2. 로컬 주소 정보를 저장하기 위한 예약된 바이트 크기
    1. 사용 중인 전송 프로토콜 + 16 바이트 크기를 가짐
    2. sizeof(ex. SOCKADDR_IN) + 16
  3. 원격 주소 정보를 저장하기 위한 예약된 바이트 크기. 0일 수 없음
    1. 사용 중인 전송 프로토콜 + 16 바이트 크기를 가짐
    2. sizeof(ex. SOCKADDR_IN) + 16
  4. 동기적 함수일 경우 변경되는 값. recvData가 수신될 때까지 PENDING 반환. 수신 완료 시, 수신된 바이트 수
  5. Overlapped 구조체