나만의 작은 도서관

[C++] 게임서버 탐구 일지 #5. Winsock TCP 서버 - 논블로킹 본문

C++/Windows 게임서버

[C++] 게임서버 탐구 일지 #5. Winsock TCP 서버 - 논블로킹

pledge24 2024. 12. 28. 18:38

목차

  • 블로킹 방식의 문제점
  • 논블로킹 방식으로 echo 서버 코드 바꾸기
  • 결론
  • 깃헙 코드

블로킹 방식의 문제점

블로킹 함수를 호출 시, 함수 내 작업이 완료되기 전까지 호출자(caller)는 작업을 멈추고 대기하게 된다. 이러한 방식의 문제점은 함수의 작업을 하는동안 함수의 결과가 나오는 동안 처리해도 되는 작업들을 처리할 수 없다는 것이다. 기존의 TCP/UDP echo 서버에서 사용한 블로킹 함수는 다음과 같다.

  • accept -> 접속한 클라가 있을 때까지 블로킹
  • connect -> 서버에 접속 될 때까지 블로킹
  • send, sendto -> 요청한 데이터를 송신 버퍼에 복사될 때까지 블로킹
  • recv, recvfrom -> 수신 버퍼에 도착한 데이터가 있고, 이를 유저레벨 버퍼에 복사될 때까지 블로킹

즉, 위 함수들을 실행할 때마다 블로킹(blocking) 되어 컴퓨터가 먹통이 되고만다. 

 

클라건 서버건 컴퓨터가 먹통이 되는건 성능에 굉장히 치명적이다. 이를 해결하기 위해 멀티스레드를 활용 해보려해도 유저가 많아지면 context switching 비용만 잔뜩 들 것이다.

 

결국 블로킹 방식의 통신은 효율적이지 않기 때문에 호출자의 작업을 막지 않는 논블로킹(non-blocking) 방식으로 변경해야한다.


논블로킹 방식으로 echo 서버 코드 바꾸기

ioctlsocket() 함수 추가

논블로킹 방식을 사용해도 기존의 echo 서버랑 구조상 크게 바뀌는 부분은 없다. 대신 논블로킹을 사용하려면 ioctlsocket() 함수를 통해 소켓에 논블로킹을 설정해야한다. 설정 방법은 아래와 같다.

 

+) 참고로, ioctlsocket은 I/O control socket의 약자인 듯 하다.

SOCKET listenSocket = ::socket(AF_INET, SOCK_STREAM, 0);
if (listenSocket == INVALID_SOCKET)
    return 0;
        
// ioctlsocket(): 소켓의 I/O 모드를 제어하는 함수
// FIONBIO: 논-블로킹 방식으로 전환
u_long on = 1;
if (::ioctlsocket(listenSocket, FIONBIO, &on) == INVALID_SOCKET)
    return 0;

 

논블로킹 방식의 코드 구조

논블로킹 방식이라도 기존에 사용하던 recv(), send()와 같은 함수들을 사용한다. 대신, 함수 작업이 완료되지 않더라도 함수를 빠져나오기 때문에 이에 대한 추가적인 처리를 해야한다. send()함수에 예제로 들면 아래와 같다.

// Send (send가 완료될 때까지 while문 루프를 돈다.)
while (true)
{
    if (::send(clientSocket, sendBuffer, sizeof(sendBuffer), 0) == SOCKET_ERROR)
    {
        // 원래 블록했어야 했는데... 너가 논블로킹으로 하라며?
        if (::WSAGetLastError() == WSAEWOULDBLOCK)
            continue;

        // Error!
        // 에러 처리 코드...
        break;
    }

    // send()함수 처리 완료 시 진입!
    cout << "Send Data! Len= " << sizeof(sendBuffer) << '\n';
    break;
}

 

이렇듯 논블로킹 함수(send(), recv(), connect()와 같이 블로킹 됐었던 함수들)를 사용할 때마다 while 문이 추가되며, 루프를 돌면서 논블로킹 작업이 완료되었는지 (작업 처리 중 = WSAEWOULDBLOCK) 확인해야한다.

 

실행 결과

클라이언트 또한 논블로킹 방식으로 변경하여(코드는 깃헙을 참고) 실행해도 기존과 동일하게 작동하는 것을 확인할 수 있다.

 

사실 성능이 떨어졌다.

지금의 논블로킹 방식의 echo 서버는 논블로킹 함수가 실행되는 동안 호출자가 아무것도 안하기 때문에 이전 블로킹 방식보다 성능이 떨어진다. 이렇듯 논블로킹 방식으로 전환되었다고 반드시 블로킹 방식보다 성능이 좋아진다 보장하지는 않는다.


결론

논블로킹 방식을 사용함으로써 함수가 실행되는 동안에도 호출자가 작업을 실행할 수 있게 되었지만 1) 논블로킹 함수를 사용할때마다 while문을 사용해야 한다는 점, 2) 코드가 더러워진다는 점 등 단점도 많아지게된다. 

 

그래서 지금보다 좀 더 효율적이고 정돈된 방식으로 소켓 통신을 할 수 있었으면 하는데, 이러한 방식들이 이미 여러 가지 존재하며 이를 소켓 모델이라고 부른다.


깃헙 코드

https://github.com/pledge24/WinGameServerPractice/tree/8ead4e5c3eb53b7f9dc40453b0f273f9bb0205b9

 

GitHub - pledge24/WinGameServerPractice

Contribute to pledge24/WinGameServerPractice development by creating an account on GitHub.

github.com