콤포넌트 구조와 설정 방법

이번 챕터에서는 아이펀 엔진 을 구성하는 콤포넌트에 대해서 알아보고 컴포넌트 단위로 이루어지는 기능들의 설정 방법에 대해서 설명하겠습니댜.

콤포넌트에 대해서

아이펀 엔진 여러 기능을 담당하는 콤포넌트들로 구성되어 있으며, 이 콤포넌트들을 통해서 여러분이 서버를 구현하는데 필요한 기능들을 제공합니다.

콤포넌트는 아이펀 엔진 이 기본적으로 제공하는 엔진 콤포넌트 와 사용자가 직접 제작하는 사용자 콤포넌트 로 나눌 수 있으며, 사용자가 생성한 서버 프로젝트 역시 사용자 콤포넌트 입니다.

my_project 라는 이름의 프로젝트를 생성하면 다음과 같이 Component 클래스를 상속받는 것을 볼 수 있습니다.

class MyProjectServer : public Component

Note

C# 프로젝트는 경우, 서버 동작 자체는 C# 소스코드를 통해서 정의하지만 뼈대가 되는 C++ 클래스 에서 C# 함수들을 호출하도록 매핑되어 있습니다.

MANIFEST 파일이란?

콤포넌트들은 MANIFEST.json 파일을 통해서 정의하며, 아이펀 엔진 설치 경로에는 내장 콤포넌트들의 기본 정의를 설정하는 MANIFEST.json 파일들이 함께 설치됩니다.

각 콤포넌트는 다른 콤포넌트와 의존 관계를 가지기도 하는데, 이러한 의존성 역시 MANIFEST.json 파일에 함께 정의하며 해당 콤토넌트는 의존하는 콤포넌트 없이는 동작할 수 없습니다.

여러분이 아이펀 엔진 으로 생성한 서버 프로젝트도 하나의 콤포넌트입니다. 따라서 여러분의 서버가 필요로 하는 콤포넌트들에 대한 의존성 역시 MANIFEST.json 에 명시돼야 합니다.

MANIFEST.json 파일의 구조

실제로 사용되는 MANIFEST.json 파일의 예를 보면서 구조를 설명해 보겠습니다. 다음은 서버에서 오브젝트를 다루기 위해 필요한 Object 콤포넌트를 정의하는 MANIFEST.json 파일의 일부입니다.

