활용 방법

호스트 간 레이턴시(ping time)얻기

ProudNet에서 호스트간 레이턴시(ping time or lag)을 얻는 방법으로, 얻어지는 ping time은 roundtrip latency, 즉 이쪽 호스트에서 저쪽 호스트로 메시지를 보낸 후 그것을 저쪽 호스트에서 즉시 응답할 때 걸리는 시간입니다.

- 서버에서 1개 클라이언트의 레이턴시를 얻는 함수

C++ 함수C# 함수설명

Proud.CNetServer.GetLastPingSec

Nettention.Proud.NetServer.GetLastReliablePingSec

해당 클라이언트의 마지막 대기 시간을 초 단위로 가져옵니다.

Proud.CNetServer.GetLastUnreliablePingMs

Nettention.Proud.NetServer.GetLastUnreliablePingMs

해당 클라이언트의 마지막 대기 시간을 Ms 단위로 가져옵니다.

Proud.CNetServer.GetRecentPingSec

Nettention.Proud.NetServer.GetRecentReliablePingSec

해당 클라이언트의 마지막 대기 시간을 초 단위로 가져옵니다.

Proud.CNetServer.GetRecentUnreliablePingMs

Nettention.Proud.NetServer.GetRecentUnreliablePingMs

해당 클라이언트의 마지막 대기 시간을 Ms 단위로 가져옵니다.

- 클라이언트에서 서버 또는 P2P 연결된 다른 클라이언트의 레이턴시를 얻는 함수

C++ 함수C# 함수

Proud.CNetClient.GetLastUnreliablePingSec

Nettention.Proud.NetClient.GetLastUnreliablePingSec

Proud.CNetClient.GetLastUnreliablePingMs

Nettention.Proud.NetClient.GetLastUnreliablePingMs

Proud.CNetClient.GetLastReliablePingSec

Nettention.Proud.NetClient.GetLastReliablePingSec

Proud.CNetClient.GetLastReliablePingMs

Nettention.Proud.NetClient.GetLastReliablePinMs

Proud.CNetClient.GetRecentUnreliablePingSec

-

Proud.CNetClient.GetRecentUnreliablePingMs

Nettention.Proud.NetClient.GetRecentUnreliablePingMs

Proud.CNetClient.GetRecentReliablePingSec

Nettention.Proud.NetClient.GetRecentReliablePingSec

Proud.CNetClient.GetRecentReliablePingMs

Nettention.Proud.NetClient.GetRecentReliablePingMs

1.7.40679-master 버전부터 클라이언트에서 서버와의 레이턴시를 구할 수 있는 새로운 함수가 추가되었습니다. 이전의 GetLast~GetRecent~ 류의 함수들은 네트워크 상태가 원활하지 않을 때에 핑퐁이 뒤로 밀리는 문제가 있었습니다.

1.7.40679 이상의 버전을 사용하실 수 있으시다면 새로 추가된 서버와의 레이턴시를 구하는 함수를 이용하시는 것을 권해드립니다.

Reliable 메세지 전송 방식의 경우 양방향 통신 방식으로 송수신 시간이 Unreliable 보다 느리지만, 송수신 순서와 도착의 확실성이 보장됩니다.

Unreliable 메세지 전송 방식의 경우 단방향 통신 방식으로 송수신 시간이 Reliable 보다 빠르지만, 송수신 순서가 꼬일 수 있으며, 도착의 확실성이 보장되지 않습니다.

이종 언어로 된 프로그램 간 통신하기

두 프로그램이 ProudNet으로 통신하되 서로 다른 프로그래밍 언어로 만들고 싶을 때가 있습니다. 이러한 경우 PIDL 컴파일러는 두 개 이상의 언어로 된 proxy와 stub을 생성한 후 각 프로그램은 필요한 것을 가져다 쓰면 됩니다.

