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문을 돌면서 계속 초기화 해주어야한다.
반응형