46. 데디케이티드 서버 지원

이 문서에서는 클라이언트 엔진의 데디케이티드 서버 기능을 이용해서 게임 서비스를 어떻게 구축하면 될지 설명합니다. 현재 아이펀엔진을 이용해서 Unreal Engine 4와 Unity3d의 데디케이티드 서버 기능을 사용할 수 있습니다.

아이펀 엔진 데디케이티드 서버 구조도

데디케이티드 서버를 띄우는 물리 혹은 가상 머신을 데디케이티드 서버 호스트라고 부릅니다. 아래와 같은 일련의 과정을 거쳐서 아이펀엔진에 접속한 유저를 데디케이티드 서버로 접속하고 게임 플레이를 가능케 합니다.

  1. (서버) 아이펀엔진에서 게임 정보를 보내서 데디케이티드 서버 생성 요청

  2. (서버) 하나의 데디케이티드 서버로 갈 유저들을 묶어서 추가 정보와 함께 전달.

  3. 데디케이티드 서버 호스트를 선택하고 데디케이티드 서버 프로세스 시작.

  4. 데디케이티드 서버 실행

    • 데디케이티드 서버 프로세스 시작
    • 명령행 인자를 클라이언트 플러그인에 전달해서 필요한 초기화 수행
    • 데디케이티드 서버가 준비되면 특정 플러그인 함수를 호출해서 준비 완료 처리
  5. 아이펀엔진 서버 쪽에서 시작 완료 콜백 호출 및 클라이언트에게 접속 정보 전달.

  6. (클라이언트) 플러그인 콜백을 받아서 데디케이티드 서버로 접속 후 게임 진행.

  7. (데디케이티드 서버) 게임이 끝나면 데디케이티드 서버 쪽에서 게임 결과 전달.

46.1. 서버 설정 수정

MANIFEST.json 에 다음 항목을 추가하시면 데디케이티드 서버 기능을 사용할 수 있습니다.

"DedicatedServerManager": {
}

46.2. 데디케이티드 서버 매니저

46.2.1. 데디케이티드 서버 매니저 설치하기

아이펀엔진이 지원하는 것과 동일한 리눅스 환경을 지원하며, MS Windows 환경도 지원하고 있습니다.

46.2.1.1. Linux 환경에 설치하기

Ubuntu linux 환경

$ sudo apt-get install -y funapi1-dedicated-server-host

CentOS 환경

$ sudo yum install -y funapi1-dedicated-server-host

46.2.1.2. MS Windows 환경에 설치하기

현재 Windows 10 (x64) 환경의 64bit Python 2.7.14 에서 테스트했습니다.

  1. 우선 MS Windows 용 python 을 내려받아서 설치합니다. (최신 Python 2.7 패키지를 설치하면 됩니다.)

  2. Python 을 기본 위치인 c:\Python27 에 설치한 경우, 명령행에서 다음과 같은 명령으로 필요한 패키지를 설치합니다.

    C:\> C:\Python2.7\Script\pip.exe install flask gevent netifaces ^
        python-gflags requests redis six
    

    Note

    ^ 문자는 MS-DOS 배치파일의 줄 이어짐 문자입니다. 한 줄로 입력하시면 됩니다.

  3. 데디케이티드 서버 매니저를 다운로드 받아서 원하는 위치에 압축을 풉니다.

46.2.2. 데디케이티드 서버 매니저 설정하기

설정 파일에 다음과 같은 값을 설정합니다.

  • 데디케이티드 서버 엔진 타입 (ue4 혹은 unity 로 지정)
  • 게임 클라이언트 연결을 받을 네트워크 인터페이스 지정
  • 아이펀 엔진 게임 서버의 명령을 받을 네트워크 인터페이스 지정
  • Redis 서버 주소. Redis 서버는 아이펀엔진과 공유해야 합니다.
  • 데디케이티드 서버 실행 파일 위치

Linux 의 경우 /etc/funapi-dedicated-server-host/funapi-dedicated-server-host.flag 파일에 설정 파일이 있습니다. MS Windows 의 경우, 압축 파일을 푼 디렉터리에서 확인하실 수 있습니다. 아래는 UnrealEngine 4를 사용 하는 경우의 예제입니다. 만약 Unity를 쓴다면 --engine_type=unity 로 설정해야 합니다.