int, double, string 등 기본 타입에 대해서는 ProudNet의 C++ 외의 언어를 위한 래핑 모듈에서 이미 제공하고 있습니다. 하지만 언어가 서로 다르면 이러한 기본 타입의 이름이 기본적으로 달라지기 마련입니다. 예를 들어 C#은 문자열 클래스가 System.String 인데, C++에서는 std::string , std::wstring, ATL::CString , Proud::String입니다.

이를 해결하기 위해 PIDL 컴파일러는 사용자가 원할 경우 생성되는 proxy,stub에서 변수 타입을 특정 언어에 한해서 변경하는 기능을 제공하고 있습니다.

아래는 사용 예입니다.

// C# 언어의 proxy,stub 생성시 TypeA를 TypeB로 개명합니다.
rename cs(TypeA, TypeB);
// C++ 언어의 proxy,stub 생성시 TypeC를 TypeD로 개명합니다.
rename cpp(TypeC, TypeD);
 
global XXX 2000
{
    Foo([in]TypeA a);  // C# 언어로 proxy,stub 생성시 Foo(TypeB a) 가 됩니다,
    Goo([in]TypeC c);  // C++ 언어로 proxy,stub 생성시 Goo(TypeD c) 가 됩니다.
}

각 호스트에 대한 사용자 정의 데이터 (Host Tag)

RMI나 이벤트 콜백을 받을 때 식별하기 위해 Proud.HostID 를 같이 받습니다. 게임 서버 개발 시 Proud.HostID 를 받으면 이 값을 근거로 해당 Host에 대한 객체를 검색한 후 그 객체를 다루는데 ProudNet 샘플 프로그램 또한 이러한 방식으로 만들어져 있습니다.

Host Tag 란, 로컬 호스트 및 타 호스트에 대해 Host ID말고도 사용자가 정의할 수 있는 식별용 데이터입니다.

이를 활용하면 프로그램의 성능을 더 올릴 수 있습니다.

OnClientJoin(CNetClientInfo* clientInfo)
{
    // OnClientJoin 함수 실행 중간에 호스트가 나간 경우 SetHostTag이 실패할 수 있으니
    // 아래와 같이 체크하는 것이 좋습니다.
    CNetClientInfo outInfo;
    if(m_client->GetClientInfo(clientInfo->m_HostID,outInfo))
    {
        Host* r = new Host;
        SetHostClientTag(clientInfo->m_HostID, r);
    }
}
 
DEFRMI_XXX_YYY(MYCLASS)
{
    void* tag = RmiContext.m_hostTag;
    // SetHostTag이 실패했다면, RmiContext.m_hostTag이 NULL일 수 있으므로 체크가 필요합니다.
    if(tag != NULL)
    {
        Host* obj = (Host*)tag;     // 찾는 비용 없음
        obj->Something();
    }
}

Host Tag를 사용하지 않았을 때 예시

DEFRMI_XXX_YYY(MYCLASS)
{
    Host* obj = Lookup(HostID);  // 찾는 비용 발생
    if(obj != NULL)
        obj->Something();
}

Host Tag는 다음과 같은 메서드로 지정합니다.

C++ 함수C# 함수

Proud.CLanClient.SetHostTag

-

Proud.CLanServer.SetHostTag

-

Proud.CNetClient.SetHostTag

Nettention.Proud.NetClient.SetHostTag

Proud.CNetServer.SetHostTag

Nettention.Proud.NetServer.SetHostTag

CLanClient, CLanServer의 경우 1.7 버전부터 지원되지 않습니다.

CLanClient, CLanServer의 역할을 NetClient, NetServer가 대신 합니다.

Host TagProud.RmiContext.m_hostTagProud.CNetPeerInfo.m_hostTag을 통해 받아지며 네트워크로 동기화가 일어나지 않아 상대방에게 전파되지 않습니다.

Thread Pool 설정하기

ProudNet의 네트워크 클라이언트 모듈 CNetClient와 서버 모듈 CNetServer는 두 종류의 스레드 풀(Thread Pool) 사용합니다.

  • Networker thread pool: ProudNet 내부 처리 전용으로 사용하며 소켓 I/O 처리 등을 담당

  • User worker thread pool: 사용자가 정의한 루틴을 실행하는 용도로 사용. RMI나 콜백 이벤트 등을 처리

