Server lecture section4 [2/6]
IocpCore
// iocpCore.h
/*----------------
IocpObject
-----------------*/
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(class IocpObject* iocpObject);
bool Dispatch(uint32 timeoutMs = INFINITE);
private:
HANDLE _iocpHandle;
};
// TEMP
extern IocpCore GIocpCore;
// iocpCore.cpp
#include "IocpCore.h"
#include "IocpEvent.h"
// TEMP
IocpCore GIocpCore;
/*--------------
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 : 로그 찍기
iocpObject->Dispatch(iocpEvent, numOfBytes);
break;
}
}
return true;
}
// iocpEvent.h
class Session;
enum class EventType : uint8
{
Connect,
Accept,
//PreRecv - 0 byte recv
Recv,
Send
};
/*--------------
IocpEvent
---------------*/
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) { }
void SetSession(Session* session) { _session = session; }
Session* GetSession() { return _session; }
private:
Session* _session = nullptr;
};
/*----------------
RecvEvent
-----------------*/
class RecvEvent : public IocpEvent
{
public:
RecvEvent() : IocpEvent(EventType::Recv) { }
};
/*----------------
SendEvent
-----------------*/
class SendEvent : public IocpEvent
{
public:
SendEvent() : IocpEvent(EventType::Send) { }
};
// iocpEvent.cpp
#include "IocpEvent.h"
/*--------------
IocpEvent
---------------*/
IocpEvent::IocpEvent(EventType type) : _type(type)
{
Init();
}
void IocpEvent::Init()
{
OVERLAPPED::hEvent = 0;
OVERLAPPED::Internal = 0;
OVERLAPPED::InternalHigh = 0;
OVERLAPPED::Offset = 0;
OVERLAPPED::OffsetHigh = 0;
}
OVERLAPPED
를 상속받는 IocpEvent
의 경우 OVERLAPPED
구조체가 메모리의 시작주소에 들어가게되므로 호환되게 사용 가능
그러나 virtual
키워드를 쓸 경우 가상함수 테이블이 메모리 시작주소에 생성되므로 사용하지 않아야 함
// Listener.h
#include "IocpCore.h"
#include "NetAddress.h"
class AcceptEvent;
/*--------------
Listener
---------------*/
class Listener : public IocpObject
{
public:
Listener() = default;
~Listener();
public:
/* 외부에서 사용 */
bool StartAccept(NetAddress netAddress);
void CloseSocket();
public:
/* 인터페이스 구현 */
virtual HANDLE GetHandle() override;
virtual void Dispatch(class IocpEvent* iocpEvent, int32 numOfBytes = 0) override;
private:
/* 수신 관련 */
void RegisterAccept(AcceptEvent* acceptEvent);
void ProcessAccept(AcceptEvent* acceptEvent);
protected:
SOCKET _socket = INVALID_SOCKET;
Vector<AcceptEvent*> _acceptEvents;
};
// Listener.cpp
#include "Listener.h"
#include "SocketUtils.h"
#include "IocpEvent.h"
#include "Session.h"
/*--------------
Listener
---------------*/
Listener::~Listener()
{
SocketUtils::Close(_socket);
for (AcceptEvent* acceptEvent : _acceptEvents)
{
// TODO
xdelete(acceptEvent);
}
}
bool Listener::StartAccept(NetAddress netAddress)
{
_socket = SocketUtils::CreateSocket();
if (_socket == INVALID_SOCKET)
return false;
if (GIocpCore.Register(this) == false)
return false;
if (SocketUtils::SetReuseAddress(_socket, true) == false)
return false;
if (SocketUtils::SetLinger(_socket, 0, 0) == false)
return false;
if (SocketUtils::Bind(_socket, netAddress) == false)
return false;
if (SocketUtils::Listen(_socket) == false)
return false;
const int32 acceptCount = 1;
for (int32 i = 0; i < acceptCount; i++)
{
AcceptEvent* acceptEvent = xnew<AcceptEvent>();
_acceptEvents.push_back(acceptEvent);
RegisterAccept(acceptEvent);
}
return false;
}
void Listener::CloseSocket()
{
SocketUtils::Close(_socket);
}
HANDLE Listener::GetHandle()
{
return reinterpret_cast<HANDLE>(_socket);
}
void Listener::Dispatch(IocpEvent* iocpEvent, int32 numOfBytes)
{
ASSERT_CRASH(iocpEvent->GetType() == EventType::Accept);
AcceptEvent* acceptEvent = static_cast<AcceptEvent*>(iocpEvent);
ProcessAccept(acceptEvent);
}
void Listener::RegisterAccept(AcceptEvent* acceptEvent)
{
Session* session = xnew<Session>();
acceptEvent->Init();
acceptEvent->SetSession(session);
DWORD bytesReceived = 0;
if (false == SocketUtils::AcceptEx(_socket, session->GetSocket(), session->_recvBuffer, 0, sizeof(SOCKADDR_IN) + 16, sizeof(SOCKADDR_IN) + 16, OUT & bytesReceived, static_cast<LPOVERLAPPED>(acceptEvent)))
{
const int32 errorCode = ::WSAGetLastError();
if (errorCode != WSA_IO_PENDING)
{
// 일단 다시 Accept 걸어줘야함
RegisterAccept(acceptEvent);
}
}
}
void Listener::ProcessAccept(AcceptEvent* acceptEvent)
{
Session* session = acceptEvent->GetSession();
if (false == SocketUtils::SetUpdateAcceptSocket(session->GetSocket(), _socket))
{
RegisterAccept(acceptEvent);
return;
}
SOCKADDR_IN sockAddress;
int32 sizeOfSockAddr = sizeof(sockAddress);
if (SOCKET_ERROR == ::getpeername(session->GetSocket(), OUT reinterpret_cast<SOCKADDR*>(&sockAddress), &sizeOfSockAddr))
{
RegisterAccept(acceptEvent);
return;
}
session->SetNetAddress(NetAddress(sockAddress));
cout << "Client Connected!" << endl;
// TODO
RegisterAccept(acceptEvent);
}
getpeername()
함수는 연결된 상대측 소켓의 주소 정보를 가져옴
- 첫 번재 인자에 소켓을 지정
- 두 번째 인자로 주소정보가 담긴
sockaddr
구조체의 주소 저장 - 세 번째 인자로 가져온 정보의 크기 저장
// Session.h
#include "IocpCore.h"
#include "IocpEvent.h"
#include "NetAddress.h"
/*--------------
Session
---------------*/
class Session : public IocpObject
{
public:
Session();
virtual ~Session();
public:
/* 정보 관련 */
void SetNetAddress(NetAddress address) { _netAddress = address; }
NetAddress GetAddress() { return _netAddress; }
SOCKET GetSocket() { return _socket; }
public:
/* 인터페이스 구현 */
virtual HANDLE GetHandle() override;
virtual void Dispatch(class IocpEvent* iocpEvent, int32 numOfBytes = 0) override;
public:
// TEMP
char _recvBuffer[1000];
private:
SOCKET _socket = INVALID_SOCKET;
NetAddress _netAddress = {};
Atomic<bool> _connected = false;
};
// Session.cpp
#include "Session.h"
#include "SocketUtils.h"
/*--------------
Session
---------------*/
Session::Session()
{
_socket = SocketUtils::CreateSocket();
}
Session::~Session()
{
SocketUtils::Close(_socket);
}
HANDLE Session::GetHandle()
{
return reinterpret_cast<HANDLE>(_socket);
}
void Session::Dispatch(IocpEvent* iocpEvent, int32 numOfBytes)
{
// TODO
}
// GameServer.cpp
int main()
{
Listener 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();
}
작동 구조 정리
listnerSocket
생성Completion Port(IocpCore)
에listenerSocket
등록 및 연결listenerSocket
설정AcceptEvent
생성clientSocket
생성AcceptEvent
를 통해clientSocket
에accept
를 걸어줌
GetQueuedCompetionStatus
실행Completion packet
이 없다면 쓰레드 대기, 있으면Listener::Dispatch
수행- 4번에서
AcceptEvent
를 통해 연결된clientSocket
과 IP주소 등을 받아 사용