#include "pch.h"
#include <thread>
#include <atomic>
#include <mutex>
#include <future>
#include "CorePch.h"
#include "ThreadManager.h"
#include "RefCounting.h"
#include "Memory.h"
#include "TypeCast.h"
#include <WinSock2.h>
#include <MSWSock.h>
#include <WS2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
void HandleError(const char* str) {
int32 errCode = ::WSAGetLastError();
cout << str << " erroCode: " << errCode << endl;
}
const int BUFSIZE = 1000;
struct Session {
SOCKET socket = INVALID_SOCKET;
char recvBuffer[BUFSIZE] = {};
int32 recvBytes = 0;
};
enum IO_TYPE {
READ,
WRITE,
ACCEPT,
CONNECT,
};
struct OverlappedEx {
WSAOVERLAPPED overlapped = {};
int32 type = 0; // read, write, accept, connect
};
void WorkerThreadMain(HANDLE iocpHandle) {
while (true) {
DWORD byteTransferred = 0;
Session* session = nullptr;
OverlappedEx* overlappedEx = nullptr;
BOOL ret = ::GetQueuedCompletionStatus(iocpHandle, &byteTransferred, (ULONG_PTR*)&session, (LPOVERLAPPED*)&overlappedEx, INFINITE); // multi-thread에서도 잘 동작함 lock필요없이
if (ret == FALSE || byteTransferred == 0) {
// TODO: 연결 끊김
continue;
}
ASSERT_CRASH(overlappedEx->type == IO_TYPE::READ);
cout << "Recv Data IOCP = " << byteTransferred << endl;
WSABUF wsaBuf;
wsaBuf.buf = session->recvBuffer;
wsaBuf.len = BUFSIZE;
DWORD recvLen = 0;
DWORD flags = 0;
::WSARecv(session->socket, &wsaBuf, 1, &recvLen, &flags, &overlappedEx->overlapped, NULL);
}
}
int main() {
//윈도우 소켓 초기화
// 관련 정보가 wsaData에 채워짐
WSADATA wsaData;
if (::WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
return 0;
SOCKET serverSocket = ::socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket == INVALID_SOCKET) {
return 0;
}
SOCKADDR_IN serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = ::htonl(INADDR_ANY);
serverAddr.sin_port = ::htons(7777);
if (::bind(serverSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
return 0;
if (::listen(serverSocket, 10) == SOCKET_ERROR)
return 0;
cout << "Accept" << endl;
// IOCP (Completion Port)와 Overlapped 비교
// - APC -> Completion Port (쓰레드마다 있는건 아니고 1개, 중앙에서 관리하는 APC 큐느낌?)
// Alertable Wait -> CP 결과 처리를 GetQueuedCompletionStatus
// 쓰레드랑 궁합이 좋다.
// CreateIoCompletionPort
// GetQueuedCompletionStatus
vector<Session*> sessionManager;
// CP 생성
HANDLE iocpHandle = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
// WorkerThreads
for (int32 i = 0; i < 5; i++) {
GThreadManager->Launch([=]() {WorkerThreadMain(iocpHandle);});
}
// Main Thread Accept
while (true) {
SOCKET clientSocket;
SOCKADDR_IN clientAddr;
int32 addrLen = sizeof(clientAddr);
clientSocket = ::accept(serverSocket, (SOCKADDR*)&clientAddr, &addrLen);
if (clientSocket == INVALID_SOCKET)
return 0;
Session* session = new Session();
session->socket = clientSocket;
sessionManager.push_back(session);
cout << "client connected!" << endl;
// 소켓을 CP에 등록
::CreateIoCompletionPort((HANDLE)clientSocket, iocpHandle, /*Key값 아무값이나 다됨*/(ULONG_PTR)session, /*쓰레드 수(0일 시 코어 수)*/0);
WSABUF wsaBuf;
wsaBuf.buf = session->recvBuffer;
wsaBuf.len = BUFSIZE;
DWORD recvLen = 0;
DWORD flags = 0;
OverlappedEx* overlappedEx = new OverlappedEx();
overlappedEx->type = IO_TYPE::READ;
::WSARecv(clientSocket, &wsaBuf, 1, &recvLen, &flags, &overlappedEx->overlapped, NULL);
}
GThreadManager->Join();
::closesocket(serverSocket); // 소켓 리소스 반환
::WSACleanup(); // 윈속 종료
return 0;
}
소켓을 CP에 등록할 때, Session 주소와 overlapped 구조체의 주소를 넘겨주는게 통상적인데, 그 주소들이 delete되면 메모리 오염이 일어나기 때문에 지워도 ref counting이나 여러가지 방법으로 제어해야함.
'MMOServer' 카테고리의 다른 글
Overlapped 모델[콜백 기반](C++) (0) | 2025.04.07 |
---|---|
Overlapped 모델[이벤트 기반](C++) (0) | 2025.04.04 |
WSA(window socket api)EventSelect (c++) (0) | 2025.04.03 |
Socket_select방식(C++) (0) | 2025.04.03 |
Non-Blocking Socket(C++) (0) | 2025.04.02 |