15. IocpCore
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번호 등등을 설정하는 부분을 래핑한 클래스