성능을 위한 팁

통신량 절약

불필요한 메시지는 주고받지 않는 것이 좋습니다. 가령 게이머에게 보여지는 퀘스트의 경우 퀘스트 문자열을 모두 보내주는 것 보다는 게임 클라이언트에 미리 저장된 리소스로 대신 보여주는 것이 좋습니다. 그러나 멀티 언어 지원의 게임에서는 오히려 필수가 되기도 합니다.

추측 항법 (dead reckoning) 은 주고받는 메시지 갯수를 줄이는데 도움이 됩니다. 데이터 양자화 는 1개의 메시지 크기를 줄이는데 도움이 됩니다.

멀티캐스트 최적화

RMI로 멀티캐스트를 할 때 한 번의 RMI 함수 호출로 여러 호스트에 동시다발적으로 보내는 것이 훨씬 좋습니다. MMO 게임 서버 유지비용으로 큰 비중을 차지하는 것은 서버가 작동하는 곳(예: IDC)에서의 회선 속도입니다. ProudNet은 이를 최적화하기 위한 다음과 같은 장치가 있습니다.

- NPC 시뮬레이션 결과를 멀티캐스트

통상적으로 NPC(Non player character)의 시뮬레이션은 서버에서 합니다. NPC의 시뮬레이션 한 결과를 클라이언트들에게 멀티캐스트 하되, 각 클라이언트가 연계되어 있는 P2P 연결을 활용하여 전송하는 방식입니다.

- PC(Player character) 시뮬레이션 결과를 멀티캐스트

통상적으로, PC의 시뮬레이션은 각 클라이언트에서 합니다. 클라이언트는 PC의 위치, 속도 정보 등을 서버에게 전송하고, 서버는 이를 받아 PC의 주변에 있는 클라이언트들에게 멀티캐스트 합니다.

그러나 ProudNet의 P2P 생성,파괴,증원,감원 처리는 매우 빠릅니다. 이를 활용하면, 각 PC의 위치 멀티캐스트를 서버를 경유하지 않고 각 클라이언트의 PC를 가시 영역에 포함시키는 다른 클라이언트들에게 P2P 통신으로 직접 전송할 수 있습니다.

주요 방법은 다음과 같습니다.

  • 일정 시간마다 각 PC의 가시 영역을 두고있는 클라이언트끼리 P2P 그룹을 맺습니다.

  • 각 클라이언트는 시뮬레이션하는 PC의 위치,속도 등을 서버와 P2P 그룹이 맺어진 클라이언트들에게 전송합니다.

  • 서버가 받은 정보를 근거로 서버는 P2P 그룹 생성,파괴,증원,감원을 지속적으로 합니다.

  • 클라이언트는 받은 정보를 근거로 PC의 위치를 갱신합니다.

게임 서버 운영체제(OS)

동시접속자수(CCU)가 100 이하의 경우는 어떠한 운영체제로 사용하던지 상관없지만, 그 이상으로 넘어간다면 서버 전용 운영체제를 써야합니다. ProudNet이 지원하는 서버 전용 운영체제는 Windows 2003 Server 이후 버전입니다. Linux도 지원하며, CentOS 6, Ubuntu 12 이상에서 테스트 되었습니다.

서버의 UDP 포트 사용 방식

클라이언트가 서버에 접속하면 1 개의 TCP 포트가 배정되어야 하지만 ProudNet은 클라이언트와의 연결을 위해 1개의 UDP 포트를 사용합니다. (단, 클라이언트가 UDP 포트를 쓸 수 없는 환경인 경우에는 TCP 포트만을 사용합니다.)

Server 는 각 클라이언트 연결에 대한 UDP 포트 배정 정책을 다음과 같이 구분하고 있습니다.

  • per-client assign mode: 각 클라이언트마다 서로 다른 UDP 포트를 배정합니다.

  • static assign mode: 모든 클라이언트를 위하여 일정 갯수의 미리 준비된 UDP 포트를 배정합니다.

- Static Assign Mode

