KoreanFoodie's Study

[C++ 게임 서버] 3-6. 소켓 옵션 본문

Game Dev/Game Server

[C++ 게임 서버] 3-6. 소켓 옵션

GoldGiver 2023. 9. 22. 23:25

Rookiss 님의 '[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버' 를 들으며 배운 내용을 정리하고 있습니다. 관심이 있으신 분은 꼭 한 번 들어보시기를 추천합니다!

[C++ 게임 서버] 3-6. 소켓 옵션

핵심 :

1. 소켓에도 옵션을 설정하고, 설정값을 불러올 수 있다. ::getsockopt 와 ::setsockopt 가 그것이다.

2. 소켓 옵션의 설정 단계는 3 가지로, 소켓 코드(SOL_SOCKET), IPv4(IPPROTO_IP), TCP 프로토콜(IPROTO_TCP) 이다.

3. SO_KEEPALIVE, SO_LINGER, SD_SEND, SO_REUSEADDR 등 자주 쓰이는 옵션을 눈에 익히면 좋다!

우리는 지금까지 소켓을 만들어서 서버-클라이언트 통신을 진행했는데, 사실 핸드폰에도 여러 기종이 있고 다양한 커스터마이징이 가능하듯이, 소켓도 여러 옵션을 줄 수 있다.

이를 설정하는 함수는 ::setsockopt 인데, 인자를 보면...

로 되어 있다. 문서는 여기를 참조하자 😊

하나하나 살펴보면, 먼저 level 이 있다.

Level 은 아래와 같은 인자를 가질 수 있는데,

이는 각 옵션을 어디에서 제어할지를 나타내는 것이라고 보면 된다. SOL_SOCKET 을 주면, 소켓 코드에서 옵션을 해석하고 처리하며, IPPROTO_IP 의 경우 IPv4 단계에서, IPPROTO_TCP 의 경우 TCP 프로토콜 단계에서 처리한다.

 이제 진짜 예시를 보자! 😀

 

// SO_KEEPALIVE = 주기적으로 연결 상태 확인 여부 (TCP Only)
// 상대방이 소리소문 없이 연결을 끊는다면?
// 주기적으로 TCP 프로토콜 연결 상태 확인 -> 끊어진 연결 감지

// 소켓 옵션 설정
bool enable = true;
::setsockopt(serverSocket, SOL_SOCKET, SO_KEEPALIVE, (char*)&enable, sizeof(enable));

먼저, SO_KEEPALIVE 이다. 이 녀석은, TCP 에서 프로토콜의 연결 상태를 확인하는 옵션을 설정한다.

 

만약 연결이 끊어졌다면, 송수신 버퍼에 있는 내용은 어떻게 되어야 할까?

이와 관련된 옵션이... SO_LINGER 인데, 다음 코드를 보자.

// SO_LINGER = 지연하다
// 송신 버퍼에 있는 데이터를 보낼 것인가? 날릴 것인가?

// onoff = 0 이면 closesocket() 이 바로 리턴. 아니면 linger 초만큼 대기 (default 0)
// linger : 지연 시간
LINGER linger;
linger.l_onoff = 1;
linger.l_linger = 5;
::setsockopt(serverSocket, SOL_SOCKET, SO_LINGER, (char*)&linger, sizeof(linger));

위 옵션대로 설정하면, closesocket 이 불리자마자 종료를 하는 것이 아니라, 5초간 대기를 한 다음에 종료를 할 것이다.

 

// Half-Close
// SD_SEND : send 막는다
// SD_RECEIVE : recv 막는다
// SD_BOTH : 둘다 막는다
// 소켓 리소스 반환
// send -> closesocket
::shutdown(serverSocket, SD_SEND);

전화를 끊을 때도 끊는다는 말을 하고 끊는 것처럼, 소켓을 닫을 때도 지켜야 할 매너가 있다.

shutdown 에서 위 세 옵션을 사용하면, 소켓이 닫혔을 때 어떤 동작을 막을지를 지정할 수 있다.

 

그리고 옵션을 설정할 뿐만 아니라 조회할 수도 있다! 예를 들어, 송수신 버퍼의 크기를 알고 싶으면 다음과 같이 코드를 작성하면 된다 :

// SO_SNDBUF = 송신 버퍼 크기
// SO_RCVBUF = 수신 버퍼 크기

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

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

결과값은 실제로 아래처럼 나오게 된다 😮

 

추가로, 재연결 등의 상황에서, 기존 주소를 재활용하는 팁을 기억하자.

// SO_REUSEADDR
// IP 주소 및 port 재사용
// 재연결 등의 상황에서, binding 이 실패하면..? 기존 주소 재활용
{
    ::setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, (char*)&enable, sizeof(enable));
}

 

마지막으로... 이건 사실 개발 단계에서 쓰일 내용인데, 네이글 알고리즘을 사용하지 않게 설정해 줄 수도 있다.

// IPPROTO_TCP
// TCP_NODELAY = Nable 네이글 알고리즘 작동 여부
// 데이터가 충분히 크면 보내고, 그렇지 않으면 데이터가 충분히 쌓일 때까지 대기!
// 장점 : 작은 패킷이 불필요하게 많이 생성되는 일을 방지
// 단점 : 반응 시간 손해
{
    bool enable = true;
    ::setsockopt(serverSocket, IPPROTO_TCP, TCP_NODELAY, (char*)&enable, sizeof(enable));
}

네이글 알고리즘은 위의 설명처럼 간단히 말해, 데이터가 너무 작으면 합쳐서 보내는 알고리즘이다. 

하지만 우리가 실제로 게임 서버를 굴릴 때는, 우리가 직접 관리를 하는 것이 안전하고 효율적이므로, 개발 단계에서 네이글을 OFF 시켜 주는 것이 좋다! 😁

Comments