Select Model
카테고리: Server
인프런에 있는 루키스님의 게임 서버 강의를 듣고 정리한 내용입니다.
Select Model
데이터를 주고 받기 전에 소켓의 유효성을 체크
- 읽기[ ] 쓰기[ ] 예외[ ] 관찰 대상 등록
- 예외(OOB)는
send
함수 마지막에 OOB 옵션 적용.recv
도 OOB로만 받을 수 있음 (근데 잘 안씀)
- 예외(OOB)는
select(readSet, writeSet, exceptSet, timeout)
: 관찰 시작- Set 중 적어도 하나의 소켓이 준비되면 준비된 소켓 갯수 리턴 (낙오된 소켓을 set에서 제거됨)
- 살아남은 소켓들로만 데이터 처리
// server side
const int32 BUFSIZE = 1000;
struct Session
{
SOCKET socket = INVALID_SOCKET;
char recvBuffer[BUFSIZE] = {};
int32 recvBytes = 0;
int32 sendBytes = 0;
};
vector<Session> sessions;
sessions.reserve(100);
fd_set reads;
fd_set writes;
while (true)
{
// 소켓 셋 초기화
FD_ZERO(&reads);
FD_ZERO(&writes);
// ListenSocket 등록
FD_SET(listenSocket, &reads);
// 소켓 등록
for (Session& s : sessions)
{
if (s.recvBytes <= s.sendBytes)
FD_SET(s.socket, &reads);
else
FD_SET(s.socket, &writes);
}
int32 retVal = ::select(0, &reads, &writes, nullptr, nullptr);
if (retVal == SOCKET_ERROR)
break;
// Listener 소켓 체크
if (FD_ISSET(listenSocket, &reads))
{
SOCKADDR_IN clientAddr;
int32 addrLen = sizeof(clientAddr);
SOCKET clientSocket = ::accept(listenSocket, (SOCKADDR*)&clientAddr, &addrLen);
if (clientSocket != INVALID_SOCKET)
{
cout << "Client Connected" << endl;
sessions.push_back(Session{ clientSocket });
}
}
// 나머지 소켓 체크
for (Session& s : sessions)
{
// Read
if (FD_ISSET(s.socket, &reads))
{
int32 recvLen = ::recv(s.socket, s.recvBuffer, BUFSIZE, 0);
if (recvLen <= 0)
{
// sessions 제거
continue;
}
s.recvBytes = recvLen;
}
// Write
if (FD_ISSET(s.socket, &writes))
{
// 논블로킹 모드 -> 일부만 보낼 수가 있음 (상대방 수신 버퍼 상황에 따라)
int32 sendLen = ::send(s.socket, &s.recvBuffer[s.sendBytes], s.recvBytes - s.sendBytes, 0);
if (sendLen == SOCKET_ERROR)
{
// sessions 제거
continue;
}
s.sendBytes += sendLen;
if (s.recvBytes == s.sendBytes)
{
s.recvBytes = 0;
s.sendBytes = 0;
}
}
}
}
1. FD_SET
// set 만들기 (read/write)
fd_set set;
SOCKET s;
// FD_ZERO : set 비우기
ex) FD_ZERO(set);
// FD_SET : 소켓 s를 넣기
ex) FD_SET(s, &set);
// FD_CLR : 소켓 s를 제거
ex) FD_CLR(s, &set);
// FD_ISSET : 소켓 s가 set에 들어있는지 확인
ex) FD_ISSET(s, &set);
const int32 BUFSIZE = 1000;
struct Session
{
SOCKET socket = INVALID_SOCKET;
char recvBuffer[BUFSIZE] = {};
int32 recvBytes = 0;
int32 sendBytes = 0;
};
vector<Session> sessions;
sessions.reserve(100);
fd_set reads;
fd_set writes;
while (true)
{
// 소켓 셋 초기화
FD_ZERO(&reads);
FD_ZERO(&writes);
// ListenSocket 등록
FD_SET(listenSocket, &reads);
// 소켓 등록
for (Session& s : sessions)
{
if (s.recvBytes <= s.sendBytes)
FD_SET(s.socket, &reads);
else
FD_SET(s.socket, &writes);
}
}
// ...
- 한 서버에 여러 클라가 붙을 경우 각 소켓을 세션으로 관리
- set의 경우 소켓의 유효성 검사를 통해 while문 돌때마다 변경됨 -> 매번 초기화&등록
- listenSocket의 경우 accept 요청 데이터를 받아와 읽어야 하므로 read set에 등록
- 일반적인 session들은 해당 상황에 맞춰 read / write set에 등록
Select
while (true)
{
// 소켓 셋 초기화
// 소켓 등록
int32 retVal = ::select(0, &reads, &writes, nullptr, nullptr);
if (retVal == SOCKET_ERROR)
break;
// Listener 소켓 체크
if (FD_ISSET(listenSocket, &reads))
{
SOCKADDR_IN clientAddr;
int32 addrLen = sizeof(clientAddr);
SOCKET clientSocket = ::accept(listenSocket, (SOCKADDR*)&clientAddr, &addrLen);
if (clientSocket != INVALID_SOCKET)
{
cout << "Client Connected" << endl;
sessions.push_back(Session{ clientSocket });
}
}
// 나머지 소켓 체크
for (Session& s : sessions)
{
// Read
if (FD_ISSET(s.socket, &reads))
{
int32 recvLen = ::recv(s.socket, s.recvBuffer, BUFSIZE, 0);
if (recvLen <= 0)
{
// sessions 제거
continue;
}
s.recvBytes = recvLen;
}
// Write
if (FD_ISSET(s.socket, &writes))
{
// 논블로킹 모드 -> 일부만 보낼 수가 있음 (상대방 수신 버퍼 상황에 따라)
int32 sendLen = ::send(s.socket, &s.recvBuffer[s.sendBytes], s.recvBytes - s.sendBytes, 0);
if (sendLen == SOCKET_ERROR)
{
// sessions 제거
continue;
}
s.sendBytes += sendLen;
if (s.recvBytes == s.sendBytes)
{
s.recvBytes = 0;
s.sendBytes = 0;
}
}
}
}
select(readSet, writeSet, exceptSet, timeout)
: 관찰 시작 (낙오자들 set에서 제거)FD_ISSET
함수를 통해 살아남은 소켓들에 대해서만 로직 처리- write set의 경우 상대방 상태에 따라 일부만 잘려서 보내질 수 있음 (거의 없다고는 하는데..)
- 데이터를 전부 보냈을 때(sendBytes == sendLen) bytes 크기 초기화
Result
- Select 모델 장점) 무작정
accept
,recv
하는 것이 아니라 준비된 소켓들에 대해서만 수행 - 매번 set 등록 & 관리..
- 단일 set에 들어갈 수 있는 소켓은 최대 64개
- 세션이 매우 많은 경우 set 여러 개로 관리해야 함
댓글 남기기