언리얼 전용 서버 배포: 소스 빌드, Client/Server 패키징, AWS EC2 운영
에디터에서 멀티플레이가 동작하는 것과 인터넷에 전용 서버를 배포하는 것은 서로 다른 단계다. 로컬 빌드는 개발 코드를 실행 가능한 바이너리로 만들지만, 다른 컴퓨터에서 게임을 실행하려면 플랫폼에 맞게 변환한 에셋과 설정, 런타임 의존성까지 함께 전달해야 한다.
언리얼 전용 서버 배포는 다음 파이프라인으로 이해할 수 있다.
엔진 소스 준비
-> 프로젝트를 소스 빌드 엔진에 연결
-> Client / Server Target 정의
-> 코드 Build
-> 에셋 Cook
-> 결과물 Stage / Package
-> 로컬 전용 서버 테스트
-> 서버 패키지를 EC2로 전송
-> AWS Security Group과 Windows Firewall 개방
-> 공인 IP와 UDP 포트로 클라이언트 접속
-> 로그와 비용을 모니터링하고 자원 정리
핵심은 클라이언트 실행 파일과 서버 실행 파일이 같은 프로젝트에서 나오더라도 서로 다른 Target이며, 실행 파일만 복사해서는 패키지가 완성되지 않는다는 점이다.
Build, Cook, Stage, Package는 서로 다른 작업이다
패키징 로그에는 여러 단계가 연속으로 나타난다. 각 단계가 만드는 결과를 구분하면 오류가 코드 문제인지, 에셋 문제인지, 배포 경로 문제인지 빠르게 판단할 수 있다.
| 단계 | 처리 대상 | 결과 |
| Build | C++ 코드, 모듈, 플러그인 | 선택한 Target과 Configuration에 맞는 실행 파일과 바이너리를 만든다. |
| Cook | 맵, Blueprint, 텍스처, 머티리얼, 사운드 | 에디터용 에셋을 WindowsClient 또는 WindowsServer 같은 런타임 형식으로 변환한다. |
| Stage | 빌드된 코드와 쿠킹된 콘텐츠 | 실행에 필요한 파일을 독립된 Staging 디렉터리에 모은다. |
| Package | Staging 결과 | 전달 가능한 실행 파일과 콘텐츠 컨테이너 묶음으로 만든다. |
| Deploy | 완성된 패키지 | EC2 같은 실행 대상에 업로드한다. |
| Run | 배포된 패키지 | 명령줄 인자를 적용해 서버나 클라이언트를 실행한다. |
프로젝트의 Binaries/Win64에 생성된 실행 파일은 Build 결과다. 이 파일만 다른 PC에 복사하면 쿠킹된 맵과 에셋이 없어서 정상적으로 실행되지 않는다. 배포 단위는 특정 .exe 하나가 아니라 Staging 또는 Packaging 결과 폴더 전체다.
전용 서버에 소스 빌드 엔진이 필요한 이유
UE 5.5의 Epic Games Launcher 배포판으로 일반 Windows 게임을 패키징할 수는 있지만, 공식 전용 서버 구성 절차는 소스 빌드 엔진과 C++ 프로젝트를 전제로 한다. Launcher 엔진에서 Server Target을 패키징하면 다음과 같은 오류를 만날 수 있다.
Server targets are not currently supported
from this engine distribution.
소스 빌드는 프로젝트 코드를 소스 형태로 만든다는 뜻이 아니다. GitHub에서 언리얼 엔진 소스 전체를 받아 직접 컴파일한 엔진 설치본을 사용하는 것이다. 이렇게 만든 엔진은 TargetType.Server를 포함한 필요한 타깃을 직접 빌드할 수 있다.
| 엔진 배포 방식 | 장점 | 전용 서버 관점 |
| Epic Games Launcher 바이너리 엔진 | 설치와 업데이트가 빠르고 일반 게임 개발에 편리하다. | UE 5.5 공식 전용 서버 빌드 절차에는 적합하지 않다. |
| GitHub 소스 빌드 엔진 | 엔진 코드 디버깅, 수정, 다양한 Target 빌드가 가능하다. | TargetType.Server 기반 전용 서버 빌드에 사용한다. |
소스 체크아웃, 중간 산출물, 바이너리, Derived Data Cache가 큰 공간을 사용하지만 모든 환경에 고정된 500GB가 필수인 것은 아니다. 선택한 브랜치, 플랫폼 의존성, 디버그 심볼, 캐시 상태에 따라 사용량이 달라진다. 시작 전에 충분한 여유 공간을 확보하고, 빌드 중 남은 용량을 모니터링하는 것이 정확하다.
언리얼 엔진 소스를 준비하는 흐름
Epic Games GitHub 저장소는 계정 연결이 필요하다.
- Epic Games 계정과 GitHub 계정을 만든다.
- Epic Games 계정의 Connections에서 GitHub 계정을 연결한다.
- GitHub로 도착한 EpicGames 조직 초대를 기한 안에 수락한다.
- 프로젝트와 같은 버전의 안정된 Release 브랜치 또는 태그를 선택한다.
- 저장소를 Clone하거나 ZIP으로 내려받는다.
Setup.bat을 실행해 엔진 바이너리 의존성과 필수 구성요소를 받는다.GenerateProjectFiles.bat으로UE5.sln을 생성한다.- Visual Studio에서
Development Editor,Win64로 UE5 Target을 빌드한다.
Setup.bat
-> 엔진이 사용하는 바이너리 의존성과 전제 조건 준비
GenerateProjectFiles.bat
-> Target.cs와 Module 정보를 분석해 IDE 프로젝트 생성
UE5.sln Build
-> 실제 엔진 및 에디터 바이너리 컴파일
Setup.bat과 GenerateProjectFiles.bat은 공식 절차상 일반 실행으로 충분하다. 폴더 권한 때문에 실제 오류가 발생한 경우가 아니라면 모든 빌드 도구를 관리자 권한으로 실행할 필요는 없다. 소스 경로는 C:\UE55Src처럼 짧고 공백이나 특수문자가 적은 위치가 긴 경로 문제를 줄이는 데 유리하다.
ZIP을 사용하면 Windows가 인터넷에서 받은 파일을 차단할 수 있다. 압축 해제 전에 ZIP 파일 속성에서 차단 해제를 확인하거나 Git Clone을 사용한다. 개발 도중 엔진 브랜치를 갱신했다면 Setup.bat과 프로젝트 파일 생성을 다시 수행해야 할 수 있다.
프로젝트를 소스 빌드 엔진에 연결한다
소스 엔진을 컴파일해도 기존 .uproject가 자동으로 새 엔진을 사용하지는 않는다. 프로젝트의 Engine Association을 소스 빌드 엔진으로 변경하고 프로젝트 파일을 다시 생성해야 한다.
NumberBaseball.uproject
-> Switch Unreal Engine Version
-> 소스 빌드 엔진 선택
-> Generate Visual Studio project files
-> Development Editor / Win64 빌드
엔진 버전이 같더라도 Launcher 빌드와 소스 빌드는 서로 다른 Engine Association이다. 팀 프로젝트라면 어떤 엔진 커밋과 Build ID를 사용했는지 문서화해야 각 개발자와 빌드 머신의 결과가 재현된다.
Target은 어떤 프로그램을 만들지 정의한다
*.Target.cs는 Unreal Build Tool이 어떤 형태의 실행 파일을 만들지 판단하는 C# 규칙이다. Target과 Module을 혼동하면 안 된다.
| 파일 | 책임 | NumberBaseball 예시 |
*.Target.cs |
Game, Client, Server, Editor 중 어떤 실행 프로그램을 빌드할지 정의한다. | NumberBaseballServer.Target.cs |
*.Build.cs |
프로젝트 Module이 의존하는 엔진 Module과 컴파일 규칙을 정의한다. | NumberBaseball.Build.cs |
Target 종류는 포함되는 엔진 코드에도 영향을 준다.
| TargetType | 의미 | 사용처 |
Editor |
에디터 기능을 포함한 개발 도구 | 에셋 편집과 PIE 테스트 |
Game |
쿠킹된 콘텐츠로 실행하는 일반 독립 게임 | 싱글플레이 또는 리슨 서버를 포함할 수 있는 게임 빌드 |
Client |
서버 전용 코드를 제외한 네트워크 클라이언트 | 플레이어에게 배포하는 전용 클라이언트 |
Server |
클라이언트와 렌더링 관련 코드를 제외한 전용 서버 | EC2에서 헤드리스로 실행하는 권위 서버 |
Server Target 작성
Editor.Target.cs를 복사하고 문자열을 일괄 치환하는 방식은 불필요한 Editor 규칙을 가져올 위험이 있다. 기본 Game Target의 버전 설정을 기준으로 새 파일을 명시적으로 작성하는 편이 안전하다.
// Source/NumberBaseballServer.Target.cs
using UnrealBuildTool;
using System.Collections.Generic;
public class NumberBaseballServerTarget : TargetRules
{
public NumberBaseballServerTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Server;
DefaultBuildSettings = BuildSettingsVersion.V5;
IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_5;
ExtraModuleNames.Add("NumberBaseball");
}
}
파일명, Target 클래스명, 생성자명의 대응이 정확해야 한다.
파일명 NumberBaseballServer.Target.cs
클래스명 NumberBaseballServerTarget
생성자명 NumberBaseballServerTarget
모듈명 NumberBaseball
하나라도 다르면 Unreal Build Tool이 Target을 발견하지 못하거나 C# 컴파일 오류를 낸다.
Client Target 작성
전용 클라이언트가 필요하면 같은 방식으로 TargetType.Client를 정의한다.
// Source/NumberBaseballClient.Target.cs
using UnrealBuildTool;
using System.Collections.Generic;
public class NumberBaseballClientTarget : TargetRules
{
public NumberBaseballClientTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Client;
DefaultBuildSettings = BuildSettingsVersion.V5;
IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_5;
ExtraModuleNames.Add("NumberBaseball");
}
}
Game Target만으로도 네트워크 게임 실행 파일을 만들 수 있지만, Client Target은 서버 코드를 제외한 명확한 클라이언트 산출물을 만든다. 별도의 클라이언트 패키지를 운영하지 않는 프로젝트라면 Client Target은 선택 사항이고 Server Target은 전용 서버 실행 파일에 필요하다.
Target 파일을 추가한 뒤에는 .uproject에서 Visual Studio 프로젝트 파일을 다시 생성한다. 기존에 열려 있던 IDE가 새 Target을 즉시 인식한다고 가정하면 안 된다.
현재 NumberBaseball의 Target 상태
현재 프로젝트에는 다음 두 Target만 있다.
| 현재 파일 | TargetType | 상태 |
NumberBaseball.Target.cs |
Game |
일반 게임 실행 파일용 Target이 준비되어 있다. |
NumberBaseballEditor.Target.cs |
Editor |
언리얼 에디터 개발용 Target이 준비되어 있다. |
NumberBaseballClient.Target.cs |
Client |
아직 존재하지 않는다. |
NumberBaseballServer.Target.cs |
Server |
아직 존재하지 않는다. |
위 코드는 프로젝트에 실제로 추가한 결과가 아니라, 현재 BuildSettingsVersion.V5와 Unreal5_5 설정을 유지하면서 배포 단계에서 추가할 Target 예시다.
서버 코드와 클라이언트 코드를 컴파일 단계에서 나눈다
Server Target은 렌더링을 하지 않지만 프로젝트 Module에 UI 코드가 포함되어 있으면 그 코드가 무조건 안전해지는 것은 아니다. 빌드 타깃에 따라 필요 없는 의존성이나 코드를 조건부로 제외할 수 있다.
// NumberBaseball.Build.cs
if (Target.Type != TargetType.Server)
{
PublicDependencyModuleNames.AddRange(
new string[]
{
"UMG",
"Slate",
"SlateCore"
}
);
}
#if !UE_SERVER
void ANBPlayerController::CreateLocalHUD()
{
// 클라이언트 전용 UI 코드
}
#endif
다만 기존 코드가 UMG 타입을 공개 헤더나 공통 클래스 선언에 사용한다면 의존성만 제거했을 때 컴파일이 깨질 수 있다. 먼저 UI 경계를 정리하고 Target별 컴파일을 실제로 통과시키면서 의존성을 줄여야 한다. 최적화를 위해 무작정 Module을 제거하는 것은 배포의 첫 단계가 아니다.
맵 설정은 실행 시작점과 쿠킹 범위를 결정한다
클라이언트와 서버는 같은 프로젝트를 사용하지만 시작 맵의 목적이 다르다.
| 설정 | 사용 주체 | 예시 |
| Game Default Map | 일반 게임 또는 클라이언트 | 서버 주소를 입력하는 Title 맵 |
| Server Default Map | 전용 서버 | 실제 매치가 실행되는 게임 맵 |
| List of Maps to Include | Cook 과정 | 패키지에 반드시 포함할 Title과 게임 맵 |
현재 NumberBaseball에는 L_Chatting.umap 하나가 있고 GameDefaultMap과 EditorStartupMap이 이 맵을 가리킨다. ServerDefaultMap과 명시적인 MapsToCook 설정은 없다. 현재 구조 그대로라면 서버 실행 명령에 맵을 직접 넘기거나 Server Default Map을 추가해야 시작점이 명확해진다.
[/Script/EngineSettings.GameMapsSettings]
GameDefaultMap=/Game/NumberBaseball/Maps/L_Chatting.L_Chatting
ServerDefaultMap=/Game/NumberBaseball/Maps/L_Chatting.L_Chatting
EditorStartupMap=/Game/NumberBaseball/Maps/L_Chatting.L_Chatting
맵을 명령줄로 직접 지정할 수도 있다.
NumberBaseballServer.exe /Game/NumberBaseball/Maps/L_Chatting -log
맵이 패키지에 쿠킹되어 있지 않으면 이름이 정확해도 열리지 않는다. Editor에서 열 수 있다는 사실은 Cook 결과에 포함되었다는 보장이 아니다. 특히 코드나 다른 에셋에서 하드 레퍼런스되지 않는 맵, 문자열로만 열리는 맵, 런타임에 동적으로 로드하는 에셋은 Packaging Settings의 목록이나 Primary Asset 규칙으로 명시해야 한다.
전용 서버에는 ?listen이 필수가 아니다
리슨 서버는 플레이어 클라이언트가 서버 역할까지 겸하므로 맵 URL에 ?listen을 붙여 서버로 열 수 있다.
NumberBaseball.exe L_Chatting?listen
반면 NumberBaseballServer.exe는 TargetType.Server로 빌드된 전용 서버다. 실행 자체가 서버이므로 다음처럼 맵과 포트만 지정하면 된다.
NumberBaseballServer.exe /Game/NumberBaseball/Maps/L_Chatting -log -port=7777
전용 서버 명령에 ?listen을 붙여도 일부 구성에서 동작할 수 있지만 의미상 중복이다. 리슨 서버 실행법과 전용 서버 실행법을 구분해 두면 배포 스크립트가 명확해진다.
클라이언트와 서버를 각각 Cook하고 Package한다
Project Launcher의 Custom Profile을 사용하면 Build, Cook, Package, Deploy, Run 단계를 세밀하게 설정할 수 있다. 최종 배포 패키지를 만들 때는 자동 실행까지 켜 둘 필요가 없다.
| 항목 | 클라이언트 프로필 | 서버 프로필 |
| Build Target | NumberBaseballClient |
NumberBaseballServer |
| Platform | WindowsClient |
WindowsServer |
| Configuration | 개발 테스트는 Development, 배포 후보는 Shipping 검토 | 초기 운영 검증은 Development, 최종 운영은 Shipping 검토 |
| Cook | 클라이언트가 사용하는 맵과 시각·음향 에셋 | 서버 게임 규칙에 필요한 맵과 데이터 |
| Deploy | 패키지 생성만 할 때 Do Not Deploy | EC2 업로드는 별도 배포 과정으로 처리 가능 |
| Run | 자동 테스트 때만 활성화 | 패키징 완료 후 자동 실행이 불필요하면 비활성화 |
Project Launcher가 자동 실행까지 수행하도록 설정되어 있으면 패키징 도중 클라이언트나 서버가 뜬다. 실행 창을 닫아야 패키징이 완료되는 정상 규칙은 아니다. 배포 산출물만 필요하다면 Launch 단계에서 실행하지 않도록 Profile을 구성한다.
결과 폴더 이름은 엔진 버전과 Profile 설정에 따라 달라질 수 있다. Saved/StagedBuilds/WindowsClient나 WindowsServer는 흔한 Staging 경로이지만, Package Project에서 직접 지정한 출력 디렉터리를 사용할 수도 있다. 폴더 이름을 고정 가정하기보다 UAT 로그의 StageDirectory와 ArchiveDirectory를 확인하는 편이 정확하다.
Development와 Shipping의 차이
| Configuration | 장점 | 사용 시점 |
| Development | 로그와 디버깅 기능을 활용하면서 최적화된 실행을 테스트하기 쉽다. | 로컬 패키징 검증과 초기 EC2 배포 |
| Shipping | 디버깅 기능을 줄인 최종 배포용 구성이다. | 충분한 테스트와 운영 로깅 정책을 마련한 릴리스 |
처음부터 Shipping으로만 테스트하면 콘솔과 일부 로그가 제한되어 원인을 찾기 어렵다. Development Server로 네트워크, 맵, 런타임 의존성, 방화벽을 먼저 검증한 뒤 Shipping으로 전환하는 편이 효율적이다.
패키징 결과는 폴더 전체로 검증한다
패키지를 다른 위치에 복사한 뒤 개발 프로젝트 경로와 에디터가 없어도 실행되는지 확인한다. 이 테스트는 우연히 로컬 Cook 결과나 엔진 DLL을 참조하는 문제를 잡아낸다.
WindowsServer/
NumberBaseballServer.exe 또는 실행용 루트 파일
Engine/
NumberBaseball/
Binaries/
Content/Paks 또는 IoStore 컨테이너
Config/
서버 패키지를 배포할 때는 이 구조를 유지해야 한다. .exe만 바탕화면으로 꺼내거나 일부 DLL만 선택해 전송하지 않는다.
로컬에서 먼저 전용 서버를 검증한다
클라우드에 올리기 전에 같은 PC에서 패키지의 실행과 접속을 검증하면 AWS 문제와 게임 문제를 분리할 수 있다.
.\NumberBaseballServer.exe `
/Game/NumberBaseball/Maps/L_Chatting `
-log `
-port=7777 `
-unattended `
-NoSound
다른 터미널에서 클라이언트를 실행한다.
.\NumberBaseballClient.exe `
127.0.0.1:7777 `
-windowed `
-ResX=1280 `
-ResY=720 `
-log
로그에서 다음 항목을 확인한다.
- 올바른 게임 맵이 로드되었는가?
IpNetDriver가 의도한 UDP 포트에서 Listen하는가?- 클라이언트 접속 때
PreLogin,Login,PostLogin흐름이 완료되는가? - 서버에서 Widget이나 LocalPlayer를 찾는 오류가 발생하지 않는가?
- 패키지를 다른 경로로 옮겨도 에셋 누락 없이 실행되는가?
7777은 언리얼 전용 서버의 일반적인 기본 포트지만 절대값이 아니다. 동일한 머신에서 여러 서버를 실행하거나 -port를 지정하면 다른 포트를 사용할 수 있다. 로그에 출력된 실제 포트와 클라이언트 접속 주소를 일치시켜야 한다.
AWS EC2 배포 구조
EC2 Windows 인스턴스에 전용 서버를 배포하면 네트워크 요청은 여러 계층을 통과한다.
외부 클라이언트
-> EC2 Public IPv4 또는 DNS
-> VPC Route / Internet Gateway
-> Security Group의 UDP 7777 Inbound Rule
-> Windows Defender Firewall의 UDP 7777 Inbound Rule
-> NumberBaseballServer의 IpNetDriver
Security Group만 열거나 Windows Firewall만 열어서는 접속되지 않는다. 서버 프로세스가 Listen하는 포트와 프로토콜까지 네 요소가 모두 일치해야 한다.
Region과 Instance Type을 고르는 기준
Region은 관리자가 편한 위치보다 실제 플레이어와 가까운 위치를 우선한다. 한국 사용자 대상 테스트라면 서울 Region이 합리적이지만, 다른 지역 사용자가 주 대상이면 지연 시간을 측정해 선택해야 한다.
전용 서버는 렌더링하지 않으므로 일반적으로 GPU 인스턴스가 필요하지 않다. 먼저 작은 범용 인스턴스로 기능을 검증하고, 서버 Tick 시간, CPU 사용률, 메모리, 동시 접속자 수를 측정해 확장한다.
| 자원 | 영향을 주는 요소 | 확인 지표 |
| vCPU | 게임 Tick, 물리, AI, RPC 처리, 복제 대상 수 | 프로세스 CPU, Game Thread 시간, Tick 지연 |
| Memory | 맵 크기, 로드된 에셋, 플레이어 수, 캐시 | Working Set, Commit Size, 메모리 증가 추세 |
| Network | 복제 빈도, Actor 수, RPC, 동시 접속자 | Network In/Out, 패킷 손실, 지연 시간 |
| Storage | 서버 패키지, 로그, 크래시 덤프 | EBS 여유 공간과 로그 증가량 |
Security Group은 용도별 최소 포트만 연다
Windows EC2 테스트에 필요한 대표적인 Inbound Rule은 다음과 같다.
| 용도 | 프로토콜과 포트 | Source 권장값 |
| Remote Desktop 관리 | TCP 3389 | 관리자 PC의 현재 공인 IP 한 개 또는 회사 VPN 대역 |
| 언리얼 게임 접속 | UDP 7777 | 제한 테스트는 참여자 IP, 공개 테스트는 필요한 범위만 허용 |
RDP의 Source를 0.0.0.0/0으로 열면 인터넷 전체가 로그인 포트에 접근할 수 있다. AWS도 RDP와 SSH는 필요한 특정 IP만 허용할 것을 권장한다. 게임 UDP 포트는 공개 매치 특성상 넓게 열 수 있지만, 관리 포트와 같은 기준을 적용하면 안 된다.
Unreal의 기본 IpNetDriver 게임 트래픽은 UDP이므로 Custom TCP 7777만 추가해서는 접속되지 않는다. 클라이언트 접속 포트와 서버의 -port 값을 변경했다면 Security Group도 같은 UDP 포트로 바꾼다.
Windows Defender Firewall도 별도로 연다
Security Group을 통과한 패킷도 Windows Guest OS의 Firewall에서 차단될 수 있다. 관리자 PowerShell에서 서버 프로그램 또는 UDP 포트에 대한 Inbound Rule을 만든다.
New-NetFirewallRule `
-DisplayName "NumberBaseball Server UDP 7777" `
-Direction Inbound `
-Action Allow `
-Protocol UDP `
-LocalPort 7777
프로그램 경로까지 제한하는 규칙은 같은 포트를 다른 프로세스가 사용하는 위험을 줄일 수 있다. 패키지 경로를 바꾸면 프로그램 기반 규칙도 갱신해야 한다.
방화벽 전체를 비활성화해서 접속을 해결하는 방식은 원인 진단에는 잠깐 도움이 될 수 있어도 운영 설정으로 남겨서는 안 된다. 필요한 프로토콜과 포트만 규칙으로 추가한다.
Windows 인스턴스에 패키지를 배포한다
간단한 실습에서는 RDP의 로컬 드라이브 공유나 압축 파일 복사로 서버 패키지를 전송할 수 있다. 규모가 커지면 S3, Systems Manager, CI/CD Artifact를 사용해 버전과 무결성을 관리하는 편이 낫다.
로컬 WindowsServer 패키지
-> ZIP 압축
-> EC2에 전송
-> 짧고 고정된 경로에 압축 해제
-> 런타임 설치
-> 서버 명령 실행
예시 배포 경로는 다음처럼 단순하게 둘 수 있다.
C:\GameServer\NumberBaseball\
개발 PC에서 만든 바로 가기는 절대 경로를 포함할 수 있어 EC2에서 깨진다. 서버에서는 새 바로 가기나 실행 스크립트를 만들거나 PowerShell에서 직접 실행한다.
패키지가 Visual C++ Runtime 같은 전제 조건을 요구하면 소스 엔진의 다음 설치 프로그램을 신뢰할 수 있는 경로에서 함께 배포할 수 있다.
Engine\Extras\Redist\en-us\UEPrereqSetup_x64.exe
오류 창을 무조건 닫고 진행하기보다 누락된 런타임과 버전을 로그로 확인하고 설치해야 한다. 서버를 재배포할 때마다 설치할 필요는 없지만 새 Windows 인스턴스에는 초기 프로비저닝 단계가 필요하다.
EC2에서 서버를 실행하고 확인한다
Set-Location 'C:\GameServer\NumberBaseball'
.\NumberBaseballServer.exe `
/Game/NumberBaseball/Maps/L_Chatting `
-log `
-port=7777 `
-unattended `
-NoSound
서버가 실제로 UDP 포트를 열었는지 확인한다.
Get-NetUDPEndpoint -LocalPort 7777
실행 프로세스를 확인한다.
Get-Process NumberBaseballServer
언리얼 로그는 일반적으로 패키지의 프로젝트 디렉터리 아래 Saved\Logs에서 확인할 수 있다. 콘솔의 마지막 한 줄만 보는 대신 맵 로드, NetDriver 생성, Bind 실패, 패키지 에셋 누락을 검색한다.
LogNet: Created socket for bind address
LogNet: GameNetDriver IpNetDriver listening on port 7777
포트가 이미 사용 중이면 서버가 7778 같은 다음 포트를 임의로 사용할 것이라고 기대하지 말고, 어떤 프로세스가 점유했는지 확인한 뒤 각 서버 인스턴스에 고유한 -port를 명시한다. Security Group과 클라이언트 주소도 같은 값으로 맞춘다.
외부 클라이언트에서 접속한다
EC2 인스턴스의 Public IPv4를 확인한 뒤 클라이언트에 주소:포트를 전달한다.
NumberBaseballClient.exe 203.0.113.10:7777 -windowed -ResX=1280 -ResY=720 -log
203.0.113.10은 문서용 예시 주소이므로 실제 실행 시 EC2의 Public IPv4 또는 DNS로 교체한다. Title UI에서 주소를 입력받는 구조라면 같은 문자열을 ClientTravel()에 전달한다.
외부 접속 실패는 다음 순서로 범위를 좁힌다.
1. 서버 프로세스가 실행 중인가?
2. 올바른 게임 맵을 열었는가?
3. Get-NetUDPEndpoint에 실제 포트가 보이는가?
4. Windows Firewall이 해당 UDP 포트를 허용하는가?
5. EC2 Security Group이 같은 UDP 포트를 허용하는가?
6. 클라이언트가 Private IPv4가 아닌 Public IPv4를 사용하는가?
7. 서버 로그에 접속 시도 또는 Handshake 오류가 남는가?
8. 클라이언트와 서버 빌드의 프로젝트 버전이 호환되는가?
로컬 127.0.0.1 접속이 실패하면 AWS 설정이 원인이 아니다. 반대로 EC2 내부에서 서버가 Listen하지만 외부에서만 실패한다면 Security Group, Windows Firewall, Public IP, VPC 경로를 우선 확인한다.
Stop, Terminate, Public IP와 비용을 구분한다
AWS 자원 정리는 단순히 원격 데스크톱 창을 닫는 것으로 끝나지 않는다.
| 동작 | 인스턴스 상태 | 데이터와 비용 영향 |
| Reboot | 같은 인스턴스를 재부팅 | 컴퓨팅 과금이 계속되고 일반적으로 Public IPv4가 유지된다. |
| Stop | 나중에 다시 시작 가능 | 인스턴스 사용 요금은 멈추지만 EBS와 일부 네트워크 자원 비용은 남을 수 있다. |
| Start | 중지된 인스턴스를 재개 | 일반 Public IPv4는 바뀔 수 있어 클라이언트 주소를 다시 확인해야 한다. |
| Terminate | 인스턴스를 영구 삭제 | 다시 시작할 수 없으며 Root EBS는 기본 설정상 삭제되지만 별도 Volume은 남을 수 있다. |
On-Demand 인스턴스는 running 시간에 컴퓨팅 요금이 발생한다. Stop 상태에서는 인스턴스 사용 요금이 중단되지만 EBS 저장 공간과 연결된 Elastic IP 등은 별도 과금될 수 있다. 현재 AWS는 Public IPv4 주소에도 비용을 부과하므로 Free Tier라는 이유만으로 비용이 항상 0원이라고 가정하면 안 된다.
Stop 후 Start하면 자동 할당 Public IPv4가 바뀌는 것이 일반적이다. 주소를 고정하려고 Elastic IP를 사용하면 관리가 편하지만 그 주소 자체의 비용과 미사용 상태를 확인해야 한다. 짧은 실습이라면 재시작 후 새 주소를 확인하는 방식이 더 단순할 수 있다.
Terminate는 복구 가능한 중지가 아니다. 로그나 패키지를 보관해야 한다면 먼저 S3나 로컬 저장소로 복사하고, 연결된 EBS Volume의 Delete on termination 설정을 확인한다. 인스턴스를 종료한 뒤에도 남은 EBS Volume, Snapshot, Elastic IP가 없는지 각 서비스 화면과 Billing에서 검토한다.
운영 환경에서는 RDP로 수동 실행하지 않는다
RDP 세션에서 콘솔 창을 직접 띄우는 방식은 첫 배포 검증에는 편하지만 세션 종료, 재부팅, 프로세스 크래시에 취약하다. 운영 단계에서는 다음 기능을 추가한다.
- Windows Service나 작업 스케줄러로 부팅 시 서버를 시작한다.
- 서버 실행 스크립트와 포트를 형상 관리한다.
- 프로세스 종료를 감지해 제한적으로 재시작한다.
- 로그를 CloudWatch Logs나 중앙 저장소로 전송한다.
- CloudWatch Alarm과 AWS Budgets로 장애와 비용을 알린다.
- 패키지 버전, 엔진 커밋, Git 커밋, 배포 시각을 기록한다.
- RDP 대신 Systems Manager Session Manager 사용 가능성을 검토한다.
- IAM 권한과 Security Group을 최소 권한으로 유지한다.
자동 재시작은 무한 크래시 루프를 만들 수 있으므로 횟수 제한과 Backoff가 필요하다. 새 버전 배포 전에는 이전 패키지를 별도 디렉터리에 보관해 빠르게 Rollback할 수 있게 한다.
배포 버전 호환성을 서버가 검사해야 한다
클라이언트와 서버가 서로 다른 패키지면 프로퍼티 레이아웃, RPC, 맵, 게임 규칙이 달라질 수 있다. 단순히 접속이 성립한다고 호환된다고 볼 수 없다.
Client Build ID
-> URL Option 또는 초기 Handshake에 포함
-> Server가 허용 버전과 비교
-> 불일치 시 PreLogin에서 명시적인 오류로 거절
운영 배포에서는 최소한 다음 정보를 로그에 남긴다.
Project Version
Git Commit
Engine Version / Source Commit
Target Configuration
Map Name
Listen Port
Deployment Timestamp
이 정보가 있어야 클라이언트가 접속하지 못할 때 방화벽 문제와 버전 불일치를 구분할 수 있다.
전체 배포 체크리스트
| 구간 | 검증 항목 |
| Engine | UE 5.5에 맞는 소스 브랜치, Visual Studio 구성요소, Setup과 GenerateProjectFiles 완료 |
| Project | 소스 엔진 연결, C++ 빌드 성공, Client와 Server Target 발견 |
| Maps | Game Default Map, Server Default Map, Cook 대상 맵 설정 |
| Package | WindowsClient와 WindowsServer 결과를 별도 생성하고 폴더 전체 실행 검증 |
| Local Test | 서버 맵 로드, UDP 7777 Listen, 두 클라이언트 접속, 종료와 재접속 확인 |
| EC2 | 플레이어와 가까운 Region, 적절한 Instance Type, 충분한 EBS 선택 |
| Network | Security Group UDP 7777, Windows Firewall UDP 7777, RDP TCP 3389 Source 제한 |
| Runtime | Prerequisite 설치, 명시적인 맵과 포트, Saved/Logs 확인 |
| Client | EC2 Public IPv4와 실제 서버 포트로 접속, 버전 호환성 확인 |
| Operations | 자동 시작, 모니터링, 로그 수집, Rollback, 비용 알림 |
| Cleanup | Stop과 Terminate 정책 결정, EBS·Snapshot·Elastic IP·Public IPv4 비용 확인 |
자주 발생하는 문제
| 증상 | 가능한 원인과 확인 지점 |
Server targets are not currently supported |
프로젝트가 Launcher 바이너리 엔진을 사용 중인지 확인하고 소스 빌드 엔진으로 전환한다. |
| Development Server 구성이 보이지 않는다. | Server.Target.cs의 파일명, 클래스명, 생성자명과 프로젝트 파일 재생성을 확인한다. |
| 서버 실행 파일은 있지만 맵을 열지 못한다. | 맵 경로, Server Default Map, List of Maps to Include, Cook 로그를 확인한다. |
| 서버를 실행하면 아무 창도 보이지 않는다. | 헤드리스 실행은 정상일 수 있다. -log, 프로세스, Saved/Logs와 UDP Endpoint를 확인한다. |
| 로컬에서는 접속되지만 EC2에서는 실패한다. | Security Group의 프로토콜이 UDP인지, Windows Firewall, Public IPv4, 실제 Listen 포트를 확인한다. |
| RDP 접속이 되지 않는다. | 인스턴스 상태 검사, Public IPv4, TCP 3389 Rule, Source의 현재 관리자 공인 IP를 확인한다. |
| EC2 Stop 후 클라이언트가 접속하지 못한다. | Start 뒤 Public IPv4가 변경되었는지 확인하고 주소 설정을 갱신한다. |
| 서버 실행 직후 런타임 DLL 오류가 난다. | 패키지 폴더 일부만 복사했는지와 UE Prerequisite 설치 여부를 확인한다. |
| 원격 서버에서 UI 또는 LocalPlayer 오류가 난다. | 서버 전용 경로에서 Widget, Viewport, Local Controller 로직이 실행되는지 확인한다. |
| 인스턴스를 중지했는데 비용이 계속 보인다. | EBS, Snapshot, Elastic IP, Public IPv4 등 인스턴스 외 자원을 확인한다. |
'UE > 멀티플레이' 카테고리의 다른 글
| UE C++ 언리얼 멀티플레이 서버 및 생명주기 (0) | 2026.08.11 |
|---|---|
| UE C++ 숫자야구 (0) | 2026.08.10 |
| UE C++ 멀티플레이 동기화 (0) | 2026.08.07 |
| UE C++ RPC오너십, 프로퍼티 리플리케이션 (1) | 2026.08.06 |
| UE C++ 액터 리플리케이션 (0) | 2026.08.05 |