1. Select
1.1 Non-blocking Socket의 문제
- 함수의 결과를 예상X
- 패킷 수신 여부와 관계없이 바로 리턴 → buf의 상태가 어떤지 알 방법X
- 소켓이 Read/Write 할 수 있는 상태인지 아닌지 판별해 주는 select 함수가 필요
1.2 Select
- Select는 읽기, 쓰기, 예외 및 관찰 대상으로 이루어짐
- 적어도 하나의 소켓이 준비되면 리턴
- 데이터를 전송할 소켓 및 데이터, 데이터 크기 저장
- 클라이언트에서 데이터를 전송할 때 다른 고유 번호가 존재
- 클라이언트가 가지고 있는 데이터를 받기 위해 Session 생성
struct Session
{
SOCKET socket = INVALID_SOCKET;
char recvBuffer[MAX_BUFFER] = {};
};
- Select 함수
- nfds
- 리눅스 환겨에서 사용되는 변수
- readfds
- read가 가능한 상태인지 확인하고 싶은 소켓들의 집합
- writefds
- write가 가능한 상태인지 확인하고 싶은 소켓들의 집합
- exceptfds
- 에러 발생 여부를 확인하고 싶은 소켓들의 집합
- timeout
- 최대 기다리는 시간
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, const timeval* timeout)
- fd_set
- 여러개의 FileDescriptor를 select함수에서 읽기/쓰기/오류에 대한 event발생 여부 체크 모록을 관리
- select 실행 후 event가 발생한 fd인지 여부를 기록하는 구조체
int main()
{
// Init WSA, Socket Create, Server IP/HOST
// Non-Blocking Socket, BIND, LISTEN
// fd_set 타입 변수 선언
fd_set readSet;
fd_set writeSet;
// 클라이언트로 전달 받은 소켓 저장
vector<Session> sessions;
// Close WSA
}
- Select
- 위에서 샐성한 fd_set을 이용하여 초기화 후 소켓에 어떠한 값을 이용할 지 등록
- 클라이언트로 부터 받는 값도 있으므로 클라이언트의 fd_set 값도 설정
- 사용 가능 여부를 판단하고 사용 가능한 소켓의 개수를 반환
int main()
{
// Init WSA, Socket Create, Server IP/HOST
// Non-Blocking Socket, BIND, LISTEN
// fd_set 타입 변수 선언 및 클라이언트로 전달 받은 소켓 저장
while (true)
{
// Set Socket Init
FD_ZERO(&readSet);
FD_ZERO(&writeSet);
// Socket 등록 / 추가
FD_SET(listenSocket, &readSet);
FD_SET(listenSocket, &writeSet);
// 클라이언트로 받은 소켓 등록
for (Session& s : sessions) FD_SET(s.socket, &readSet);
for (Session& s : sessions) FD_SET(s.socket, &writeSet);
timeval tv{ 2, 0 }; // 시간 제한 2초 설정
// 0, 0의 값을 전달 시 즉시 리턴
int32 socketSelect = ::select(0, &readSet, &writeSet, nullptr, nullptr);
if (socketSelect == SOCKET_ERROR) break;
}
// Close WSA
}
- FD_ISSET
- readSet에서 설정한 소켓이 있는 지 확인
- select 함수로 인해 readSet 및 writeSet이 아닌 모든 값은 자동으로 버림
- accept된 클라이언트가 있으면 Session의 값에 추가
- session은 클라이언트로 전달 받은 소켓을 저장하는 값
int main()
{
// Init WSA, Socket Create, Server IP/HOST
// Non-Blocking Socket, BIND, LISTEN
// fd_set 타입 변수 선언 및 클라이언트로 전달 받은 소켓 저장
while (true)
{
// Set Socket Init & 등록 / 추가
// 클라이언트로 받은 소켓 등록 및 select 사용
// 2중 체크 - readSet에 listenSocket이 있는 지 확인
if (FD_ISSET(listenSocket, &readSet))
{
SOCKADDR_IN clientAddr;
::memset(&serverAddr, 0, sizeof(serverAddr));
int32 addrLength = sizeof(clientAddr);
SOCKET clientSocket = ::accept(listenSocket, reinterpret_cast<SOCKADDR*>(&clientAddr), &addrLength);
if (clientSocket == INVALID_SOCKET)
if (::WSAGetLastError() == WSAEWOULDBLOCK) continue; // 문제가 되는 상황X
sessions.push_back(Session{ clientSocket });
}
}
// Close WSA
}
- Listen Socket으로부터 클라이언트와 서버와 연결된 소켓은 주고 받을 수 있게 설정
int main()
{
// Init WSA, Socket Create, Server IP/HOST
// Non-Blocking Socket, BIND, LISTEN
// fd_set 타입 변수 선언 및 클라이언트로 전달 받은 소켓 저장
while (true)
{
// Set Socket Init & 등록 / 추가
// 클라이언트로 받은 소켓 등록 및 select 사용
// 2중 체크 - readSet에 listenSocket이 있는 지 확인
// accept
for (Session& s : sessions)
{
if (FD_ISSET(s.socket, &readSet))
{
int32 recvLength = ::recv(s.socket, s.recvBuffer, MAX_BUFFER, 0);
if (recvLength == INVALID_SOCKET) continue;
cout << s.recvBuffer << endl;
}
}
}
// Close WSA
}
2. Client
- https://choiprogramming.tistory.com/106
[SEVER] Non-blocking Socket
1. Blocking Socket 1.1 Blocking - accept, connect, recv, send - 대부분 소켓 관련 함수들은 호출 스레드를 블로킹 상태로 만듦 - 블로킹 상태: 현재 스레드가 더 이상 코드를 진행하지 않고 block 상태로 멈춰 있
choiprogramming.tistory.com
'Create Game > [Window API] Game Client & Game Server' 카테고리의 다른 글
[SERVER] Overlapped I/O (0) | 2023.01.14 |
---|---|
[SERVER] WSAEvent Select (0) | 2023.01.13 |
[SEVER] Non-blocking Socket (1) | 2023.01.11 |
[SEVER] UDP Server (0) | 2023.01.10 |
[SEVER] TCP Server (0) | 2023.01.10 |