게임개발/ue server

[UE] 언리얼 엔진의 커넥션 구성

차차냥 2024. 6. 16. 21:49

언리얼에서의 커넥션 구성을 살펴봅니다.

 

1. 네트워크 통신을 담당하는 언리얼 주요 클래스

언리얼 엔진 통신의 구성은 하이레벨과 로우 레벨로 구성할 수 있습니다.

하이 레벨 : 게임 구성하는 단위인 액터, 컴포넌트, 월드와 같이 컨텐츠를 구성하는 오브젝트의 상태와 속성과 관련된 상위 개념을 의미한다.
로우 레벨 : 상태와 속성을 네트워크를 통해 전달(통신)하기 위해 만들어진 데이터 스트림을 말한다. 

 

 

PlayerController 클래스 : 네트워크 통신에 접근 가능한 게임 내의 대표 액터 네트워크 커넥션을 관리합니다.

UNetConnection 클래스 : 상위에서 네트워크 통신을 담당하는 클래스. 패킷 데이터의 인코딩 디코딩, 네트워크 통신량 조절(대역폭 관리), 채널 관리를 합니다.

즉, 하이 레벨의 상태값이나 속성값을 로우 레벨로 원활하게 변경하여 전달할 수 있도록 준비하는 역할을 합니다.

인코딩과 디코딩은 데이터를 효율적으로 저장, 전송, 처리하도록 하는 중요한 역할을 수행한다.
인코딩은 데이터 압축, 다른 형식으로 변환하여 저장공간을 절약하고 전송 시간을 줄이는데 도움을 준다.
디코딩은 인코딩(압축 및 변형)된 데이터를 원래의 형태로 되돌려 사용자가 이해할 수 있게 만드는 역할을 한다.

 

UNetDriver 클래스 : 로우 레벨에서의 소켓 관리와 패킷 처리, 네트워크 통신 설정

 

 

2. 서버의 네트워크 초기화 과정

서버의 모드가 정해지는 부분은 현재 월드에 NetDriver 의 존재 유무를 기준으로 판단합니다.

서버는 월드의 Listen 함수를 호출해 NetDriver 를 생성하는데요, 이 NetDriver 가 서버 모드가 클라이언트-서버 인지, 스탠드얼론인지를 결정하는 것입니다.

 

위에서 얘기한 NetMode 의 존재 유무 파악과 서버 시작 과에 대해 코드를 보면서 살펴보겠습니다.

먼저, NetMode 가 어떻게 파악되는지 살펴볼 수 있는 함수로는 UWorld::InternalGetNetMode() 함수가 있습니다.

ENetMode UWorld::InternalGetNetMode() const
{
    if (NetDriver != nullptr)
        {
            const bool bIsClientOnly = IsRunningClientOnly();
            return bIsClientOnly ? NM_Client : NetDriver->GetNetMode();
        }
 (후략)
 }

 

함수의 내용물을 보면, NetDriver 가 nullptr 가 아닌지를 먼저 체크합니다.

만약 NetDriver 가 nullptr 라면 스탠드얼론이라고 판단합니다. NetDriver 는 통신을 담당하여 커넥션을 관리하는 녀석이기 때문입니다.

NetDriver 가 nullptr 가 아니라면, 해당 함수 안에서 GetNetDriver() 함수를 통해 얻은 NetDriver 의 GetNetMode() 로 NetMode 의 상태를 가져와 네트워크 모드를 파악합니다.

 

NetMode 의 상태를 가져오는 함수인 GetNetMode() 의 내용물은 아래와 같습니다.

// Normal
return (IsServer() ? (GIsClient ? NM_ListenServer : NM_DedicatedServer) : NM_Client);

 

이 때 NetMode 를 판단하는 IsServer() 함수는 어떤 함수일까요?

 

언리얼 엔진에서는 이 특징을 활용해서 현재 어플리케이션이 서버 모드인지 클라이언트 모드인지를 확인할 수 있도록 하는 함수인 IsServer() 라는 함수가 존재합니다..

 

NetDriver 는 다수의 커넥션을 관리하며, 서버와 클라이언트에 따라 다르게 동작합니다.

클라이언트에서의 NetDriver 는 항상 하나의 ServerConnection 만을 가지고, 서버에서의 NetDriver 는 다수의 ClientConnection 을 가집니다.

클라이언트의 NetDriver 는 초기화될 때 항상 ServerConnection을 가지고 있습니다. 만약 ServerConnection 이 Null 이면(없으면) 서버라는 뜻이겠죠.

bool UNetDriver::IsServer() const
{
    // Client connections ALWAYS set the server connection object in InitConnect()
    // @todo ONLINE improve this with a bool
    return ServerConnection == NULL;
}

 

 

서버가 어떻게 시작되는지는 UWorld::Listen() 함수로 확인할 수 있습니다.

bool UWorld::Listen( FURL& InURL )

 

Listen 함수에서 하는 역할은 NetDriver 를 만들어주는 것입니다.

// Create net driver.
    if (GEngine->CreateNamedNetDriver(this, NAME_GameNetDriver, NAME_GameNetDriver))
    {
       NetDriver = GEngine->FindNamedNetDriver(this, NAME_GameNetDriver);

#if UE_WITH_IRIS
       if (IrisSystemHolder.IsHolding())
       {
          NetDriver->RestoreIrisSystem(IrisSystemHolder.ReplicationSystem);
          
          IrisSystemHolder.Clear();
       }
#endif // UE_WITH_IRIS

       NetDriver->SetWorld(this);
       FLevelCollection* const SourceCollection = FindCollectionByType(ELevelCollectionType::DynamicSourceLevels);
       if (SourceCollection)
       {
          SourceCollection->SetNetDriver(NetDriver);
       }
       FLevelCollection* const StaticCollection = FindCollectionByType(ELevelCollectionType::StaticLevels);
       if (StaticCollection)
       {
          StaticCollection->SetNetDriver(NetDriver);
       }
    }

 

 Listen 함수에서 NetDriver 가 생성되기 전에는 InternalGetNetMode() 함수에서 NetDriver 가 nullptr 이기 때문에 NetMode 를 스탠드얼론 상태로 판단합니다. 하지만 Listen 함수가 호출되어 NetDriver 가 생성된 이후에는 제대로 된 NetMode 가 다시 판단되어집니다.