ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [ProudNet] 살펴보기 : 연결 수립 (Simple example)
    기타 2024. 1. 31. 10:01

    이번 글은 프라우드넷에서 제공하는 가이드의 Simple 프로젝트를 사용합니다.

    ProudNet Documentation : Simple 예제 만들기

     

    이번 글에서는 프라우드넷의 Simple 프로젝트를 구현하는 과정에서 발생한 이슈 몇 가지와, 프라우드넷에서 서버 - 클라이언트 간 통신을 하는 기본적인 방법을 살펴보도록 하겠습니다.

    이번 글에서 사용된 Visual Studio는 2022(V143)입니다.

     


     

    1. PIDL 생성 오류

     

    더보기

    Common프로젝트의 PIDL.exe를 이용하여 proxy, stub, common 파일을 생성하는 데 문제가 발생할 수 있습니다.

    이는 프라우드넷의 설치 경로의 문제일 수 있습니다.

     

    프라우드넷이 Program files 폴더에 있을 경우 권한이 부족할 수 있으니 다른 경로로 설치하거나, 권한을 수정해주세요.

     

    2. Client P2P Join

     

    더보기

    Simple 프로젝트의 클라이언트 기능 중 아래 기능이 있습니다.

    • 클라이언트는 서버에 의해 생성된 P2P그룹의 다른 클라이언트에게 메시지를 전달할 수 있습니다.

    해당 기능 중, P2P그룹이 생성되었을 때 작동하는 람다 함수의 코드를 수정해야 합니다.

    다음 코드입니다.

    netClient->OnP2PMemberJoin = [&](...) {
        ...
        if(memberHostID != netClient->GetLocalHostID()){
            memberHostID = memberHostID;
            ...
        }
    };

    위 코드는 생성된 P2P그룹의 호스트 ID를 저장하는 코드입니다.

    위 코드를 아래와 같이 수정해야 합니다.

    netClient->OnP2PMemberJoin = [&](...) {
        ...
        if(memberHostID != netClient->GetLocalHostID()){
            recentP2PGroupHostID = memberHostID;
            ...
        }
    };

     

     

    3. libssl-1_1-x64.dll, libcrypto-1_1-x64.dll

     

    더보기

    해당 이슈는 다른 글에서 다루고 있습니다.

    [ProudNet] libssl-1_1-x64.dll, libcrypto-1_1-x64.dll 오류

     

    4. 예제 살펴보기

     

    Simple예제는 서버 - 클라이언트 간의 간단한 통신을 다루고 있습니다.

    각각 서버와 클라이언트가 수행하는 기능은 아래와 같습니다.

    서버 클라이언트
    1. 접속한 클라이언트들로 P2P그룹을 생성합니다.
    2. 생성된 P2P그룹을 해제합니다.
    3. P2P그룹에 속한 클라이언트에 메시지를 전송합니다.
    1. 현재 P2P그룹의 클라이언트들에게 메시지를 전송합니다.

    이 외에 연결을 수립, 해제하거나 각종 상황 (클라이언트의 접속, 해제, P2P 그룹 생성 감지 등)에 대한 이벤트도 있습니다.

    이러한 기능 또한 프라우드넷의 서버, 클라이언트 객체에서 대응 할 수 있으며, 각각 서버와 클라이언트 객체의 필드로 있는 람다식을 정의하는 것으로 구현할 수 있습니다.

    // Server
    shared_ptr<CNetServer> srv(CNetServer::Create());
    
    srv->OnClientJoin = [](CNetClientInfo* clientInfo) {
        ...
    };
    ...
    
    // Client
    shared_ptr<CNetClient> netClient(CNetClient::Create());
    
    netClient->OnJoinServerComplete = [&](ErrorInfo* info, const ByteArray& replyFromServer) {
        ...
    };

    위 코드는 각각 클라이언트와 서버 객체를 생성하는 코드와, 각각 클라이언트와 서버가 연결되었을 때 실행되는 행동을 람다식으로 정의하는 코드입니다.

    이와 같이 클라이언트와 서버 객체에는 여러 상황에 호출되는 람다식이 있습니다.

    이에 관한 자세한 내용은 다른 글에서 다룰 수 있도록 하겠습니다.

     

    4.1 서버

     

    더보기

    프라우드넷의 서버는 CNetServer객체의 Start함수를 호출하는 것으로 시작할 수 있습니다.

    int main() {
        shared_ptr<CNetServer> srv(CNetServer::Create());
        CStartServerParameter p1;
        ...
        try {
            srv->Start(pl);
        } catch(Exception& e) {
            ...
        }
        ...
    }

    이 때, Start함수의 파라미터 CStartServerParameter는 프로토콜 버전, 사용하는 포트 등을 지정해주는 파라미터 클래스입니다.

    위 문단의 표의 기능 외에, 서버에서 수행할 수 있는 여러 기능은 CNetServer의 람다식 필드를 정의하는 것으로 구현할 수 있습니다.

    Simple예제에서는 클라이언트의 접속을 확인하는 OnClientJoin, 접속을 해제하는 OnClientLeave등을 정의하고 있습니다.

     

    이후 사용자의 입력에 따라 아래의 기능을 수행합니다.

    string userInput;
    
    while(1) {
        cin >> userInput;
        if (userInput == "1") {
            HostID list[100];
            int listCount = srv->GetClientHostIDs(list, 100);
            g_groupHostID = srv->CreateP2PGroup(list, listCount, ByteArray());
        } else if (userInput == "2") {
            g_S2CProxy.SystemChat(g_groupHostID, RmiContext::ReliableSend, _PNT("Hello~~~!"));
        } else if (userInput == "3") {
            srv->DestroyP2PGroup(g_groupHostID);
        } else if (userInput == "q") {
            break;
        }
    }

    입력에 따른 행동은 다음과 같습니다.

    입력 행동
    1 현재 접속한 클라이언트를 P2P그룹으로 묶습니다.
    2 생성된 P2P그룹의 클라이언트들에게 메시지를 전송합니다.
    3 생성된 P2P그룹을 해제합니다.
    q 서버를 종료합니다.

     

     

    4.2 클라이언트

     

    더보기

    프라우드넷의 클라이언트는 통신 뿐 아니라, 사용자의 입력 등을 처리해야 합니다.

    여러 작업을 병렬로 수행하기 위해 통신 스레드를 추가로 사용하며, 통신 또한 메시지 방식을 이용하여 주고받은 통신이 없을 때에도 동작할 수 있어야 합니다.

    int main() {
        shared_ptr<CNetClient> netClient(CNetClient::Create());
        bool keepWorkerThread = true;
        CNetConnectionParam cp;
        ...
        netClient->Connect(cp);
        
        Proud::Thread workerThread([&]() {
            while (keepWorkerThread) {
                Proud::Sleep(10);
                netClient->FrameMove();
            }
        });
        workerThread.Start();
        ...
    }

     

    서버의 프로토콜 버전, 주소, 포트 등의 정보를 지정하는 CNetConnectionParam객체를 이용하여 서버와 연결 한 이후, 통신을 위한 스레드를 추가로 생성합니다.

    생성된 스레드는 10ms간격으로 통신의 결과를 확인하는 FreamMove함수를 호출합니다.

    이 함수는 주고받은 통신이 없더라도 0을 반환하여 프로그램이 중단되지 않도록 합니다.

     

    이후 사용자의 입력에 따라 아래의 기능을 수행합니다.

    string userInput;
    while (keepWorkerThread) {
        cin >> userInput;
    
        if (userInput == "q") {
            cout << "Input echo : q\n";
            keepWorkerThread = false;
        } else if (userInput == "a") {
            if (isConnected) {
                CriticalSectionLock lock(g_critSec, true);
                RmiContext sendHow = RmiContext::ReliableSend;
                sendHow.m_enableLoopback = false; // don't sent to myself.
                g_C2CProxy.P2PChat(recentP2PGroupHostID, sendHow,
                    _PNT("Welcome ProudNet!!"), 1, 1);
            } else {
                cout << "Not yet connected.\n";
            }
        }
    }

    서버와는 다르게, keepWorkerThread를 조건으로 하여 반복문을 지속하는 것을 볼 수 있습니다.

    이는 중간에 통신 스레드가 중단되었을 경우에 대비하기 위함입니다.

     

    사용자의 입력에 따른 행동은 다음과 같습니다.

    입력 행동
    a 생성된 P2P그룹의 클라이언트들에게 메시지를 송신합니다.
    q 클라이언트를 종료합니다.

     

     


     

    Simple 프로젝트에서, 클라이언트와 서버 간의 연결을 수립하는 과정에 대해 살펴보았습니다.

    위 코드는 Simple 프로젝트의 일부분으로, 본문의 코드 조각으로는 정상적인 통신이 성립되지 않습니다.

     

    다음 글에서는 클라이언트와 서버 간의 통신을 성립하게 하는 기능들을 살펴보도록 하겠습니다.

    감사합니다.

    댓글

Designed by Tistory.