[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버를 참고하여 정리한 내용입니다.
IocpObject들을 Completion Port에 등록하여 I/O가 처리되고 완료될 때마다 Session 측에 결과를 전달한다.
#pragma once
/*
iocpObject
iocpEvent는 Overlapped 구조체를 받아와서 recv/write 등의 상태를 판단
*/
class IocpObject
{
public:
virtual HANDLE GetHandle() abstract;
virtual void Dispatch(class IocpEvent* iocpEvent, int32 numOfBytes = 0) abstract;
};
/*
iocpCore
*/
class IocpCore
{
public:
IocpCore();
~IocpCore();
HANDLE GetHandle() { return _iocpHandle; }
bool Register(IocpObject* iocpObject);
bool Dispatch(uint32 timeoutMs = INFINITE);
private:
HANDLE _iocpHandle;
};
// TEMP 임시용
extern IocpCore GIocpCore;
#include "pch.h"
#include "iocpCore.h"
#include "IocpEvent.h"
/*
iocpCore
*/
IocpCore::IocpCore()
{
_iocpHandle = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);
ASSERT_CRASH(_iocpHandle != INVALID_HANDLE_VALUE);
}
IocpCore::~IocpCore()
{
::CloseHandle(_iocpHandle);
}
bool IocpCore::Register(IocpObject* iocpObject)
{
return ::CreateIoCompletionPort(iocpObject->GetHandle(), _iocpHandle, reinterpret_cast<ULONG_PTR>(iocpObject), 0);
}
bool IocpCore::Dispatch(uint32 timeoutMs)
{
DWORD numOfBytes = 0;
IocpObject* iocpObject = nullptr;
IocpEvent* iocpEvent = nullptr;
if (::GetQueuedCompletionStatus(_iocpHandle, OUT & numOfBytes, OUT reinterpret_cast<PULONG_PTR>(&iocpObject), OUT reinterpret_cast<LPOVERLAPPED*>(&iocpEvent), timeoutMs))
{
// 등록에 성공했을 때
iocpObject->Dispatch(iocpEvent, numOfBytes);
}
else
{
// 실패했을 때
// 에러코드가 여러 개임!
int32 errCode = ::WSAGetLastError();
switch (errCode)
{
case WAIT_TIMEOUT:
return false;
default:
// TODO 로그 찍기 / 타임아웃이 아니면 일단 Dispatch
iocpObject->Dispatch(iocpEvent, numOfBytes);
break;
}
}
return false;
}
IocpObject : 핵심 클래스. 나중에 작업할 Listener와 Session이 상속받는 클래스이다. 이들을 IocpHandle에 등록하여 I/O 결과를 받아올 수 있다.
_iocpHandle : Completion Port. 여기에 세션들이 들어올 때마다 iocpObject들을 등록할 수 있다.
Register : iocpObejct를 등록한다.
Dispatch : I/O 결과를 iocpEvent로 가져온다.
IocpCore에 등록할 때 이벤트 정보를 갖고 있어야 한다. 해당 이벤트의 종류로 I/O 결과를 구분할 수 있다.
#pragma once
/*
EventType
*/
enum class EventType : uint8 {
Connect,
Accept,
//PreRecv,
Recv,
Send
};
/*
IocpEvent
절대 가상함수를 사용하지 마라. offset 첫번째로 설정되어 메모리를 덮어씌울 수도 있기 때문이다.
*/
// 예전에 만들었던 overlappedEx 구조체랑 같은 역할임!
class IocpEvent : public OVERLAPPED
{
public:
IocpEvent(EventType type);
void Init();
EventType GetType() { return _type; }
protected:
EventType _type;
};
/*
ConnectEvent
*/
class ConnectEvent : public IocpEvent
{
public:
ConnectEvent() : IocpEvent(EventType::Connect) { };
};
/*
AcceptEvent
*/
class AcceptEvent : public IocpEvent
{
public:
AcceptEvent() : IocpEvent(EventType::Accept) { };
};
/*
RecvEvent
*/
class RecvEvent : public IocpEvent
{
public:
RecvEvent() : IocpEvent(EventType::Recv) { };
};
/*
SendEvent
*/
class SendEvent : public IocpEvent
{
public:
SendEvent() : IocpEvent(EventType::Send) { };
};
#include "pch.h"
#include "IocpEvent.h"
IocpEvent::IocpEvent(EventType type)
{
Init();
}
void IocpEvent::Init()
{
OVERLAPPED::hEvent = 0;
OVERLAPPED::Internal = 0;
OVERLAPPED::InternalHigh = 0;
OVERLAPPED::Offset = 0;
OVERLAPPED::OffsetHigh = 0;
}
추후, IocpObject와 같은 객체들을 shared_ptr로 변경할 예정이다(I/O가 끝나기 전에 Session이 소멸하면, IocpEvent가 오염된 메모리에 접근할 가능성이 있다. 이와 같은 상황을 방지해야 한다)