본문 바로가기

Game Server

[C++/게임서버] 논블로킹 소켓

사실 지금까지 쓴 소켓은 블로킹 소켓이다. 이에 대한 증거 함수는 다음과 같다.

  1. accept : 접속한 클라이언트가 있을 경우
  2. connect : 서버 접속 성공했을 경우
  3. send, sendto : 요청한 데이터를 송신 버퍼에 복사했을 때
  4. recv, recvfrom : 수신 버퍼에 도착한 데이터가 있고, 이를 유저 레벨에 복사했을 때

문제는 이 함수들은 이에 대한 역할을 하기 전까지 계속 멈춰있다는 점이다. 그래서 이를 해결하기 위해서 예로 들어 5000명이 온다고 가정할 때 5000개의 스레드를 만드는 것은 굉장히 좋지 않은 발상이란 것이 느껴진다.

 

그래서 나온 개념이 논블로킹이다. 사실 이것이 모든 것을 해결해주지 않는다. 암튼 진행해보자면, windows 기준에서 이를 어떻게 만들 것인가? 일단 소켓 생성 과정은 다음과 같다.

 

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)
   return 0;

기존 방식과 다르게 ioctlsocket이란 함수가 나온다. 이 함수는 소켓의 IO 컨트롤 옵션을 주는 것으로 여기서는 FIONBIO 즉 논 블로킹 옵션을 주도록 하겠다.

 

아래는 aceept와 recv, send를 하는 과정이다. 

while(true)
{
   SOCKET clientSocket = accept(serverSocket, (SOCKADDR*)&clientAddr, &addrLen);

   // 에러같은데,,,?
   if(clientSocket == INVALID_SOCKET)
   {
      // 프로그래머가 논 블로킹이라고 했어요!
      if(::WSAGetLastError() == WSAWOULDBLOCK)
         continue;
         
      // 그 외 진짜 에러 상황에 따라서 if를 만들자.'
      break;
   }
   cout << "Client Successfully Connected" << endl;
   
   while(true)
   {
      char recvBuffer[1000];
      int32 recvLen = ::recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0);
      // 받은게 없는데?
      if(recvLen == SOCKET_ERROR)
      {
         // 논블로킹이라며..
         if(::WSAGetLastError() == WSAWOULDBLOCK)
            continue;
            
         // 에러처리
         break;
      }
      
      cout << "Recv Data Len" << recvLen << endl;
      
      // Send
      while(true)
      {
         if(::send(clientSocket, recvBuffer, recvLen, 0) == SOCKET_ERROR)
         {   
            // 논블로킹이라며..
            if(::WSAGetLastError() == WSAWOULDBLOCK)
               continue;
               
            // 진짜 에러네
            break;
         }
      }
   }
}

while 등으로 인해 코드가 좀 더럽긴 한데, 보자면 결국 while문을 계속 돌리면서 만약 이에 대한 처리가 필요한다면, 그 함수를 실행하고 만약 그게 아니라면 WSAGetLastError()를 통해서 WSAWOULDBLOCK인지 확인한 뒤 그게 맞다면 넘어가고 아니면 진짜 에러라 처리하는 식이다. 

 

문제는 블로킹하고 비교해도 효율이 그렇게 좋은 상황은 아니다. 왜냐면 while(true)를 통해서 계속 체크를 해야하며, 동시에 정확한 순서가 있기 때문에 이럴꺼면 차라리 블로킹을 하는 식을 하는 것이 나을 것이다. 그리고 3중 루프를 가지고 있기 때문에 굉장히 비효율적일 것이다. 그래서 다음 글부터 소켓 모델을 통해서 이런 문제를 해결해보겠다.