Linux 환경 설정 예

# Lines that begins with the '#' are ignored.
# You should update binary_path to your dedicated server executable's location.
--binary_path=/var/lib/your/dedicated-server/binary

# Engine type. Possible values are 'ue4' (UnrealEngine4) and 'unity' (Unity).
--engine_type=ue4

# Also, you should update redis_host, redis_port to match the redis address.
--redis_host=127.0.0.1
--redis_port=6379

# If you want to bind the specific NIC for clients, update the following lines.
# And you may choose another NIC for inter-server communication.
# eg) You may use eno1 for games, and eno2 for inter-server communication.
# Depends on your OS, NIC name can be varying - eg) eth0, eno1, ens1, enp2s0.
--game_interface=eth0
--restful_interface=eth1

데디케이티드 서버 바이너리가 /var/lib/your/dedicated-server/binary 라는 이름의 실행 파일이고, redis 서버가 127.0.0.1:6379 에 떠 있는 경우에 해당하는 설정 파일입니다. 또한 게임 클라이언트는 eth0 로 통신하고, 아이펀엔진 게임 서버와는 eth1 로 통신하는 경우 위와 같이 설정하시면 됩니다.

MS Windows 환경 설정 예

MS Windows 는 game_ip, restful_ip 인자를 사용합니다. (interface 인자를 쓰지 않습니다.) 해당 값을 지정해어야 정상적으로 연결을 받을 수 있습니다. game_ip 는 게임 클라이언트가 데디케이티드 서버에 접속할 때 사용할 주소입니다. 데디케이티드 서버를 띄우는 서버의 public IP를 지정해야 합니다. restful_ip 는 아이펀엔진 서버가 데디케이티드 서버 호스트에 접속할 때 사용하는 주소입니다. 데디케이티드 서버의 내부 통신용 IP가 있다면 해당 값을, 없다면 public IP를 지정해주시기 바랍니다.

# Lines that begins with the '#' are ignored.
# You should update binary_path to your dedicated server executable's location.
--binary_path=D:\ShooterGame\ShooterGame.exe

# Engine type. Possible values are 'ue4' (UnrealEngine4) and 'unity' (Unity).
--engine_type=ue4

# Also, you should update redis_host, redis_port to match the redis address.
--redis_host=127.0.0.1
--redis_port=6379

# For Microsoft Windows, you must use following flags, instead of
# game_interface or restful_interface.
# IP addresses used by game clients and game servers respectively.
--game_ip=10.0.0.7
--restful_ip=10.10.1.7

Tip

만약 특정 인자를 추가로 데디케이티드 서버에 전달하려는 경우, --binary_path 값을 실행 파일 위치 대신 스크립트 파일 (OS에 따라 .sh, 혹은 .bat) 로 지정하시고, 해당 파일에서 인자를 추가하는 방법을 사용할 수 있습니다.

혹은, 개발 중에 UE4 에디터를 이용해서 패키징 과정없이 띄우도록 설정할 수 있습니다. 예를 들면 다음과 같습니다. (MS Windows 에서 .bat 파일 이용)

REM exmple batch file, which utilizes UE4 editor to launch dedicated server

"C:\Program Files\Epic Games\4.14\Engine\Binaries\Win64\UE4Editor.exe" ^
  "C:\Work\apps-ue4-dedi-server-example\ShooterGame\ShooterGame.uproject" ^
  HighRise -skipcompile -server -log ^
  %*

UE4 에디터를 이용해서 데디케이티드 서버를 띄우며, 맵 이름인 HighRise 를 추가로 전달합니다.

게임마다 변해야하는 맵 이름, 게임 모드 등의 값을 전달할 때는 다음 절을 참조해서 DedicatedServerManager::Spawn() 함수의 server_args 인자를 이용하시기 바랍니다.

46.2.3. 데디케이티드 서버 매니저 실행하기

Ubuntu 16.04 / CentOS 7 환경에 실행하기

우선 다음 명령어로 서비스를 활성화합니다.

