SocketUtils 초기화Listener 객체 생성 및 설정int main()
{
SocketUtils::Init();
SocketUtils::Init():WSAStartup을 호출하여 TCP/IP 소켓을 사용할 수 있는 환경을 만듭니다.AcceptEx, ConnectEx 등)을 사용할 준비도 이 과정에서 설정됩니다. Listener listener;
listener.StartAccept(NetAddress(L"127.0.0.1", 7777));
Listener 객체:Listener는 IOCP와 연동된 소켓의 연결 대기 역할을 합니다.StartAccept:NetAddress 클래스를 통해 지정된 IP(127.0.0.1)와 포트(7777)로 바인딩됩니다. for (int32 i = 0; i < 5; i++)
{
GThreadManager->Launch([=]()
{
while (true)
{
GIocpCore.Dispatch();
}
});
}
GThreadManager:GIocpCore.Dispatch(): GThreadManager->Join();
Join(): SocketUtils::Clear();
}
SocketUtils::Clear():WSACleanup을 호출하여 네트워크 환경을 정리합니다.static LPFN_CONNECTEX ConnectEx;
static LPFN_DISCONNECTEX DisconnectEx;
static LPFN_ACCEPTEX AcceptEx;
ConnectEx, DisconnectEx, AcceptEx는 확장된 비동기 소켓 기능을 제공합니다.WSAIoctl를 통해 함수 포인터를 동적으로 바인딩합니다.static SOCKET CreateSocket();
WSASocket API를 사용하여 비동기 소켓(OVERLAPPED 플래그 포함)을 생성합니다.static bool BindAnyAddress(SOCKET socket, uint16 port);
INADDR_ANY를 사용하여 모든 네트워크 인터페이스에서 접속을 허용합니다.Init 함수void SocketUtils::Init()
{
WSADATA wsaData;
assert(::WSAStartup(MAKEWORD(2, 2), OUT &wsaData) == 0);
SOCKET dummySocket = CreateSocket();
assert(BindWindowsFunction(dummySocket, WSAID_CONNECTEX, reinterpret_cast<LPVOID*>(&ConnectEx)));
assert(BindWindowsFunction(dummySocket, WSAID_DISCONNECTEX, reinterpret_cast<LPVOID*>(&DisconnectEx)));
assert(BindWindowsFunction(dummySocket, WSAID_ACCEPTEX, reinterpret_cast<LPVOID*>(&AcceptEx)));
Close(dummySocket);
}
WSAStartup:
더미 소켓 생성:
AcceptEx 등)를 바인딩하기 위한 임시 소켓입니다.BindWindowsFunction:
WSAIoctl을 호출하여 특정 GUID(예: WSAID_ACCEPTEX)에 해당하는 함수 포인터를 얻습니다.Close:
CreateSocket 함수SOCKET SocketUtils::CreateSocket()
{
return ::WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
}
WSASocket:WSA_FLAG_OVERLAPPED 사용).SetLinger 함수bool SocketUtils::SetLinger(SOCKET socket, uint16 onoff, uint16 linger)
{
LINGER option;
option.l_onoff = onoff;
option.l_linger = linger;
return SetSockOpt(socket, SOL_SOCKET, SO_LINGER, option);
}
SO_LINGER 설정:linger)을 설정합니다.onoff가 1일 경우, 데이터가 모두 전송될 때까지 종료를 지연시킵니다.BindAnyAddress 함수bool SocketUtils::BindAnyAddress(SOCKET socket, uint16 port)
{
SOCKADDR_IN myAddress;
myAddress.sin_family = AF_INET;
myAddress.sin_addr.s_addr = ::htonl(INADDR_ANY);
myAddress.sin_port = ::htons(port);
return SOCKET_ERROR != ::bind(socket, reinterpret_cast<const SOCKADDR*>(&myAddress), sizeof(myAddress));
}
SOCKADDR_IN 초기화:
sin_family: 주소 패밀리 설정(AF_INET).sin_addr: 모든 인터페이스(INADDR_ANY)로 설정.sin_port: 주어진 포트로 설정.bind 호출:
StartAccept 함수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::Bind(_socket, netAddress) == false)
return false;
if (SocketUtils::Listen(_socket) == false)
return false;
const int32 acceptCount = 1;
for (int32 i = 0; i < acceptCount; i++)
{
IocpEvent* acceptEvent = new IocpEvent(EventType::Accept);
_acceptEvents.push_back(acceptEvent);
RegisterAccept(acceptEvent);
}
return true;
}
소켓 생성 및 IOCP 등록:
GIocpCore)에 등록합니다.소켓 설정:
SO_REUSEADDR: 동일한 포트에서 여러 소켓을 사용할 수 있도록 설정.bind: 지정된 네트워크 주소와 포트에 소켓을 바인딩.listen: 소켓을 수신 대기 상태로 설정.Accept 이벤트 등록:
acceptCount 횟수만큼 IocpEvent 객체를 생성하고, 비동기 Accept 작업을 등록합니다.RegisterAccept 함수void Listener::RegisterAccept(IocpEvent* acceptEvent)
{
Session* session = new Session();
acceptEvent->Init();
acceptEvent->session = 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)
{
RegisterAccept(acceptEvent);
}
}
}
ProcessAccept 함수void Listener::ProcessAccept(IocpEvent* acceptEvent)
{
Session* session = acceptEvent->session;
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;
RegisterAccept(acceptEvent);
}
Accept 작업 완료:
Accept 재등록:
Listener 클래스는 서버에서 클라이언트 연결을 대기하는 역할을 합니다.protected:
SOCKET _socket = INVALID_SOCKET;
vector<IocpEvent*> _acceptEvents;
_socket: INVALID_SOCKET 값으로 초기화됩니다._acceptEvents: bool Listener::StartAccept(NetAddress netAddress)
소켓 생성:
SocketUtils::CreateSocket를 호출하여 소켓을 생성합니다.false를 반환합니다.IOCP에 등록:
GIocpCore.Register(this)를 호출하여 Listener 객체를 IOCP에 등록합니다.소켓 설정:
SetReuseAddress: 소켓 주소 재사용 가능 여부 설정.SetLinger: 연결 종료 시 대기 시간을 설정.Bind: 네트워크 주소를 소켓에 바인딩.Listen: 소켓을 수신 대기 상태로 설정.Accept 이벤트 등록:
acceptCount 횟수만큼 IocpEvent 객체를 생성하고 Accept 작업을 준비합니다.void Listener::RegisterAccept(IocpEvent* acceptEvent)
SocketUtils::AcceptEx를 호출하여 비동기 Accept 작업을 요청합니다.void Listener::ProcessAccept(IocpEvent* acceptEvent)
Accept 작업 완료:
Accept 재등록:
void Listener::Dispatch(IocpEvent* iocpEvent, int32 numOfBytes)
ProcessAccept)을 호출합니다.private:
HANDLE _iocpHandle;
_iocpHandle:bool IocpCore::Register(IocpObject* iocpObject)
IocpObject의 핸들을 IOCP 객체에 등록합니다.CreateIoCompletionPort 호출:bool IocpCore::Dispatch(uint32 timeoutMs)
GetQueuedCompletionStatus 호출:
timeoutMs 시간만큼 대기합니다.에러 처리:
WAIT_TIMEOUT 오류가 발생하면 작업이 없음을 의미합니다.작업 실행:
IocpObject의 Dispatch 메서드를 호출하여 작업을 처리합니다.IocpCore::IocpCore()
{
_iocpHandle = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);
assert(_iocpHandle != INVALID_HANDLE_VALUE);
}
IocpCore::~IocpCore()
{
::CloseHandle(_iocpHandle);
}
Listener, Session 등)는 이를 상속받아 구현됩니다.virtual HANDLE GetHandle() abstract;
virtual void Dispatch(IocpEvent* iocpEvent, int32 numOfBytes = 0) abstract;
GetHandle: Dispatch: IocpEvent의 객체로 표현됩니다.EventType type;
Session* session = nullptr;
type:session:AcceptEvent에서 사용).void IocpEvent::Init()
{
hEvent = 0;
Internal = 0;
InternalHigh = 0;
Offset = 0;
OffsetHigh = 0;
}
OVERLAPPED 구조체의 필드를 초기화합니다.private:
SOCKET _socket = INVALID_SOCKET;
NetAddress _netAddress = {};
Atomic<bool> _connected = false;
char _recvBuffer[1000];
_socket:_netAddress:_connected:_recvBuffer:void Session::Dispatch(IocpEvent* iocpEvent, int32 numOfBytes)
Recv 이벤트의 경우 수신 데이터를 처리합니다.private:
SOCKADDR_IN _sockAddr = {};
_sockAddr:IN_ADDR NetAddress::Ip2Address(const WCHAR* ip)
{
IN_ADDR address;
::InetPtonW(AF_INET, ip, &address);
return address;
}
IN_ADDR 구조체로 변환합니다.