Blocking: 소켓 함수 호출 시 조건이 만족되지 않아 블로킹되는 상황을 막을 수 있다.
Non-Blocking: 소켓 함수 호출 시 조건이 만족되지 않아 나중에 다시 호출해야하는 상황을 막을 수 있다.
지금까지 실습했던 내용의 Socket들은 기본적으로 Blocking이 기본 설정이였다.
Blocking 방식은 기본적으로 막는다 라는 의미인데. 이는 무언가가 완료 될 때까지 대기 한다는 의미이다. 하지만 여러 유저가 Send, Recv를 동시에 하는 상황에서 만약 한쪽에 대기상황이 걸리게 된다면 실시간 통신에서 이는 좋은 점이 아니다. 그렇기에 사용하는 것이 Non-Blocking 방식이지만 이도 확실한 해결법은 아니다.
Non-Blocking 방식을 사용하더라도 준비가 완전히 되지 않는 소켓을 전송하고 이를 확인하기 위해 함수를 호출하는 형식은 자원(CPU)의 낭비를 초래한다.
그렇기에 Non-Blocking 방식을 사용하더라도 준비가 완료된 소켓을 파악하고 이를 먼저 전송하는것이 Select Model이다.
// Select 모델 = (select 함수가 핵심이 되는)
// 소켓 함수 호출이 성공할 시점을 미리 알 수 있다!
// 문제 상황)
// 수신버퍼에 데이터가 없는데, read 한다거나!
// 송신버퍼가 꽉 찼는데, write 한다거나!
// - 블로킹 소켓 : 조건이 만족되지 않아서 블로킹되는 상황 예방
// - 논블로킹 소켓 : 조건이 만족되지 않아서 불필요하게 반복 체크하는 상황을 예방
// socket set
// 1) 읽기[ 2 ] 쓰기[ ] 예외(OOB)[ ] 관찰 대상 등록
// OutOfBand는 send() 마지막 인자 MSG_OOB로 보내는 특별한 데이터
// 받는 쪽에서도 recv OOB 세팅을 해야 읽을 수 있음
// 2) select(readSet, writeSet, exceptSet); -> 관찰 시작
// 3) 적어도 하나의 소켓이 준비되면 리턴 -> 낙오자는 알아서 제거됨
// 4) 남은 소켓 체크해서 진행
// fd_set set;
// FD_ZERO : 비운다
// ex) FD_ZERO(set);
// FD_SET : 소켓 s를 넣는다
// ex) FD_SET(s, &set);
// FD_CLR : 소켓 s를 제거
// ex) FD_CLR(s, &set);
// FD_ISSET : 소켓 s가 set에 들어있으면 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);
}
// [옵션] 마지막 timeout 인자 설정 가능
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)
{
// TODO : 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)
{
// TODO : sessions 제거
continue;
}
s.sendBytes += sendLen;
if (s.recvBytes == s.sendBytes)
{
s.recvBytes = 0;
s.sendBytes = 0;
}
}
}
}