$ sudo systemctl daemon-reload
$ sudo systemctl enable funapi-dedicated-server-host

그리고 서비스를 실행합니다.

$ sudo systemctl start funapi-dedicated-server-host

Ubuntu 14.04 환경에 실행하기

다음과 같이 서비스를 실행합니다.

$ sudo start funapi-dedicated-server-host

MS Windows 환경에 실행하기

funapi-dedicated-server-host.flag 에 있는 내용을 설정하신 후, funapi_dedicated_server 폴더가 보이는 디렉터리에서 아래 명령을 실행합니다.

여기서는 D:\DedicatedServerHost 디렉터리에 압축을 풀었다고 가정합니다.

D:\DedicatedServerHost> C:\Python2.7\python.exe -m funapi_dedicated_server ^
    --flagfile=funapi-dedicated-server-host.flag

Note

^ 문자는 MS-DOS 배치파일의 줄 이어짐 문자입니다. 한 줄로 입력하셔도 됩니다.

46.3. 게임 시작 하기

게임 서버에서 다음과 같은 함수를 호출합니다.

#include <funapi/service/dedicated_server_manager.h>

void start_callback(const fun::Uuid &match_id,
                    const std::vector<std::string> &accounts,
                    bool success) {
  // 게임 시작 후 처리해야할 게 있다면 여기서 처리할 것.
  // 실패 (success = false) 한 경우도 여기서 처리.
}


// 게임 데이터, 유저목록, 서버 실행 인자 (추가로 필요한 경우만) 를 넘겨서
// 데디케이티드 서버를 실행한다.
DedicatedServerManager::Spawn(
    match_id,
    game_data,
    server_args,
    accounts,
    user_data,
    start_callback);

패러미터는 다음과 같은 의미입니다.

  • fun::Uuid match_id: 데디케이티드 서버 인스턴스마다 붙이게 되는 유일한 아이디입니다. 게임을 플레이한 후 결과를 받을 때도 이 아이디로 받게 됩니다.
  • fun::Json game_data: 데디케이티드 서버에서 받아서 사용할 수 있는 JSON 형식의 데이터입니다. 게임을 시작하기 위해 필요한 정보는 여기에 넣어주세요.
  • std::vector<std::string> server_args: 데디케이티드 서버 실행 파일에 인자로 전달할 값이 있다면 이 인자에 설정하시기 바랍니다. 예를 들어 맵을 선택해야한다면 map=blahblah?opt1=1&opt2=2 와 같이 넘길 수 있습니다.
  • std::vector<std::string> accounts: AccountManager 에 로그인할 때 전달한 유저별 계정 아이디를 여기에 넣어서 전송하면, 해당 유저의 클라이언트에게 데디케이티드 서버 접속 정보를 전달합니다.
  • std::vector<fun::Json> user_data: 유저 별로 특정 데이터를 전달할 때 사용합니다. accounts 와 동일한 수량/순서로 지정해야 합니다.
  • start_callback: 게임 시작 성공/실패 후에 호출하는 콜백 함수입니다.

데디케이티드 서버를 띄울 수 있는 호스트가 있고, 성공적으로 실행한 경우 콜백 함수 호출 후에 개별 클라이언트에게 접속 정보 (주소, 토큰 등) 를 전달합니다. 이후에는 개별 클라이언트가 데디케이티드 서버에 접속해서 게임을 진행하게 됩니다.

46.3.1. 진행 중인 게임에 유저 보내기 (난입)

이미 시작한 게임에 유저를 보내야하는 경우, DedicatedServerManager::SendUsers API를 이용할 수 있습니다.

static void SendUsers(const fun::Uuid &match_id,
                      const fun::Json &match_data,
                      const std::vector<std::string> &accounts,
                      const std::vector<fun::Json> &user_data,
                      const SendCallback &callback);

DedicatedServerManager::Spawn 에서 server_args 를 제외한 나머지 항목을 인자로 전달하면 됩니다.

46.4. 데디케이티드 서버 처리하기 (Unreal Engine 4)

Unreal Engine 4 예제 는 GitHub 에서 확인할 수 있습니다. Unity 예제 는 GitHub에서 확인하실 수 있습니다.