최상의 성능을 내기 위해 이들 스레드 풀을 각 모듈이 소유하는 대신 특정 스레드 풀이 공유되도록 하기 위해서는 스레드 풀을 명시적으로 만들고 네트워크 모듈에 셋팅합니다.

현재 클라이언트 단에 이 기능을 사용하시려면 ProudNetServer.h를 include 해야하며, 1.7.36365 이후부터 ProudNetServer.h 를 include 하지 않아도 사용 가능합니다.

사용자가 생성한 thread pool 객체는 네트워크 모듈들이 모두 파괴되기 전에는 파괴된다면 throw exception이 발생합니다.

아래는 전체적인 흐름에 관련된 pseudo code입니다.

// 스레드풀 시작/종료에 대한 콜백을 담당하는 객체입니다.  
Proud::CThreadPoolEventFunctional e;  
  
// 즉, 스레드풀 시작/종료에 대한 루틴을 넣습니다. (선택사항)  
e.OnThreadBeginFunction = [](){...};  
e.OnThreadEndFunction = [](){...};  
  
// CPU 갯수만큼의 스레드를 가진 thread pool 객체를 만듭니다.  
Proud::CThreadPool* p = Proud::CThreadPool::Create(&e, GetNoofProcessors());  
// 2개의 스레드를 가지고 싶으면
Proud::CThreadPool* p = Proud::CThreadPool::Create(&e, 2);
 
// ----------파라미터로 이 thread pool을 등록한 객체를 전달합니다.----------
// 서버단의 경우
Proud::CStartServerParameter param;
param.m_externalUserWorkerThreadPool = p;
 
// 클라이언트단의 경우
Proud::CNetConnectionParam param;
// 프라우드넷의 스레드 모델 종류새 창에서 링크 열기참조
param.m_userWorkerThreadModel = Proud::ThreadModel::ThreadModel_UseExternalThreadPool;
param.m_externalUserWorkerThreadPool = p;

스레드 풀 설정 방법은 기본적으로 위와 같으며, 원하시는 Case에 따라 설정 값을 다르게 넣으세요. 서버 뿐만 아니라 클라이언트에서도 여러가지 스레드 모델을 설정하실 수 있습니다.

- 여러 네트워크 모듈이 같은 Thread Pool 공유

Proud::CStartServerParameter param1;
Proud::CStartServerParameter param2; 
Proud::CStartServerParameter param3; 
 
//Proud::CThreadPool::Create()로 thread pool 객체를 명시적으로 생성
Proud::CThreadPool* p = Proud::CThreadPool::Create(...);  
  
//user worker thread pool을 공유해줍니다
//Networker thread pool은 디폴트 설정이 그대로 유지되어, 공유되지 않습니다.
param1.m_externalUserWorkerThreadPool = p;
param2.m_externalUserWorkerThreadPool = p;  
param3.m_externalUserWorkerThreadPool = p;

- Networker, User worker thread pool의 개수를 각각 다르게 사용

Proud::CStartServerParameter param;
 
Proud::CThreadPool* p1 = Proud::CThreadPool::Create(..., 1); 
Proud::CThreadPool* p2 = Proud::CThreadPool::Create(..., 4);  
  
param.m_externalNetWorkerThreadPool = p1;  
param.m_externalUserWorkerThreadPool = p2;

- Networker와 User worker의 thread pool 통합

Proud::CStartServerParameter param;  
Proud::CThreadPool* p = Proud::CThreadPool::Create(..., 8); 
param.m_externalNetWorkerThreadPool = p;  
param.m_externalUserWorkerThreadPool = p;

- 순수 싱글 스레드 모델

순수하게 싱글 스레드로 작동하는 서버를 CPU 갯수만큼 띄우기 위한 방법으로, 스레드가 전혀 없는 스레드 풀 객체를 만들고 수동으로 스레드 풀을 숨쉬게 해주는 함수를 지속 호출해줘야 합니다.