서버가 사용할 UDP 포트 번호 목록 외, 다른 UDP 포트가 사용되진 않고, UDP 포트 번호 목록 중에서 한 가지가 사용됩니다. 즉, 2개 이상의 클라이언트가 같은 UDP 포트를 공유하게 됩니다. (이 클라이언트들에게 메시지 흐름에 문제가 생기진 않습니다.)

클라이언트 수가 많으면 소량의 UDP socket이 많은 클라이언트와의 통신을 감당해야 하기 때문에 UDP socket 내부 버퍼량에 한계가 온다면 패킷 로스로 이어질 위험이 있습니다. 그렇다고 해서 UDP socket을 처음부터 너무 많이 준비하면 이 많은 UDP socket을 위한 서버의 처리 부하로 메모리와 CPU 사용량이 증가될 수 있으므로 주의해야 합니다.

ICMP host unreachable packet에 대한 내성이 약하기 때문에 ICMP 방화벽 설정 을 해주어야 합니다.

- Per-client Assign Mode

서버에 접속하는 각 클라이언트는 서로 다른 UDP 포트를 배정받고, 서버가 사용할 UDP 포트번호 리스트 이외에 임의의 포트 번호가 사용됩니다. 일반적으로 Per-client assign mode static assign mode보다 장점이 많고 성능이 좋아 Per-client assign mode를 쓰는 것을 권장하지만 주의 사항이 있습니다. 서버가 사용할 UDP 포트 번호 목록보다 더 많은 클라이언트가 접속할 경우 임의의 UDP 포트가 할당되는데, 이때 할당된 포트가 서버 측 방화벽에서 허락되지 않은 번호이면 UDP 통신이 원활하지 못할 수 있습니다.

어떤 서버 방화벽은 outbound 패킷 감지가 있는 경우에 한해 포트 사용을 허용하는 기능이 있습니다. 이를 활용하면 Per-client assign mode를 쓰면서도 UDP 포트 허용을 꺼두어도 안전하면서도 원활한 통신이 이루어집니다.

서버가 어떤 assign mode를 쓰게 할 것인지 설정하려면 Proud.CNetServer.Start 호출 시 Proud.CStartServerParameter.m_udpAssignMode 를 설정합니다. 그리고 서버가 사용할 UDP 포트 목록은 Proud.CStartServerParameter.m_udpPorts 에 설정할 수 있습니다.

ProudNet 사용 시 assign mode, UDP 포트 목록의 길이, 방화벽 설정의 권장하는 유형은 다음과 같습니다.

권장수준Assign ModeUDP 포트 목록 길이방화벽 설정적용 가능 조건

매우 권장

Per-client

(per-client 무시) -

outbound 패킷 감지 시 일시 허용

클라우드 서버가 아닌 'outbound 패킷 감지 시 일시 허용' 기능을 on/off 할 수 있는 일부 물리 서버

권장

Per-client

-

UDP 포트 목록에 등록된 번호들을 항상 허용

권장

Static

동시접속자 수의 1/10

UDP 포트 목록에 등록된 번호들을 항상 허용 / ICMP host unreachable 차단

위험

Per-client

-

UDP 포트 목록에 등록된 번호들을 항상 허용

위험

Static

상관없음

UDP 포트 허용 범위 상관없음 / ICMP host unreachable을 차단하지 않음

서버 스레드 풀의 스레드 갯수

Proud.CNetServer.Start 서버 실행 시 스레드 풀의 스레드 갯수를 지정할 수 있습니다. 서버의 역할과 기능에 따라 서버 컴퓨터에서 띄울 수 있는 서버 프로세스 갯수나 생성하는 스레드 의 갯수는 다릅니다.

몇 가지 대표적인 가이드를 설명하자면 아래와 같습니다.

우선, 스레드 수를 서버의 CPU 코어 수 만큼이라고 가정합니다.

ProudNet 서버가 사용되는 서버가 순수하게 CPU burst time을 차지하지 않고 device burst time을 차지할 경우 스레드의 갯수를 늘립니다. 게임 서버에서 대표적인 device burst time을 차지하는 경우는 유저 DB를 접근하고 있을 때입니다.

