MMOServer

WSA(window socket api)EventSelect (c++)

이야기prog 2025. 4. 3. 22:55
#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;
	int32 sendBytes = 0;
};
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;
	}

	u_long on = 1;
	if (::ioctlsocket(serverSocket, FIONBIO, &on) == INVALID_SOCKET) // non-block
		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;

	// WSAEventSelect = (WSAEventSelect 함수가 핵심이 됨)
	// 소켓과 관련된 네트워크 이벤트를 [이벤트 객체]를 통해 감지

	// 이벤트 객체 관련 함수들
	// 생성 : WSACreateEvent (수동 리셋 + Non-signaled 상태 시작)
	// 삭제 : WSACloseEvent
	// 신호 상태 감지 : WSAWaitForMultipleEvents
	// 구체적인 네트워크 이벤트 알아내기 : WSAEnumNetworkEvents
	
	// 소켓 <-> 이벤트 객체 연동
	// WSAEventSelect(socket, event, networkEvents)
	// 네트워크 이벤트는
	// FD_ACCEPT : 접속한 클라가 있는지? accept
	// FD_READ : 데이터 수신 가능 recv, recvfrom
	// FD_WRITE : 데이터 송신 가능 send, sendto
	// FD_CLOSE : 상대가 접속 종료
	// FD_CONNECT : 통신을 위한 연결 절차 완료
	// FD_OOB

	// 주의 사항
	// WSAEventSelect : 함수 호출 시, 해당 소켓은 자동으로 non-block 모드 전환
	// accept() 함수 리턴하는 소켓은 listenSocket과 동일한 속성을 갖는다.
	// - 따라서 clientSocket은 FD_READ, FD_WRITE 등을 다시 등록 필요
	// 드물게 WSAEWOULDBLOCK 오류가 뜰 수 있으니 예외 처리 필요
	// - 이벤트 발생 시, 적절한 소켓 함수 호출해야 함.
	// - 아니면 다음 번에는 동일 네트워크 이벤트가 발생하지 않음
	// ex) FD_READ 이벤트 떴으면 recv()관련 호출해야 하고, 안하면 FD_READ가 발생하지 않음

	// 1) count, event
	// 2) waitAll : 모두 기다리거나 하나만 완료 되어도 되는지
	// 3) timeout
	// 4) 지금은 false
	// return : 완료된 첫번째 인덱스
	// WSAWaitForMultipleEvents

	//1) socket
	//2) eventObject : socket 과 연동된 이벤트 객체 핸들을 넘겨주면, 이벤트 객체를 non-signaled
	//3) networkEvent : 네트워크 이벤트 / 오류 정보가 저장
	//WSAEnumNetworkEvents

	vector<WSAEVENT> wsaEvents;
	vector<Session> sessions;
	sessions.reserve(100);

	WSAEVENT listenEvent = ::WSACreateEvent();
	wsaEvents.push_back(listenEvent);
	sessions.push_back(Session{ serverSocket });
	if (::WSAEventSelect(serverSocket, listenEvent, FD_CLOSE | FD_ACCEPT) == SOCKET_ERROR) // serverSocket <-> listenEvent 연결 -> serverSocket을 관찰하면서 
		return 0;																		// FD_CLOSE, FD_ACCEPT 발생하면 알려줌 (WSAWaitForMultipleEvents)

	while (true) {

		int32 index = ::WSAWaitForMultipleEvents(wsaEvents.size(), &wsaEvents[0], FALSE, WSA_INFINITE, FALSE);

		if (index == WSA_WAIT_FAILED)
			continue;

		//WSAWaitForMultpleEvents return값이 WSA_WAIT_EVENT_0 + index라서 뺴주고 사용하면 됨
		index -= WSA_WAIT_EVENT_0;

		// ::WSAResetEvent(wsaEvents[index]); signal -> non signal인데, WSAENumNetworkEvents에서 두번째 인수에 들어간 이벤트는 리셋시켜줌 
		// 그래서 굳이 쓸 필요가 없다.

		WSANETWORKEVENTS networkEvents;
		if (::WSAEnumNetworkEvents(sessions[index].socket, wsaEvents[index], &networkEvents) == SOCKET_ERROR) {
			continue;
		}

		// Listener 소켓 체크
		if (networkEvents.lNetworkEvents & FD_ACCEPT) {
			
			//Error Check
			if (networkEvents.iErrorCode[FD_ACCEPT_BIT] != 0) {
				continue;
			}

			SOCKADDR_IN clientAddr;
			int32 addrLen = sizeof(clientAddr);

			SOCKET clientSocket = ::accept(serverSocket, (SOCKADDR*)&clientAddr, &addrLen);
			if (clientSocket != INVALID_SOCKET) {
				cout << "Client Connect" << endl;

				WSAEVENT clientEvent = ::WSACreateEvent();

				wsaEvents.push_back(clientEvent);
				sessions.push_back(Session{ clientSocket });
				if (::WSAEventSelect(clientSocket, clientEvent, FD_CLOSE | FD_WRITE | FD_READ) == SOCKET_ERROR)
					return 0;
			}
		}

		if (networkEvents.lNetworkEvents & FD_READ || networkEvents.lNetworkEvents & FD_WRITE) {
			if ((networkEvents.iErrorCode[FD_READ_BIT] != 0) && (networkEvents.lNetworkEvents & FD_READ))
				continue;
			if ((networkEvents.iErrorCode[FD_WRITE_BIT] != 0) && (networkEvents.lNetworkEvents & FD_WRITE))
				continue;

			Session& s = sessions[index];

			//READ
			if (s.recvBytes == 0) {
				int32 recvLen = ::recv(s.socket, s.recvBuffer, BUFSIZE, 0);
				if ((recvLen == SOCKET_ERROR) || (WSAGetLastError() == WSAEWOULDBLOCK)) {
					//TODO
					// session remove
					continue;
				}

				s.recvBytes = recvLen;
				cout << "Recv Data: " << recvLen << endl;
			}

			//WRITE
			if (s.sendBytes < s.recvBytes) {
				int32 sendLen = ::send(s.socket, &s.recvBuffer[s.sendBytes], s.recvBytes - s.sendBytes, 0);
				if ((sendLen == SOCKET_ERROR) || (WSAGetLastError() == WSAEWOULDBLOCK)) {
					//TODO
					// session remove
					continue;
				}

				cout << "Send Data: " << sendLen << endl;
				s.sendBytes += sendLen;
				if (s.sendBytes == s.recvBytes) {
					s.sendBytes = 0;
					s.recvBytes = 0;
				}
			}
		}

		if (networkEvents.lNetworkEvents & FD_CLOSE) {
			//TODO : session remove
			SOCKET& tmp = sessions[index].socket;
			sessions.erase(remove_if(sessions.begin(), sessions.end(), [index, &sessions](Session& s) {return sessions[index].socket == s.socket;}), sessions.end());
			::closesocket(tmp);
			::WSACloseEvent(wsaEvents[index]);
			cout << "close" << endl;
			
		}

	}



	::closesocket(serverSocket); // 소켓 리소스 반환
	::WSACleanup(); // 윈속 종료
	return 0;
}

WSAEvent와 socket을 WSAEventSelect로 결합시켜 socket을 관찰하다가 networkEvent가 발생하면 WSAEvent에 반환

-> WSAWaitForMultipleEvents에서 WSAEvent의 index를 반환 -> WSAEnumNetworkEvents 함수로 WSAENUMNETWORKEVENTS 구조체에 어떤 networkEvent가 발생하였는지, 에러가 있는지를 받아서 활용할 수 있음.

'MMOServer' 카테고리의 다른 글

Overlapped 모델[콜백 기반](C++)  (0) 2025.04.07
Overlapped 모델[이벤트 기반](C++)  (0) 2025.04.04
Socket_select방식(C++)  (0) 2025.04.03
Non-Blocking Socket(C++)  (0) 2025.04.02
sockopt(C++)  (0) 2025.04.02