MMOServer

Overlapped 모델[이벤트 기반](C++)

이야기prog 2025. 4. 4. 01:31

client

 

#include "pch.h"
#include <iostream>

#include <WinSock2.h>
#include <MSWSock.h>
#include <WS2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
int main()
{
	//윈도우 소켓 초기화
	// 관련 정보가 wsaData에 채워짐
	WSADATA wsaData;
	if (::WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		return 0;

	SOCKET clientSocket = ::socket(AF_INET, SOCK_STREAM, 0);
	if (clientSocket == INVALID_SOCKET) {
		return 0;
	}

	u_long on = 1;
	if (::ioctlsocket(clientSocket, FIONBIO, &on) == INVALID_SOCKET) {
		// non-block
		return 0;	
	}

	SOCKADDR_IN serverAddr;

	serverAddr.sin_family = AF_INET;
	::inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);
	serverAddr.sin_port = ::htons(7777);

	//connect
	while (true) {
		if (::connect(clientSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
			if (::WSAGetLastError() == WSAEWOULDBLOCK) {
				continue;
			}

			if (::WSAGetLastError() == WSAEISCONN) // 이미 connect되었는데 또 시도
				break;

			//ERROR
			//break;
		}
	}
	cout << "Server Connect!" << endl;
	char sendBuffer[100] = "Hello, World!";

	WSAOVERLAPPED overlapped;
	WSAEVENT wsaEvent = ::WSACreateEvent();
	overlapped.hEvent = wsaEvent;
	while (true) {
		//send

		WSABUF wsabuf;
		wsabuf.buf = sendBuffer;
		wsabuf.len = 100;

		DWORD sendLen = 0;
		DWORD flags = 0;

		if (::WSASend(clientSocket, &wsabuf, 1, &sendLen, flags, &overlapped, nullptr) == SOCKET_ERROR) {
			if (::WSAGetLastError() == WSA_IO_PENDING) {
				::WSAWaitForMultipleEvents(1, &wsaEvent, FALSE, WSA_INFINITE, FALSE);
				::WSAGetOverlappedResult(clientSocket, &overlapped, &sendLen, FALSE, &flags);
			}

			//ERROR
			else {
				break;
			}
		}

		cout << sendBuffer << endl;
		cout << "Send Data Len : " << sizeof(sendBuffer) << endl;


		this_thread::sleep_for(1s);
	}
	::WSACloseEvent(wsaEvent);
	::closesocket(clientSocket); // 소켓 리소스 반환
	::WSACleanup(); // 윈속 종료

}

 

 

Server

 

#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;
	WSAOVERLAPPED overlapped = {};
};
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;
	
	// Overlapped IO (비동기 + 논블록)
	// - Overlapped 함수를 건다 (WSARecv, WSASend)
	// - overlapped 함수가 성공하였는지 확인 후,
	// -> 성공하였으면 결과 얻어서 처리
	// -> 실패하였으면 사유를 확인

	// 1) 비동기 입출력 소켓
	// 2) WSABUF 배열의 시작 주소 + 개수
	// 3) 보내고/받은 비트 수
	// 4) 상세 옵션 0
	// 5) WSAOVERLLAPED 구조체의 주소값
	// 6) 입출력이 완료되면 OS가 호출할 콜백 함수 (이번에는 안씀)
	//WSASend
	//WSARecv
	

	// Overlapped 모델 (이벤트 기반)
	// -비동기 입출력 지원하는 소켓 생성 + 통지 받기 위한 이벤트 객체 생성
	// -비동기 입출력 함수 호출(1에서 만든 이벤트 객체 같이 넘김)
	// -비동기 작업이 바로 완료되지 않으면, WSA_IO_PENDING 오류 코드
	// 운영체제(백그라운드)는 이벤트 객체를 signaled 상태로 만들어서 완료 상태 알려줌
	// -WSAWaitForMultipleEvents 함수 호출해서 이벤트 객체의 signal 판별
	// -WSAGetOverlappedResult 호출해서 비동기 입출력 결과 확인 및 데이터 확인

	// 1)비동기 소켓
	// 2)넘겨준 overlapped 구조체
	// 3)전송된 바이트 수
	// 4)비동기 입출력 작업이 끝날때까지 대기할지?
	// false (WSAWaitForMultipleEvents로 작업이 끝날때까지 대기할 것이기 때문에 false로 하면 됨)
	// 5)비동기 입출력 작업 관련 부가 정보 (거의 사용 안함)
	//WSAGetoverlappedResult

	while (true) {
		SOCKET clientSocket;

		SOCKADDR_IN clientAddr;
		int32 addrLen = sizeof(clientAddr);
		while (true) {
			clientSocket = ::accept(serverSocket, (SOCKADDR*)&clientAddr, &addrLen);
			if (clientSocket != INVALID_SOCKET)
				break;

			if (::WSAGetLastError() == WSAEWOULDBLOCK)
				continue;

			// 문제 있는 상황
			return 0;
		}

		Session s = Session{ clientSocket };
		WSAEVENT wsaEvent = ::WSACreateEvent();

		s.overlapped.hEvent = wsaEvent;
		cout << "client connected!" << endl;

		while (true) {
			WSABUF wsabuf;
			wsabuf.buf = s.recvBuffer;
			wsabuf.len = BUFSIZE;

			DWORD recvLen = 0;
			DWORD flags = 0;

			if (::WSARecv(s.socket, &wsabuf, 1, &recvLen, &flags, &s.overlapped, nullptr) == SOCKET_ERROR) {
				if (WSAGetLastError() == WSA_IO_PENDING) {
					// Pending
					::WSAWaitForMultipleEvents(1, &wsaEvent, FALSE, WSA_INFINITE, FALSE);
					::WSAGetOverlappedResult(s.socket, &s.overlapped, &recvLen, FALSE, &flags);
				}
				else {
					break;
					// error
				}


			}

			cout << "Data Received: " << recvLen << endl;
			cout << "Data detailed:" << s.recvBuffer << endl;
		}

		::closesocket(s.socket);
		::WSACloseEvent(wsaEvent);

	}


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

 

 

간단한 예제를 위해 전과 다르게 echo서버가 아닌 client에는 send, server에서는 recv만 하는 서버로 구현하였다.

Overlapped 모델부터는 논블락 소켓 + 비동기 함수로 사용되고 있다.

'MMOServer' 카테고리의 다른 글

IOCP(C++)  (0) 2025.04.07
Overlapped 모델[콜백 기반](C++)  (0) 2025.04.07
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