MMOServer

IOCP(C++)

이야기prog 2025. 4. 7. 20:47
#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