데디케이티드 서버에서 아이펀엔진 서버와 통신하는데 필요한 부분을 모아서 UE4 플러그인의 fun::FunapiDedicatedServer 클래스의 멤버 함수로 제공합니다.

46.4.1. 명령행 인자 처리

아래와 같은 코드를 호출해서 명령행 인자를 읽어들입니다. 아래 코드를 호출해주어야 게임 서버에서 보낸 데이터를 읽거나, 게임 결과를 게임 서버에 보낼 수 있습니다. 해당 함수는 다음과 같습니다. 만약 필요한 패러미터를 모두 얻지 못하면 false 를 반환합니다. 다음과 같이 실행하면 필요한 인자를 자동으로 해석합니다.

bool ParseConsoleCommand(const TCHAR* cmd,
                         const FString &match_id_field,
                         const FString &manager_server_field);

fun::FunapiDedicatedServer::ParseConsoleCommand(FCommandLine::Get());

만약 데디케이티드 서버의 명령행 인자를 기본 값이 아닌 다른 값으로 사용하시려는 경우, 다음과 같이 호출하시면 됩니다.

fun::FunapiDedicatedServer::ParseConsoleCommand(
    FCommandLine::Get(),
    "FunapiMatchID",        // match id 에 해당하는 인자 이름입니다.
    "FunapiManagerServer",  // 데디케이티드 서버를 관리하는 서버 주소입니다.
  );

46.4.2. 필요한 데이터 가져오기

게임 서버 쪽에서 데디케이티드 서버로 보낸 데이터는 다음과 같은 함수를 호출해서 가져오시면 됩니다. 해당 데이터를 가져온 이후에 콜백 함수 안에서 필요한 초기화를 진행하시면 됩니다.

void GetGameInfo(
    const TFunction<void(FHttpResponsePtr response)> &completion_handler);

예를 들어, 다음과 같이 콜백함수를 지정하고 호출합니다.

// 데이터를 받은 후 호출할 콜백 함수.
void OnDataReceived(FHttpResponsePtr response) {
  // response 안에 JSON 형식으로 데이터가 들어있습니다.
  // 게임 서버에서 보낸 데이터는 JSON data의 "data" attribute 밑의
  // 내용에 해당합니다.
}

// 실제로 데이터를 가져옵니다.
fun::FunapiDedicatedServer::GetGameInfo(OnDataReceived);

46.4.3. 데디케이티드 서버가 준비되면 알려주기

데디케이티드 서버 초기화가 끝나면 클라이언트가 접속할 수 있도록 다음 함수를 호출해서 데디케이티드 서버 매니저에게 알립니다.

fun::FunapiDedicatedServer::PostReady();

46.4.4. 접속한 유저 인증처리

이 함수는 GetGameInfo() 를 호출한 이후에만 유효합니다.

UE4의 PreLogin 단계에서 유효한 유저인지 확인하기 위한 함수를 제공합니다. 즉, 클라이언트가 접속한 이후에 인증 처리를 위해 다음 함수를 호출합니다. 유저 아이디와 인증 토큰 필드 이름을 FunapiUID, FunapiToken 으로 사용합니다. 만약 다른 값을 써야하는 경우, 두번째 함수를 이용하시기 바랍니다.

// 유저 인증 처리; 반환 값: 인증 성공 여부
bool AuthUser(const FString& options,  // PreLogin 단계에서 넘어오는 인자
              FString &error_message);  // 오류가 발생한 경우 오류 메시지

// 유저 아이디 / 토큰 필드명을 변경한 경우 이 함수를 씁니다.
bool AuthUser(const FString& options,  // PreLogin 단계에서 넘어오는 인자
              const FString& uid_field,  // uid 필드명
              const FString& token_field,  // 인증 토큰 필드명
              FString &error_message);  // 오류가 발생한 경우 오류 메시지

예를 들어, 다음과 같이 호출하시면 됩니다.

// 기본 필드 이름을 쓰는 경우
if (!fun::FunapiDedicatedServer::AuthUser(Options, ErrorMessage)) {
  // 인증 실패한 경우 처리
}


