헛둘이 2023. 12. 11. 09:41

IocpCore

- IocpCore는 IOCP 구조의 뼈대인 큐 역할을 하는 클래스이다.
- 주로 쓰이는 함수는 Register, Dispatch가 있는데 Register는 IocpObject를 담는 함수이며, Dispatch는 멀티스레드가 실행하여 이 함수를 실행하여 큐에 담긴 Job들을 처리하는 작업을 수행한다.
- IocpObject는 이전 IOCP 예제에서 Session에 해당하는 오브젝트이며, 이전에는 클라이언트만을 담당했지만 이제는 Accept 명령을 받는 서버 소켓도 이 클래스를 상속받아 Listener라는 객체로 관리된다.
- IocpObject를 상속받은 Session은 Send, Recv라는 Job을 생성하고 Listener는 Accpet라는 Job을 생성해서 IOCP큐에 담는다.
- 그러면 거기 담긴 Job들은 멀티스레드가 다 처리하게 된다.

#include "pch.h"
#include "ThreadManager.h"
#include "SocketUtils.h"
#include "Listener.h"
#include "IocpCore.h"

int main()
{
	SocketUtils::Init();

	ListenerRef listener = make_shared<Listener>();
	listener->StartAccept(NetAddress(L"127.0.0.1", 7777));

	for (int32 i = 0; i < 5; ++i)
	{
		GThreadManager->Launch([=]()
			{
				while (true)
				{
					GIocpCore.Dispatch();
				}
			});
	}

	GThreadManager->Join();
	SocketUtils::Clear();
}

- ListenerRef는 위에서 언급한 Listener를 shared_ptr로 관리하는 객체
 
 

IocpEvent

- IocpEvent는 이전 예제에서 Overlapped를 의미한다.
- 실제로 이 클래스는 Overlapped를 상속받아 작성되는데, Overlapped는 IocpObject가 Job을 생성할 때 같이 생성하는 구조체였다.
- 그리고 Job이 완료되었을때 그 Job을 처리하는 부분에서 이 Overlapped를 가져올 수 있는데, Overlapped가 메모리의 첫 번째 위치에 있기 때문에 IocpEvent로 넘겨받아 IocpEvent에 추가적으로 담겨있는 타입 등을 확인하고 적절히 처리할 수 있다.
- IocpEvent은 초기에 nullptr로 있다가, GetQueuedCompletionState함수로부터 포인터를 넘겨받는 입장이므로, 스마트포인터여서는 안되기 때문에 유일하게 쌩 포인터로 사용하게 된다.
 

bool IocpCore::Dispatch(uint32 timeoutMs)
{
    DWORD numOfBytes = 0;
    ULONG_PTR key = 0;
    IocpEvent* iocpEvent = nullptr;

    if (GetQueuedCompletionStatus(_iocpHandle, &numOfBytes, 
        &key, reinterpret_cast<LPOVERLAPPED*>(&iocpEvent), timeoutMs))
    {
        //iocpObject가 이전 예제의 Session이고, iocpEvent가 이전 예제의 overlappedEx이다.
        IocpObjectRef iocpObject = iocpEvent->owner;
        iocpObject->Dispatch(iocpEvent);
    }

- IocpEvent는 GetQueuedCompletionStatus에 의해 대기하던 스레드가 처리해야 할 일감이 있다는 소식을 듣고
호다닥 달려와서 처리하게 되는데, 그 과정에서 IocpEvent의 포인터를 넘겨받게 되고, 넘겨받은 IocpEvent로 가상함수 Dispatch를 호출하며 Listener라면 Accept를 처리, Session이라면 Recv, Send를 처리하게 된다.
- iocpObject를 멤버변수로 가지고 있는 부분이 매우 중요한데, 만약 서버에 클라이언트가 접속해있는 상태에서 갑자기 그 클라이언트가 종료하면 어떻게 될까? 원래라면 세션을 정리하고 메모리를 해제하게 되는데, 그랬을 때 IOCP큐에 Job이 남아 있다면 이미 해제된 포인터에 접근하게 되면서 크래쉬가 나게 된다.
- 그러나 IocpEvent(Job)이 IocpObject를 보유하고 있기 때문에 IocpEvent에 참조된 횟수가 0이 되기 전까지는 IocpObject가 삭제되지 않는다.
- 따라서 Job이 완전히 소진되고 나서 IocpObject가 삭제되는 것을 보장받게 된다.
 

NetAddress

- NetAddress는 기존의 SOCKADDR_IN 구조체를 이용해서 IP, PORT번호 등등을 설정하는 부분을 래핑한 클래스