[C++] IocpCore

강병우·2023년 10월 7일

[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버를 참고하여 정리한 내용입니다.

IocpCore

IocpObject들을 Completion Port에 등록하여 I/O가 처리되고 완료될 때마다 Session 측에 결과를 전달한다.

IocpCore.h

#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;

IocpCore.cpp

#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로 가져온다.

IocpEvent

IocpCore에 등록할 때 이벤트 정보를 갖고 있어야 한다. 해당 이벤트의 종류로 I/O 결과를 구분할 수 있다.

IocpEvent.h

#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) { };
};

IocpEvent.cpp

#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가 오염된 메모리에 접근할 가능성이 있다. 이와 같은 상황을 방지해야 한다)

0개의 댓글