나만의 작은 도서관

[C++] 게임서버 탐구 일지 #4. 다양한 소켓 옵션 본문

C++/Windows 게임서버

[C++] 게임서버 탐구 일지 #4. 다양한 소켓 옵션

pledge24 2024. 12. 28. 18:25

목차

  • 소켓 옵션 get, set
  • SO_KEEPALIVE
  • SO_LINGER
  • ::shutdown()
  • SO_SNDBUF, SO_RCVBUF
  • SO_REUSERADDR
  • TCP_NODELAY

소켓 옵션 get, set

소켓 옵션은 클라이언트-서버가 통신할 때 발생하는 특정 상황을 어떻게 대응할 것인지 설정하기위해 사용한다. 설정된 옵션을 가져오기 위해서는 getsockopt() 함수를, 옵션을 설정하기 위해선 setsockopt() 함수를 사용하면된다. 

 

getsockopt() 함수와 setsockopt() 함수는 동일한 인자를 받으며 총 5개의 인자를 넣어줘야한다. setsockopt() 함수를 보면 다음과 같이 생겼다.

int setsockopt(
  [in] SOCKET     s,
  [in] int        level,
  [in] int        optname,
  [in] const char *optval,
  [in] int        optlen
);
  • s: 옵션을 설정할 소켓
  • level: 옵션을 해석하고 처리할 주체. 주체로는 SOL_SOCKET(소켓 코드), IPPROTO_IP(IPv4), IPPROTO_TCP (TCP) 등이 있다.
  • optname: 옵션의 이름
  • optVal: 옵션의 설정값
  • optlen: optVal의 크기. sizeof(optVal)와 같이 넣어주면 된다.

 

소켓 옵션 링크

https://learn.microsoft.com/ko-kr/windows/win32/api/winsock/nf-winsock-setsockopt

 

setsockopt 함수(winsock.h) - Win32 apps

setsockopt 함수(winsock.h)는 소켓 옵션을 설정합니다.

learn.microsoft.com


SO_KEEPALIVE

통신을 하다보면 상대가 소리소문 없이 연결을 끊는 상황이 발생할 수 있다. SO_KEEPALIVE는 이러한 상황을 감지하기 위해 주기적으로 데이터를 보내 상대가 살아있는 지 확인한다.

 

아래 예제는 serverSocket이라는 이름의 생성해둔 SOCKET에 SO_KEEPALIVE를 적용하는 코드이다.

bool enable = true;
::setsockopt(serverSocket, SOL_SOCKET, SO_KEEPALIVE, (char*)&enable, sizeof(enable));

SO_LINGER

통신을 종료하는 상황에 미처 보내지 못한 데이터가 송신 버퍼에 남아있을 수 있다. 이 때 아직 못 보낸 데이터를 마저 보낼 것인지, 버릴 것인지 선택하기 위한 옵션이 바로 SO_LINGER이다.

 

아래 예제는 serverSocket이라는 이름의 생성해둔 SOCKET에 SO_LINGER를 적용하는 코드이다.

// LINGER 생성
LINGER linger;
linger.l_onoff = 1;     // 1은 on, 0은 off
linger.l_linger = 5;    // 대기 시간
::setsockopt(serverSocket, SOL_SOCKET, SO_LINGER, (char*)&linger, sizeof(linger));

::shutdown()

shutdown()은 통신을 종료하기 전에 "데이터를 보내거나 받지 않겠다"고 알리는 함수이다. 옵션 설정에 따라 송신 버퍼(SD_SEND) 또는 수신 버퍼(SD_RECV)만 닫거나(하나만 막는 걸 Half-close 라고 부른다) 둘 다 닫을 수 있다(SD_BOTH).

 

MMORPG에선 "통신이 끊기면 너 잘못"이라 치부하고 넘기는 경우가 많기 때문에 게임 서버에선 shutdown()을 굳이 사용하지 않는다.

 

아래 예제는 serverSocket이라는 이름의 생성해둔 SOCKET에 송신 버퍼를 닫는 코드이다.

::shutdown(serverSocket, SD_SEND); // 데이터를 보내지 않겠다고 알림
.
.
.
::closesocket(serverSocket);	// 소켓 리소스 반환

SO_SNDBUF, SO_RCVBUF

SO_SNDBUF, SO_RCVBUF는 각각 송신 버퍼의 크기, 수신 버퍼의 크기를 의미한다. setsockopt()를 통해 버퍼 크기를 변경하거나, getsockopt()을 통해 크기를 읽어올 수 있다.

 

아래 예제는 serverSocket이라는 이름의 생성해둔 SOCKET에 송/수신 버퍼의 크기를 출력하는 코드이다.

int32 sendBufferSize;
int32 optionLen = sizeof(sendBufferSize);
::getsockopt(serverSocket, SOL_SOCKET, SO_SNDBUF, (char*)&sendBufferSize, &optionLen);
cout << "송신 버퍼 크기 : " << sendBufferSize << '\n';	

int32 recvBufferSize;
optionLen = sizeof(recvBufferSize);
::getsockopt(serverSocket, SOL_SOCKET, SO_RCVBUF, (char*)&recvBufferSize, &optionLen);
cout << "수신 버퍼 크기 : " << recvBufferSize << '\n';

// 실행 결과
// 송신 버퍼 크기 : 65536
// 수신 버퍼 크기 : 65536

SO_REUSERADDR

SO_REUSERADDR는 사용한 IP주소 및 port를 재사용하고 싶을 때 사용하는 옵션이다. 서버가 강제로 닫히는 경우에 사용 중이었던 IP주소 및 port가 일정시간이 지날 때까지 서버를 재시작하지 못할 수 있다. 이럴때 SO_REUSEADDR 옵션을 켜서 재사용을 허용하면 서버를 재시작하지 못하는 불편함을 해결할 수 있다.

 

설정해두면 개발 단계시 편하니 어지간하면 설정하는 것이 좋다.

 

아래 예제는 serverSocket이라는 이름의 생성해둔 SOCKET에 주소 재사용을 활성화하는 코드이다.

{
    bool enable = true;
    ::setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, (char*)&enable, sizeof(enable));
}

TCP_NODELAY

TCP에는 Nagle 알고리즘이 적용되어 있다. (Nagle 알고리즘은 데이터가 충분히 크면 보내고, 그렇지 않으면 충분히 쌓일 때까지 대기하며 전송을 미루는 알고리즘이다.)

 

Nagle 알고리즘 사용 시 모아서 보내기 때문에 작은 패킷이 불필요하게 많이 생성되는 일을 막을 수 있다는 장점이 있지만, 반응성이 낮아진다는 단점이 있기 때문에 반응성이 매우 중요한 게임서버에서는 사용하지 않는다. (모아서 처리하고 싶은 경우 코드를 짜는 것이 더 편하기 때문인 것도 있다.)

 

아래 예제는 serverSocket이라는 이름의 생성해둔 SOCKET의 TCP에서 Nagle 알고리즘을 비활성화하는 코드이다.

{
    bool enable = true;
    ::setsockopt(serverSocket, IPPROTO_TCP, TCP_NODELAY, (char*)&enable, sizeof(enable));
}

 


깃헙 코드

https://github.com/pledge24/WinGameServerPractice/tree/b5fc2aeb07b408f8c973800b1603ea4f8cc37483

 

GitHub - pledge24/WinGameServerPractice

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

github.com