"components": [
  {
    "name": "Object",
    "dependency": {
      "IoService": {},
      "RandomGenerator": {},
      "RawTimer": {},
      "ObjectDirectoryService": {}
    },
    "arguments": {
      "enable_database" : false,
      "cache_expiration_in_ms" : 300000,
      "copy_cache_expiration_in_ms": 700,

콤포넌트를 정의할 때 공통적으로 사용하는 파라미터들에 대한 설명은 다음과 같습니다.

  • name: 콤포넌트의 이름입니다. 만약 여러분이 생성한 프로젝트의 MANIFEST.json 파일이라면 name 은 프로젝트를 생성할 때 지정한 프로젝트명이 됩니다.

  • dependency: 이 콤포넌트가 의존하는 다른 콤포넌트를 나열합니다. 없을수도 있고, 여러개일수도 있습니다.

  • arguments: 이 콤포넌트의 설정 가능한 항목들을 나열합니다.

그럼 다음은 여러분이 생성한 서버의 MANIFEST.json 파일을 열어 보겠습니다.

서버 프로젝트를 생성하면 {프로젝트명}-source 디렉터리가 생성되는데, src 디렉터리에 여러분이 생성한 서버 콤포넌트를 정의하는 MANIFST.json 파일이 있습니다.

Note

이해를 돕기 위해 구성 요소들이 정의된 순서를 변경하고, 일부 콤포넌트에 대한 설명을 생략했으므로 여러분이 생성한 서버의 MANIFEST.json 파일과 완전히 일치하지 않습니다.

{
  // MANIFEST.json schema 버전을 나타냅니다. 현재는 항상 1이어야 합니다.
  // ** 임의로 변경하지 마세요. **
  "version": 1,

  "components": [
    {
      // "funapi_initiator my_project" 명령을 실행해서 프로젝트를 만들었다면
      // 서버 콤포넌트명은 MyProjectServer 가 됩니다.
      // ** 임의로 변경하지 마세요. **
      "name": "MyProjectServer",

      // 서버 콤포넌트에 파라미터를 넘길 필요가 있다면 argument 에 정의해 주세요.
      // 샘플 프로젝트에 "example_arg1" 와 "example_arg2" 를 출력하는
      // 코드가 있습니다.
      "arguments": {
        "example_arg1": "val1",
        "example_arg2": 100
      },

      // "funapi_initiator my_project" 로 프로젝트를 만든 경우 libmy_project.so 가 됩니다.
      // ** 임의로 변경하지 마세요. **
      "library": "libmy_project.so",

      // 의존하는 아이펀 엔진 콤포넌트들의 설정값을 여기에 지정합니다.
      // 아이펀 엔진 콤포넌트들의 기본 정의들은
      // 리눅스인 경우 /usr/share/funapi/manifests,
      // 윈도우즈인 경우 <아이펀 엔진 설치 경로>\share\funapi\manifests
      // (기본 설치 경로: C:\Program Files\iFunFactory\iFunEngine\VS2019\share\funapi\manifests)
      // 아래 그룹별 디렉터리의 MANIFEST.json 에 정의되어 있습니다.
      "dependency": {

        ...

        "SessionService": {
          // SessionService 콤포넌트의 설정을 변경합니다.
          "tcp_json_port": 8012,
          ...
        },

        ...

        "IoService": {
          // IoService 콤포넌트의 설정을 변경합니다.
          "internal_threads_size": 2,
          ...
        }

        ...
      }
    }
  ]
}
  • version: MANIFEST.json* 파일의 schema 버전을 정의합니다. 현재는 항상 1이어야 합니다. **(임의로 변경하지 마세요.)

  • name: 여러분이 생성한 서버도 콤포넌트이기 때문에 이름이 필요합니다. 프로젝트를 생성할 때 지정한 프로젝트 이름을 콤포넌트 이름으로 사용합니다. (임의로 변경하지 마세요.)

  • library: 여러분이 생성한 서버 코드(콤포넌트)는 빌드과정을 통해 shared object 로 만들어집니다. 이 때 생성하는 shared object 파일의 이릅을 정의합니다. (임의로 변경하지 마세요.)

  • dependency: 여러분이 생성한 서버 콤포넌트가 의존하는 아이펀 엔진 내장 콤포넌트들의 설정값을 여기에 정의합니다. 여기에 명시되지 않은 아이펀 엔진 내장 콤포넌트의 설정값은 기본 설정을 따릅니다. 아이펀 엔진 콤포넌트들의 기본 정의들은 리눅스인 경우 /usr/share/funapi/manifests 아래, 윈도우즈인 경우 <아이펀 엔진 설치 경로>\share\funapi\manifests (기본 설치 경로: C:\Program Files\iFunFactory\iFunEngine\VS2019\share\funapi\manifests) 그룹별 디렉터리의 MANIFEST.json 에 정의되어 있습니다.

    Note

    각 콤포넌트들의 설정 가능한 항목들에 대한 설명은 설정 파일 (MANIFEST.json) 상세 를 참고해 주시기 바랍니다.

MANIFEST.json 샘플

다음은 앞서 설명에 사용했던 my_project 프로젝트에서 자동 생성된 MANIFEST.json 입니다.

{
  "version": 1,
  "components": [
    {
      "name": "MyProjectServer",
      "arguments": {
        "example_arg1": "val1",
        "example_arg2": 100
      },
      "dependency": {
          "AppInfo": {
            "app_id": "MyProject",
            "client_current_version": "0.0.3",
            "client_compatible_versions": ["0.0.1", "0.0.2"],
            "client_update_info": "",
            "client_update_uri": ""
          },
          "EventDispatcher": {
            "event_threads_size": 4,
            "enable_event_profiler": true,
            "enable_outstanding_event_profilier": true,
            "slow_event_log_threshold_in_ms": 300,
            "event_timeout_in_ms": 30000,
            "enable_inheriting_event_tag": true,
            "enable_random_event_tag": true,
            "enable_event_thread_checker": true
          },
          "Logging": {
            "activity_log_output": "json://activity/activity_log.json",
            "activity_log_rotation_interval": 60,
            "activity_log_write_schema": true,
            "glog_flush_interval": 1,
            "glog_retention_period_in_days": 30
          },
          "IoService": {
            "internal_threads_size": 4
          },
          "SessionService": {
            "tcp_json_port": 8012,
            "udp_json_port": 0,
            "http_json_port": 8018,
            "tcp_protobuf_port": 0,
            "udp_protobuf_port": 0,
            "http_protobuf_port": 0,
            "session_timeout_in_second" : 300,
            "use_session_reliability": false,
            "use_sequence_number_validation": false,
            "use_encryption": false,
            "tcp_encryptions": ["ife1", "ife2"],
            "udp_encryptions": ["ife2"],
            "http_encryptions": [],
            "encryption_ecdh_key": "02d87cf9965f27cec9dd399b00cde2fb1c39af4711df5a1cb96a79f597c2e1b8",
            "disable_tcp_nagle": true,
            "enable_http_message_list": true,
            "session_message_logging_level": 0,
            "enable_per_message_metering_in_counter": false,
            "json_protocol_schema_dir": "json_protocols",
            "ping_sampling_interval_in_second": 0,
            "ping_message_size_in_byte": 0,
            "ping_timeout_in_second": 0,
            "close_transport_when_session_close": true,
            "send_session_id_as_string": true
          },
          "Timer": {},
          "Object": {
            "enable_database" : false,
            "cache_expiration_in_ms": 300000,
            "copy_cache_expiration_in_ms": 700,
            "enable_delayed_db_update" : false,
            "db_update_delay_in_second" : 10,
            "db_mysql_server_address" : "tcp://127.0.0.1:3306",
            "db_mysql_id" : "funapi",
            "db_mysql_pw" : "funapi",
            "db_mysql_database" : "funapi",
            "db_read_connection_count" : 8,
            "db_write_connection_count" : 16,
            "db_key_shard_read_connection_count" : 8,
            "db_key_shard_write_connection_count" : 16,
            "db_character_set": "utf8",
            "use_db_select_transaction_isolation_level_read_uncommitted": true,
            "use_db_stored_procedure": true,
            "export_db_schema": false,
            "use_db_char_type_for_object_id": false,
            "enable_assert_no_rollback" : true
          },
          "AccountManager": {
            // To redirect client to servers behind load-balancers, set
            // redirection_strict_check_server_id to "false".
            "redirection_strict_check_server_id": true,
            // Secret hexdecimal string used to protect redirection-procedure
            // from tampering.
            "redirection_secret": "403f217b685f437df905541b5e3286f1725153f7a95e63d1c0158731d52c0d5f"
          },
          "CounterService": {
            "counter_flush_interval_in_sec": 0,
            "counter_monitoring_interval_in_sec": 30,
            "warning_threshold_event_queue_length": 3000,
            "warning_threshold_outstanding_fetch_query": 5000,
            "warning_threshold_outstanding_update_query": 5000
          },
          "RuntimeConfiguration": {
            "enable_runtime_configuration": true,
            "additional_configurations": []
          },
          "ApiService": {
            "api_service_port": 8014,
            "api_service_event_tags_size": 1,
            "api_service_logging_level": 2
          },
          "AuthenticationClient": {
            "use_authenticator" : false,
            "remote_authenticator_ip_address" : "127.0.0.1",
            "remote_authenticator_port" : 12800
          },
          "BillingClient": {
            "use_biller" : false,
            "remote_biller_ip_address" : "127.0.0.1",
            "remote_biller_port" : 12810,
            "googleplay_refresh_token" : "",
            "googleplay_client_id" : "",
            "googleplay_client_secret" : ""
          },
          "LeaderboardClient": {
            "use_leaderboard" : false,
            "leaderboard_agents": {
              "" : {
                "address": "127.0.0.1:12820",
                "fallback_servers": []
              }
            }
          },
          "ClientResourceService": {
            "use_client_resource_service" : false,
            "client_resource_service_port" : 0,
            "client_resource_dir" : "client_data",
            "client_resource_url_base": "",
            "client_resource_list_url": "",
            "client_resource_service_threads_size": 2,
            "client_resource_max_file_size": 10485760
          },
          "MapLoader": {
            "use_map_loader": false,
            "map_export_path": "",
            "map_server_url": ""
          },
          "SystemInfo": {
            "systeminfo_refresh_interval_in_sec": 5
          },
          "ResourceManager": {
            "game_json_data_dir": "game_data",
            "enable_game_data_mysql": false,
            "game_data_mysql_server": "tcp://localhost:3306",
            "game_data_mysql_username": "funapi",
            "game_data_mysql_password": "funapi",
            "game_data_mysql_database": "game_data",
            "game_data_mysql_character_set": "utf8",
            "game_data_mysql_tables": "game_data_table1,game_data_table2"
          },
          "Redis": {
            "enable_redis": false,
            "redis_mode": "redis",
            "redis_servers": {
              "": {
                "address": "127.0.0.1:6379",
                "auth_pass": ""
              }
            },
            "redis_sentinel_servers": {
              "": {
                "master_name": "mymaster",
                "addresses": ["127.0.0.1:26379"],
                "auth_pass": ""
              }
            },
            "redis_async_threads_size": 4
          },
          "MaintenanceService": {
            "under_maintenance": false,
            "maintenance_data_path": ""
          },
          "RpcService": {
            "rpc_enabled": false,
            "rpc_threads_size": 4,
            "rpc_port": 8015,
            "rpc_nic_name": "",  // if not specified, uses first NIC appeared in predictable network interface names.
            "rpc_tags": [],
            "rpc_message_logging_level": 0,
            "rpc_disable_tcp_nagle": true,
            "enable_rpc_reply_checker": true
          },
          "ZookeeperClient": {
            "zookeeper_nodes": "localhost:2181",
            "zookeeper_client_count": 4,
            "zookeeper_session_timeout_in_second": 60
          },
          "HardwareInfo": {
            "external_ip_resolvers": "aws,nic:eth0,nat:192.0.2.113:tcp+pbuf=8012:http+json=8018"
          },
          "Curl": {
            "curl_threads_size": 1
          },
          "HttpClientPool": {
            "http_client_pool_max_recycle_interval_in_sec": 3
          },
          "CrossServerStorage": {
            "enable_cross_server_storage": false,
            "redis_tag_for_cross_server_storage": ""
          }
      },
      "library": "libmy_project.so"
    }
  ]
}

MANIFEST.json 내용 암호화

MANIFEST.json 에는 DB 암호 등 민감한 정보를 저장해야 될 수 있습니다. 개발 과정에서는 소스 접근 권한이 있는 개발자들만 접근하고, 서비스 환경에서는 라이브 서버에 접근할 수 있는 관리자만이 MANIFEST.json 을 볼 수 있기 때문에 대개의 경우는 큰 문제가 되지 않지만, 더 안전하게 관리하기 위해 MANIFEST.json 파일에 민감한 정보를 그대로 문자열로 저장하지 않고 암호화해야 할 수도 있습니다.

아이펀 엔진 은 라이선스 파일(account.ilf) 을 기반으로 문자열을 암호화하는 설정 암호화 기능을 제공합니다.

Important

설정 암호화 기능은 account.ilf 파일을 기반으로 암호화 및 복호화가 이루어지기 때문에 호스트 account.ilf 파일이 없거나 암호화를 수행한 호스트와 복호화를 수행하는 호스트의 파일이 다를 경우 복호화 결과가 원본 문자열과 다를 수 있습니다.

특히, account.ilf 파일을 갱신하면 암호화 문자열을 다시 생성해야 합니다.

문자열 암호화 하기

설정 암호화 기능은 문자열에 대해서만 사용할 수 있으며 결과로 출력되는 암호화된 문자열은 MANIFEST.json 파일의 설정으로써 직접 사용할 수 있습니다.

mypassword 라는 문자열을 암호화 하기위해서는 다음과 같이 입력합니다.

$ funapi_argument_encryptor "mypassword"
/etc/ifunfactory/account.ilf loaded.
Argument: mypassword
Encrypted argument: __ENCA_[ASnbrseCc050UsCHZWzV5A]

만일, account.ilf 파일의 경로를 지정하고 싶다면 다음과 같이 사용할 수 있습니다.

$ funapi_argument_encryptor "mypassword" /path/to/game/servers/account.ilf
/path/to/game/servers/account.ilf loaded.
Argument: mypassword
Encrypted argument: __ENCA_[ASnbrseCc050UsCHZWzV5A]

암호화된 문자열을 MANIFEST.json 에 적용하기

위에서 생성된 Encrypted argument 의 값을 MANIFEST.json 파일에 그대로 입력합니다. 다음은 Object 콤포넌트 설정 중 db_mysql_pw 에 앞에서 mypassword 원본 문자열을 암호화한 결과를 설정하는 예제입니다.

...
"Object": {
    ...,
    "db_mysql_server_address" : "tcp://127.0.0.1:3306",
    "db_mysql_id" : "funapi",
    "db_mysql_pw" : "__ENCA_[ASnbrseCc050UsCHZWzV5A]",
    "db_mysql_database" : "funapi",
    ...
},
...

이렇게 MANIFEST.json 파일에 사용된 암호화 문자열은 아이펀 엔진 이 자동으로 복호화해서 사용합니다.

단, 앞서 설명한 것처럼 암호화할 때 사용했던 라이선스 파일(account.ilf) 파일을 가지고 서버를 기동해야 하는 것을 유념해 주세요.

MANIFEST.json 오버라이드하기

MANIFEST.json 은 실제 서비스 되는 기계에 게임 서버 프로그램이 배포될 때 같이 배포되며 모든 기계에 공통적으로 적용되는 정적인 정보들을 담고 있습니다.

따라서 서비스되는 호스트에서 배포되어 설치된 MANIFEST.json 을 수작업으로 수정하는 것은 바람직하지 않습니다. 이런 방법은 서버 대수가 많아짐에 따라 관리 문제를 야기하기때문입니다.

그러나 아주 특수한 경우에는 불가피하게 서비스되는 호스트의 MANIFEST.json각 기계에 맞춰 수정해야되는 경우가 있을 수 있습니다.

이런 경우에는 MANIFEST.json 에 있는 내용을 *override*하는 별도의 파일을 사용할 수 있습니다.

Important

이 파일은 각 호스트별 임시 설정에 해당하기 때문에 게임 서비스 패키징 에는 포함되지 않습니다.

override 파일은 사용 중인 운영체제가 리눅스(CentOS, Ubuntu)인지, 윈도우즈인지에 따라 생성해야할 위치가 달라집니다.

리눅스(CentOS, Ubuntu)인 경우 파일 경로

만일 funapi_initiator my_project 라는 이름으로 프로젝트를 만들었다면, 서비스되는 호스트에 /etc/my_project/MANIFEST.override.json 이라는 이름으로 파일을 만듭니다.

만약 Flavor: 역할에 따라 서버 구분하기 기능을 사용하여 my_flavor 의 경우만 설정을 override 한다면, /etc/my_project/MANIFEST.my_flavor.override.json 라는 이름으로 파일을 만듭니다.

예를 들어 hello 라는 이름의 프로젝트에서 game 이라는 flavor 를 정의했다면 /etc/hello/MANIFEST.game.override.json 이 됩니다.

윈도우즈인 경우 파일 경로

만일 my_project 라는 이름으로 프로젝트를 만들고, 서버 패키징을 통해 패키지 압축파일을 생성 한 뒤에, 서비스 될 호스트의 C:\server 경로에 압축을 해제하였다고 가정하곘습니다.

이 경우 우선 C:\server\etc 디렉터리와 C:\server\etc\my_project 디렉터리를 생성합니다. 그리고 C:\server\etc\my_project\MANIFEST.override.json 이라는 이름으로 파일을 만듭니다.

Caution

프로젝트 생성 시 입력한 이름에 공백 또는 대문자가 포함된 경우, 디렉터리를 다른 이름으로 생성해야 합니다. 이런 경우 패키지를 압축해제 후 bin 디렉터리 내부에 있는 <xxx>_launcher.bat 파일 이름에서 <xxx> 부분을 이용해 C:\server\etc\<xxx> 디렉터리를 생성하신 후, override 파일을 생성하시면 됩니다.

예를 들어 iFun Engine Game 으로 프로젝트를 최초에 생성하셨다면, bin 디렉터리 내부에는 i_fun_engine_game-launcher.bat` 라는 파일이 생성됩니다. 경우 *override* 파일은 ``C:\server\etc\i_fun_engine_game\MANIFEST.override.json 으로 생성해야 합니다.

만약 Flavor: 역할에 따라 서버 구분하기 기능을 사용하여 my_flavor 의 경우만 설정을 override 한다면, C:\server\etc\my_project\MANIFEST.my_flavor.override.json 라는 이름으로 파일을 만듭니다.

예를 들어 hello 라는 이름의 프로젝트에서 game 이라는 flavor 를 정의했다면 C:\server\etc\hello\MANIFEST.game.override.json 이 됩니다.

override 파일 형식

다음과 같은 형태로 내용을 기술합니다.

{
  "override": {
    "ApiService": {
      "api_service_port": 9014
    },
    "SessionService": {
      "tcp_json_port": 0,
      "udp_json_port": 8017
    }
  }
}

먼저 override 라는 JSON 프로퍼티가 존재해야되며, 그 안에 아이펀 엔진 기능 이름을 나열하고, 각 기능별로 필요한 설정값을 저장합니다. 위의 예는 ApiServiceapi_service_port, SessionServicetcp_json_port, udp_json_portoverride 한 경우입니다.