게임 서버의 로직이 여러 스레드에서 동시 접근가능한 구조로 만들어져 있으면 1개의 게임 서버 프로세스만 서버 컴퓨터에서 실행시켜도 무방합니다. 하지만, 게임 서버의 로직이 언제나 1개의 스레드 에서만 실행할 수 있도록 critical section이나 mutex로 보호되고 있는 경우 게임 서버 프로세스를 여러 개를 띄우는 것이 좋습니다.

Speed Hack 감지 기능 및 서버 성능

ProudNet은 내부적으로 Speed Hack 탐지하는 기능이 있습니다. 클라이언트-서버 간 주고받는 ping 메시지의 주기를 체크하는 방식으로, 수 초 간 인터벌로 ping을 클라이언트에서 서버로 보냅니다. 서버에서는 ping 메시지가 도착하는 주기가 인터벌의 30% 이상 지나치게 오차 범위에 차이가 나면서, ping이 20회 정도 이상 도착하게 되면 Speed Hack을 쓰는 것으로 간주합니다.

이렇게 Speed Hack이 탐지되면 해당 클라이언트에 대한 서버 이벤트 Proud.INetServerEvent.OnClientHackSuspected 가 콜백되어 불량 사용자 명단을 수집하거나 추방(Proud.CNetServer.CloseConnection)할 수 있습니다.

다음과 같은 상황에서는 탐지가 어려울 수 있습니다.

  • 20% 미만의 속도로 컴퓨터 속도 해킹을 하는 경우에는 탐지되지 않습니다.

  • 통신 상태가 좋지않은 클라이언트는 Speed Hack을 쓰는 것으로 오인될 가능성이 있습니다.

한편, 게이머가 의도적으로 스피드 핵을 아주 잠깐 쓸 수도 있습니다. 이를 감지하기 위해 서버에서는 클라이언트가 서버에 처음 접속한 이후, 주고 받는 '클라이언트 내부 시간 값'과 '서버에서의 시간 값'을 비교합니다.

만약 클라이언트에서 처음 접속한 시간 이래 서버에게 지속적으로 보내는 '클라이언트 내부 시간 값'과 '서버에서 측정된 시간 값'의 차이가 지나치게 크면, 스피드 핵을 쓴다고 간주합니다. 단, 이 체크 방식은 클라이언트가 서버에 접속한 후 수십 초가 지난 후부터 시행합니다.

C++ 함수C# 함수설명

Proud.INetServerEvent.OnClientHackSuspected

Nettention.Proud.NetServer.ClientHackSuspectedHandler

Speed Hack이 감지된 클라이언트의 콜백 서버 이벤트

Proud.CNetServer.CloseConnection

Nettention.Proud.NetServer.CloseConnection

사용자 추방

Proud.CNetServer.SetSpeedHackDetectorReckRatioPercent

Nettention.Proud.NativeNetServer.SetSpeedHackDetectorReckRatioPercent

Speed Hack 탐지 신속성과 정확성 조절

Proud.CNetServer.EnableSpeedHackDetector

Nettention.Proud.NativeNetServer.EnableSpeedHackDetector

Speed Hack 감지기 on/off

스피드 핵 탐지 기능은 클라이언트가 서버로 초당 1~2회의 UDP 패킷을 보내기 때문에 일정 량의 트래픽을 유발하며 서버에 연결된 클라이언트의 수에 비례하여 트래픽이 증가합니다. 그러므로 필요한 경우가 아니라면 사용을 자제하는 것이 효율적입니다.

Xeon E312XX(샌디브릿지) 메모리 4G 의 서버PC에 10,000개의 클라이언트를 Connection 만 하였을 경우 스피드 핵 탐지기를 사용하지 않으면 0 ~ 3% 의 CPU 사용량이, 사용하였을 땐 25 ~ 35% 의 CPU 사용량이 측정 되었습니다.

