Dedicated Server 打包与部署
Dedicated Server 打包与部署
这篇把 UE Dedicated Server(DS)从拉代码到打包、再到自动部署上 Linux 走一遍。前半是本地打 DS 包的全流程,后半是用 Jenkins 通过 SSH 把 DS 部署到 Linux(本地机器或 AWS)的通用化 Groovy 脚本。
一、生成工程
从 Perforce 或 Git 拉到项目源码后,先在根目录跑 GenerateProjectFiles.bat 生成 .sln:
...\Engine\Build\BatchFiles\GenerateProjectFiles.bat ...\MyProject.uproject
二、打 DS 包
命令行打 Win64 Server
完整的 Windows bat 脚本:
@echo off
rem 项目目录
set PROJ_DIR=%~dp0\..\
rem uproject 路径
set PROJ_UPROJ=%PROJ_DIR%MyProject.uproject
rem 引擎目录
set UE5_DIR=%~dp0\..\..\
rem RunUAT 工具
set UE5_RUN_UAT=%UE5_DIR%Engine\Build\BatchFiles\RunUAT.bat
rem 平台只支持 Win64 和 Linux
set PLATFORM=Win64
set BUILD_TARGET=Development
rem 产物输出目录
set PROJ_BINARY_DIR=%PROJ_DIR%Archives\%BUILD_TARGET%
set BUILD_SERVER_COMMAND="%UE5_RUN_UAT%" BuildCookRun -project="%PROJ_UPROJ%" -noP4 -platform=%PLATFORM% -clientconfig=%BUILD_TARGET% -serverconfig=%BUILD_TARGET% -cook -build -stage -pak -server -serverplatform=%PLATFORM% -noclient -archive -iterate -archivedirectory="%PROJ_BINARY_DIR%"
关键开关:-server -noclient 表示只打服务端、不打客户端;-serverplatform= 指定服务端平台;-iterate 走增量 Cook。
在 Win64 上交叉编译 Linux Server
需要先装交叉编译工具链(对应引擎版本的 clang toolchain,例如 v19_clang-11.0.1-centos7.exe,装好后 UBT 会自动识别)。然后把上面脚本里的 PLATFORM 改成 Linux 即可:
set PLATFORM=Linux
其余 BuildCookRun 命令完全一致。Linux 版 DS 产物是 .sh 启动脚本。
用编辑器打包
如果不想敲命令行,也可以在编辑器的 Project Settings / Platforms 里配好 server target 后,直接 Package 出 DS 包。打出来的包放在打包时设的路径下。
三、启动 DS
打完包后会得到一个完整的 server 目录,启动方式有两类。
直接双击 exe 启动
满足下面条件才能直接双击 xxxServer.exe:
- 客户代码里没有强制必须用命令行启动的逻辑。
- 默认启动的地图和端口已经写进了
Game/Config/DefaultEngine.ini和Engine/Config/BaseEngine.ini(改完要重新打包才生效)。
如果不想重新打包,可以在已打好的包里手动建 ...\WindowsServer\Game\Saved\Config\Engine.ini(前提是这个 server 还没运行过,运行过就跳过这步),把默认地图和端口写进去:
[/Script/EngineSettings.GameMapsSettings]
ServerDefaultMap=/Game/Maps/Village/Village_Root.Village_Root
[URL]
Port=7777
[/Script/OnlineSubsystemUtils.OnlineBeaconHost]
ListenPort=15000
命令行启动
命令行能加更多自定义参数。Windows 示例:
"%PROJ_BINARY_DIR%\WindowsServer\MyProjectServer.exe" "/Game/Maps/AZ1/AZ1_01/AZ1_01_Root?listen" -log -debug -NoTimeouts -nosteam -server -messaging -MaxServerPlayers=25 -port=7777 BeaconPort=15000
几个参数的含义:
- 地图路径
/Game/Maps/.../AZ1_01_Root?listen:?listen表示以监听服务器方式启动;路径只是 pak 内的逻辑映射,不必纠结物理位置。 -port:服务器连接端口,一般用 UDP。要测 UDP 端口是否通,可以用nc工具。BeaconPort:监听(beacon)端口。-lan:不接入服务发现时用;如果接 Consul 之类的服务发现,改用wan=<ip>参数。
Linux 版只是把启动目标换成 MyProjectServer.sh,参数一致。
四、连接前的准备
防火墙:打开 Windows Defender 防火墙,在入站规则里新建规则放行 DS 端口(出站规则同理)。DS 一般走 UDP,但保险起见 TCP 也建一条同样的规则。
服务发现(可选):如果用 Consul,启动顺序是先起 Consul 再起 DS,然后可以用它实时看已启动 DS 的状态。接 Consul 大致要改三处:相关 C++ 会话逻辑(由程序改)、DefaultS6Platform.ini 里的 [Consul] Url="http://<ip>:8500"、以及启动命令行里的 wan 参数。
五、自动部署 DS 到 Linux(Jenkins + SSH)
下面是用 Jenkins Pipeline 把打好的 DS 包推到 Linux(本地 Rocky Linux 或 AWS EC2)的通用化 Groovy。建议先把 DS 打成 zip 再传:
"C:\\Program Files\\7-Zip\\7z.exe" a -tzip ${params.Platform}.zip ${StagedBuilds}
配置 SSH
从 Windows 连 Linux,先生成密钥:
ssh-keygen
# 本地 Linux
GVars.PEM = "C:\\Users\\<user>\\.ssh\\id_rsa_linuxserver"
GVars.LPEM = "/c/Users/<user>/.ssh/id_rsa_linuxserver"
连 AWS 用从 AWS 拿到的 PEM:
GVars.PEM = "E:\\<aws-key>.pem"
GVars.LPEM = "/e/<aws-key>.pem"
检查是否已有 DS 在跑
// Jenkins Pipeline
def remote_check_proc(){
String cmd = """ssh -i ${GVars.PEM} ${GVars.DSUser}@${GVars.DSIP} pgrep -f "${GVars.DSName}" """
println(cmd)
Boolean cmdstatus = false
catchError(buildResult: 'SUCCESS', stageResult: 'SUCCESS') {
def processIds = powershell(script: cmd, returnStdout: true).trim()
processIds.split().each { pid ->
echo "Found running ${GVars.DSName} with PID $pid"
}
cmdstatus = true
}
currentBuild.result = 'SUCCESS'
if(!cmdstatus){
println("No ${GVars.DSName} on ${GVars.DSIP}")
}
return cmdstatus
}
杀掉正在跑的 DS
# 本地 Rocky Linux(需要 sudo 密码)
ssh -i ${PEM_Path} ${GVars.DSUser}@${GVars.DSIP} "printf '<sudo-pass>' | sudo -S pkill -f ${GVars.DSName}"
# AWS EC2
ssh -i ${PEM_Path} ${GVars.DSUser}@${GVars.DSIP} "sudo -S pkill -f ${GVars.DSName}"
上传并解压
用 rsync 传,断点续传更稳:--partial 保留中断时的半成品文件;--partial-dir=DIR 把半成品放单独目录,保持目标目录干净;--append / --append-verify 对持续增长的文件追加(后者会先校验已有数据)。
# 上传
"C:\\Program Files\\Git\\bin\\bash.exe" -c "rsync -avz --partial --progress -e 'ssh -i ${GVars.LPEM}' ${LinuxStagedBuilds}/${params.Platform}.zip ${GVars.DSUser}@${GVars.DSIP}:${GVars.DSDir}"
# 解压并赋可执行权限
ssh -i ${GVars.PEM} ${GVars.DSUser}@${GVars.DSIP} "rm -rf ${GVars.DSDir}${params.Platform}"
ssh -i ${GVars.PEM} ${GVars.DSUser}@${GVars.DSIP} "unzip -o ${GVars.DSDir}${params.Platform}.zip -d ${GVars.DSDir}"
ssh -i ${GVars.PEM} ${GVars.DSUser}@${GVars.DSIP} "chmod +x ${GVars.DSDir}${params.Platform}/${GVars.StartDSScript}"
启动
启动前确认端口(TCP/UDP)已开;AWS 上要先向 IT 申请端口放行(默认常用 7777、7778、7779、1985)。用 nohup ... & 后台启动,日志重定向到文件:
ssh -i ${GVars.PEM} ${GVars.DSUser}@${GVars.DSIP} "nohup ${GVars.DSDir}${params.Platform}/${GVars.StartDSScript} ${GameMap}?MaxPlayers=${params.MaxPlayers}?ip=127.0.0.1?PublicIP=<ds-ip> -server -port=${BasePort} > ${GVars.DSDir}Logs/ds_${GameMap}_${FullDate}.log 2>&1 &"
这里 PublicIP 填 DS 对外的公网/局域网 IP(脚本里用 GVars.DSIP 变量代入),客户端按这个地址连入。