// 기본 필드 이름을 쓰지 않는 경우
if (!fun::FunapiDedicatedServer::AuthUser(Options,
                                          "FunapiUID",
                                          "FunapiToken",
                                          ErrorMessage)) {
  // 인증 실패한 경우 처리
}

46.4.5. 게임 결과 보고하기

게임이 끝나면 JSON 형식으로 게임 서버에게 보낼 데이터를 만들어서 전송합니다. 아래 함수를 이용합니다.

void PostResult(const FString &json_string, const bool use_exit);

use_exittrue 인 경우, 결과 보고 후 데디케이티드 서버를 종료합니다.

예를 들어, 다음과 같이 호출해서 게임 결과를 보고합니다.

fun::FunapiDedicatedServer::PostResult(
    FString("{ \"foo\":\"bar\"}"), false);

여기서 전달한 게임 결과는 게임 결과 처리하기 절에서 설명하는 서버 코드에서 처리합니다.

46.5. 데디케이티드 서버 처리하기 (Unity3d)

Unity 예제 는 GitHub에서 확인하실 수 있습니다. 데디케이티드 서버에서 아이펀엔진 서버와 통신하는데 필요한 부분을 모아서 Unity 플러그인의 FunapiDedicatedServer 클래스에서 대응하는 함수를 제공합니다.

Tip

유니티 데디케이티드 서버의 경우 UDP를 사용하므로 방화벽에서 UDP를 허용하도록 해야 합니다. 유니티 네트워크에 대한 설명은 All about the Unity networking transport layer 글을 참고해주세요.

46.5.1. 명령행 인자 처리

bool FunapiDedicatedServer.Init();

해당함수를 호출해서 데디케이디드 서버를 시작할 때 명령행으로 전달한 인자를 처리합니다. 형식이 잘못되거나 필요한 인자가 없는 경우 false 를 반환합니다.

46.5.2. 데디케이티드 서버 시작

void FunapiDedicatedServer.Start();

해당 API를 호출하면 데디케이티드 서버 관련 기능이 동작하기 시작합니다.

46.5.3. 매치 혹은 유저 데이터 얻기

// 유저 데이터를 얻습니다.
string GetUserDataJsonString(string uid);

// 매치 데이터를 얻습니다.
string GetMatchDataJsonString();

46.5.4. 접속한 유저 인증 처리

bool AuthUser(string uid, string token);

uid 에 해당하는 유저가 인증 토큰 token 을 전달해온 경우 맞는 값인지 검사합니다.

46.5.5. 유저 진입/이탈 전달하기

void SendJoined(string uid);

void SendLeft(string uid);

위 API를 써서 특정 유저 (uid) 가 진입/이탈했다는 정보를 게임 서버에 전달합니다.

46.5.6. 게임 결과 보고 하기

게임이 끝나면 JSON 형식으로 게임 서버에게 보낼 데이터를 만들어서 전송합니다. 아래 함수를 이용합니다.

void SendResult(string json_string);

게임 서버에 보내는 데이터는 유효한 JSON 데이터여야 합니다.

46.6. 게임 결과 처리하기

앞서 설명한 게임 결과 보고하기 에서 게임 서버에게 보낸 결과는 콜백 함수를 미리 등록해두면 해당 함수에서 처리할 수 있습니다.

#include <funapi/service/dedicated_server_manager.h>


// 게임 결과를 처리하는 콜백 함수입니다.
void result_callback(const fun::Uuid &match_id,
                     const fun::Json &match_data,
                     bool success) {
    // 게임 결과 처리를 수행합니다.
}

// 콜백을 등록합니다.
DedicatedServerManager::RegisterMatchResultCallback(result_callback);

게임 서버의 Start() 함수 등에서 콜백 함수를 미리 등록해놓으면, 게임 종료 후 결과를 받아서 처리할 수 있습니다. match_data 에는 데디케이티드 서버가 전송한 JSON 데이터가 전달됩니다. 해당 값과 매치 아이디 등을 이용해서 게임 결과를 데이터베이스에 저장하는 등의 작업을 수행하시기 바랍니다.