Networker thread pool을 지정 시 Process나 FrameMove 등을 너무 느리게 불러주면 안됩니다. 콜백되는 메소드들은 최대한 빨리 필요한 처리를 완료해야 합니다.

그렇지 않은 경우, 내부에서 ping 측정이 이상하게 나오거나 서버와 클라이언트 간의 연결이 끊어지는 등이 발생할 수 있습니다.

Proud::CStartServerParameter param;
Proud::CThreadPool* p = Proud::CThreadPool::Create(..., 0);  
 
param.m_externalNetWorkerThreadPool = p;  
param.m_externalUserWorkerThreadPool = p;  
....
 
void main()  
{  
    while(true)  
    {  
        // 최대 10밀리초까지 기다리면서, thread pool에 쌓인 이벤트를 처리한다.  
        p->Process(10);  
    }  
}

Thread Model 설정하기 - 클라이언트

위에서 user worker와 networker를 분리하여 실수가 일어나지 않도록 막았지만, 여전히 FrameMove는 명시적으로 호출해야 한다는 부분이 남아 있습니다. 이는 쌓인 RMI를 원하는 시점에 실행할 수 있다는 장점을 제공합니다.

클라이언트단의 코드가 복잡하여 Process나 FrameMove를 호출하면 로직이 꼬이거나 실수로 해당 코드가 누락되는 상황등을 신경쓰지 않아도 되도록 설정하는 방법이 Thread Model 설정입니다. ProudNet은 네트워킹에 대해 다양한 조작 방법도 제공하지만, 그것을 신경쓰지 않도록 설정할 수도 있습니다.

- ProudNet 스레드 모델 종류

  • ThreadModel_SingleThreaded: RMI 및 메세지 콜백이 사용자가 FrameMove를 부를 때 완료됩니다.(기본값)

  • ThreadModel_MultiThreaded: 사용자가 FrameMove를 부르지 않아도 바로 콜백이 완료됩니다.

  • ThreadModel_UseExternalThreadPool: thread pool 지정 시 반드시 이것으로 설정해줘야 합니다.

CNetConnectionParam m_netWorkerThreadModel m_userWorkerThreadModel에서 위의 값을 셋팅할 수 있습니다. 기본값은 ThreadModel_SingleThreaded입니다.

Proud::CNetConnectionParam param;
 
//user worker, network 둘 다 사용자 지정 스레드 풀을 사용하고 싶을 경우
param.m_userWorkerThreadModel = Proud::ThreadModel::ThreadModel_UseExternalThreadPool;  
param.m_netWorkerThreadModel = Proud::ThreadModel::ThreadModel_UseExternalThreadPool;  
 
//단순히 FrameMove를 부르고 싶지 않을 경우
param.m_userWorkerThreadModel = Proud::ThreadModel::ThreadModel_MultiThreaded;

사용자 루틴을 비동기로 실행하기

Proud.IRmiHost.RunAsync()로 여러분의 함수나 람다식 A를 비동기로 실행할 수 있습니다. Proud.IRmiHost.RunAsync()는 즉시 리턴하고, A는 스레드 풀에 있는 스레드 중 하나에서 실행됩니다.

만약 A가 지정된 HostID의 호스트 H를 위해서 실행하게 할 경우, H의 callback 함수와 A는 동시에 실행되지 않음을 보장합니다.

Proud::CNetServer* s = ...;
Proud::HostID r1 = ...;
string a = ...;
 
s->RunAsync(r1, [a] { WriteSomething(a); });

송신량 과다 시 경고 기능

ProudNet의 서버와 네트워크은 회선 속도에 비해 송신량이 지나치게 과다한 경우를 감지하여 경고하는 기능이 있습니다. ErrorType_SendQueueIsHeavy를 담은 Proud.INetCoreEvent.OnWarning이 콜백됩니다.

송신량 과다 시, 불필요한 메시징을 개선하거나 송신량 자동 조절 기능 (Throttling)을 권합니다.

Last updated