MMOServer

Socket_select방식(C++)

이야기prog 2025. 4. 3. 19:24
반응형
#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;

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

	//FD_SET, FD_ZERO, FD_ISSET, FD_CLR
	// reads, writes, except(예외 reads나 writes시 마지막에 추가로 주어서 긴급 상황 알림 받는 쪽도 except해야함)
	//select 반환시 낙오자는 알아서 없어짐
	fd_set reads; // read_set
	fd_set writes; // write_set

	while (true) {

		//소켓 셋 초기화
		FD_ZERO(&reads);
		FD_ZERO(&writes);

		// serverSocket 등록
		FD_SET(serverSocket, &reads);

		// 소켓 등록
		for (Session& s : sessions) {

			if (s.recvBytes <= s.sendBytes)
				FD_SET(s.socket, &reads);
			else
				FD_SET(s.socket, &writes);
		}

		// 첫번째 인자 linux랑 맞추려고 쓰는 인자, window는 0, 마지막 인자 timeout, 
		int32 retVal = ::select(0, &reads, &writes, nullptr, nullptr);
		if (retVal == SOCKET_ERROR)
			break;

		// Listen 소켓 체크
		if (FD_ISSET(serverSocket, &reads)) {
			SOCKADDR_IN clientAddr;
			int32 clientLen = sizeof(clientAddr);

			SOCKET clientSocket = ::accept(serverSocket, (SOCKADDR*)&clientAddr, &clientLen);
			if (clientSocket != INVALID_SOCKET) {
				cout << "Client Connected!" << endl;
				sessions.push_back(Session{ clientSocket });
			}
		}

		for (Session& s : sessions) {
			if (FD_ISSET(s.socket, &reads)) {
				int32 recvLen = ::recv(s.socket, s.recvBuffer, BUFSIZE, 0);
				if (recvLen <= 0) {
					//TODO : sessions 제거 (연결끊김 == 0)
					continue;
				}

				s.recvBytes = recvLen;
			}

			if (FD_ISSET(s.socket, &writes)) {
				// 블로킹 모드일 때, 모든 데이터 보냄
				// 논블로킹 모드일 때, 일부 데이터를 보낼 때도 있다.(구현상 거의 없다)
				int32 sendLen = ::send(s.socket, &s.recvBuffer[s.sendBytes], s.recvBytes - s.sendBytes, 0);
				if (sendLen == SOCKET_ERROR) {
					//TODO : sessions 제거 (error)
					continue;
				}

				s.sendBytes += sendLen;
				if (s.sendBytes == s.recvBytes) {
					s.sendBytes = 0;
					s.recvBytes = 0;
				}
			}
		}
	}


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

 

non-block socket에 select를 사용하였다. ECHO서버 

select방식은 select호출 시, 호출되지 못한 나머지 소켓들이 reads, writes에서 제거되고 호출된 소켓들만 남기 때문에 while문을 돌면서 계속 초기화 해주어야한다.

반응형