수신 처리 루틴의 최적화

수신 처리 루틴 중 오랜 시간을 차지하면 서버 성능 저하에 많은 영향을 끼칩니다. 우선, 수신측 (stub) 호출 시점 접근를 통해 성능이 떨어지는 수신 처리 루틴을 찾아 해결해 나가는 것이 중요합니다.

그 다음, 느린 수신 처리 루틴이 차지하는 것이 device burst time 인지 CPU burst time 인지 찾아야 합니다.

  • device burst time 이 긴 경우 critical section lock 을 최소화로 설계하고, device burst 원인(데이터베이스 등)의 성능을 올려야 합니다.

  • CPU burst time 이 긴 경우, 계산 루틴을 최적화 또는 병렬 처리를 하거나 다른 서버로의 분산 처리를 해야 합니다. 예) NPC의 AI

연결 유지 기능 사용하기

모바일 환경에서는 셀룰러, 와이파이 전환이 빈번합니다. ProudNet은 이러한 환경에서도 엔진 단에서 자동으로 네트워크 핸드오버를 처리합니다.

사용자는 서버와 연결되기 전, 다음과 같은 코드만 추가하면 됩니다.

Proud::CNetConnectionParam cp;
cp.m_enableAutoConnectionRecovery = true;
 
client.Connect(cp);

서버에서 핸드 오버 과정 중의 이벤트 함수는 INetServerEvent에서 아래의 함수를 오버라이드 하면 됩니다.

virtual void OnClientOffline(CRemoteOfflineEventArgs &args) {}
virtual void OnClientOnline(CRemoteOnlineEventArgs &args) {}

마찬가지로 클라이언트에서의 이벤트 함수는 INetClientEvent 에서 아래의 함수를 오버라이드 합니다.

virtual void OnServerOffline(CRemoteOfflineEventArgs &args) {}
virtual void OnServerOnline(CRemoteOnlineEventArgs &args) {}
virtual void OnP2PMemberOffline(CRemoteOfflineEventArgs &args) {}
virtual void OnP2PMemberOnline(CRemoteOnlineEventArgs &args) {}

클라이언트의 네트워크가 끊어지고 Offline 이 콜백 된 뒤, 일정 시간 동안 재연결되지 않으면 INetClientEvent.OnLeaveServer, INetServerEvent.OnClientLeave 가 콜백 됩니다.

1.7.36365이상 버전에서 서버는 각 클라이언트마다 연결 유지 시간을 설정할 수 있습니다.

NetServer.SetAutoConnectionRecoveryTimeoutTimeMs(HostID, int)
NetServer.SetDefaultAutoConnectionRecoveryTimeoutTimeMs(int)

네트워커 스레드 (Networker Thread)

ProudNet의 클라이언트는 내부적으로 네트워크 I/O를 처리하는 worker thread를 가지고 있습니다. 이는 사용자가 Proud.CNetClient.FrameMove()를 일정 시간마다 호출하지 않아도 네트워크 연결과 ping latency 측정을 원활하게 하는 역할을 합니다.

그러나 iPhone 3GS 등 일부 저사양 하드웨어에서는 여러 스레드가 작동하는 것이 성능에 악영향을 주기 때문에 두 개 이상의 스레드가 동시에 작동하지 않는 것이 좋습니다. 대신, networker thread의 작업을 사용자 앱의 main thread에서 실행합니다.

  • Proud.CNetClient.UseNetworkerThread_EveryInstance(false)를 호출합니다. 그러면 더 이상 networker thread는 작동하지 않습니다.

  • 자주 Proud.CNetClient.NetworkerThreadHeartbeat_EveryInstance()를 호출합니다. 만약, 게임 데이터 로딩 등 불가피하게 장시간 이 함수를 호출할 수 없을 땐 사전에 UseNetworkerThread_EveryInstance(true)를 호출하고, 일이 끝난 후에 UseNetworkerThread_EveryInstance(false)를 호출하세요.

서버가 다수의 LAN카드(NIC)를 장착한 경우

