GameChoi 2023. 1. 12. 23:15

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