게임 서버가 여러개의 LAN카드(NIC)를 장착한 경우가 있습니다. 보안을 위해 이들 중 일부만 게이머와의 통신을 허용하면서 말이죠. 이러한 경우에는 서버 시작시 어떠한 NIC를 쓸 것인지를 지정해야 합니다. 만약 지정되어 있지 않는 경우 클라이언트가 서버로 연결은 할 수 있지만 UDP 통신이나 P2P 통신이 정상 작동하지 않을 수 있습니다.

Proud.CNetServer.Start 의 입력 파라미터 Proud.CStartServerParameter.m_localNicAddr 를 통해 사용할 NIC를 지정할 수 있습니다. 사용할 NIC들의 로컬 주소 리스트를 얻으려면 Proud.CNetUtil.GetLocalIPAddresses 를 사용하는 것이 도움이 됩니다.

공유기(NAT router)나 L4 switch 뒤에 있는 서버 설정

인터넷 공유기 뒤에 있는 개발 기기에서 서버를 띄울 때 서버를 인터넷 공유기(NAT router) 뒤에 두고 싶거나, 서버가 L4 스위치 뒤에 있는 서버 기기에서 작동해야 하는 경우 또는 서버가 포트 포워딩을 필요로 하는 클라우드 서버에서 작동하는 경우일 때, 공유기의 포트 포워딩을 설정할 것입니다.

이 때 CNetServer에 추가 설정이 필요한데, 이러한 설정이 없으면 Unreliable 메시징 은 항상 TCP를 사용하게 되는 문제가 생깁니다. P2P 메시징은 항상 TCP relay를 이용하는 문제도 생길 것입니다.

Server1과 Server2가 공유기 뒤에 있다는 가정 하에 아래의 그림을 살펴봅니다. 각 서버는 100.10.0.1, 100.10.0.2라는 사설 주소를 갖고있고, 공유기는 각 서버에 대해 11.22.33.44:5555와 11.22.33.44:5556으로 포트 포워딩을 해주고 있습니다.

CNetServer.Start 를 호출할 때 들어가는 파라미터 Proud.CStartServerParameter.m_serverAddrAtClient 를 넣으십시오. 위의 그림의 경우 NAT router의 공개 주소 11.22.33.44를 넣어야 하며 NAT router에 UDP 포트 포워딩도 할 것입니다.

위의 그림처럼 포트 포워딩을 6,000 ~ 6,500 범위로 했다면 CNetServer 가 UDP port 6,000 ~ 6,500을 쓰도록 설정해야 하는데, 이를 위해 UDP port assign mode를 static으로 설정합니다. 그리고 CNetServer가 사용할 port를 6,000 ~ 6,500이 되게 합니다.

예시

Proud::CNetServer* s;

...

Proud::CStartServerParameter p;

...

p.m_serverAddrAtClient = "11.22.33.44";
p.m_udpAssignMode = Proud::ServerUdpAssignMode::ServerUdpAssignMode_Static;
for(int i=6000;i<=6500;i++)
    p.m_udpPorts.Add(i);
 
s->Start(p);

작동 확인을 위해 두 클라이언트가 P2P 그룹을 맺은 후 메시지를 주고 받을 때 P2P로 오가는지 확인합니다. ProudNet 특성 상, P2P로 오가는 메시지는 몇 초 지나야 direct P2P로 변경됩니다.

P2P로 메시지를 받을 때, 파라미터 RmiContext m_relayed 값이 false로 바뀌는지 확인하십시오. 혹은 OnChangeP2PRelayState가 호출되며 파라미터에 OK가 들어있는지 보시기 바랍니다.

원하는 결과가 나오지 않는다면 CNetServer.EnableLog 를 이용하여 로그 내용을 통해 문제의 원인을 찾을 수 있습니다.

공유기나 스위치 뒤에 있는 것과는 달리, 로드 밸런서 뒤에 서버가 있는 경우 연결 유지 기능이 동작하지 않을 수 있습니다.

Last updated