基于Qt的泰拉开服器-TmodServerHelperPlus

起风了 Lv3

开发背景

我开有长期游戏服务器,初衷就是看到一些学生党和工作党闲余时间想一起联机,但一般的游戏联机都比较麻烦,因此想提供一个供大家游玩的服务器,起初是由我室友提供的资金,后来是群友集资,我来作为代理腐竹维护服务器,目前开有 Minecraft 和 Terraria。

可以说目前主流的联机游戏,我几乎都开过,但是时间久了,就会觉得很多不方便之处:

  1. 有些游戏的服务端开服步骤麻烦,要选择一些不适用的选项,比如是否启用steam服务器,我自己都开服了还选他干嘛捏,所以我需要能自动一键开服的功能。

  2. 游戏的服务端时常因为模组的问题崩溃,而我也不可能时时刻刻守在一边,虽然可以手机远程连接桌面进行重启,但也不是时刻都有时间,所以需要崩溃自动重启的功能。

  3. 大部分服务端都要同时开模组,模组是原版游戏的拓展,可以大大丰富游戏的内容,而模组也提供了可以配置的文件,一般都是json格式,对我来说一个一个打开配置文件修改保存过于繁琐,希望能像excel修改表格一样清晰易操作。

  4. 开服也需要定义生成世界的各种环境,因此需要快捷的定义功能。

作为试做,我以Terraria的Tmodoader Server服务端为目标,针对该服务端开发了一款泰拉瑞亚服务端运维桌面平台。

需求分析

分析

核心是,解决开发背景里提到的4个核心问题。

需求

开发泰拉服务器服务端运维平台

  1. 实现服务端的一键启动、停止、强制停止、重启功能。
  2. 实现服务端的崩溃自动重启功能。
  3. 实现服务端配置便捷修改功能。

技术选型

由于是适用于Windows端的运维平台,且关乎服务端运行,因此选择占用尽可能小、效率尽可能高的技术,Qt + C++完美满足了上述需求。

Qt(开发UI)+ C++

软件界面设计

软件界面采用 Qt Designer 设计。
图标采用 Photoshop(PS)设计。

Logo设计

共设计设计了两版logo,第一版是这样子的:

但是最终没有采用,因为软件UI是上面这样的,两者风格不搭,一个现代化风格的logo配上陈年老酿GUI,想想还是算了,于是又重新设计一版:

这样两个复古风格的就能玩到一块去了。

至于为什么要设计成这种类齿轮的样式呢,请容我娓娓道来:

首先这个软件是专门为 tModLoader 服务端做适配的(PS:后面为了方便简称 Tmod),那么 Tmod 的图标是长这样的:

看到没,它的图标就是个齿轮,意思也很容易猜,因为 Tmod 是 Terraria (泰拉瑞亚)这款游戏的模组加载器,只要你玩模组,必须通过 Tmod,这个齿轮的意思显然就是对 Terraria 的修改拓展的意思,而我设计的 logo,外层是齿轮,内部有个手摇曲柄,手摇曲柄可以给齿轮施加外力,让它转得更快。这不就对应着开发这款软件的目的嘛——高效管理 Tmod 服务端!

软件界面设计

主界面

该界面包含了管理一个服务端的基础功能,启动、关闭、重启、崩溃自动重启。

左侧终端信息可以显示服务端输出,方便查看服务器状态。下面可以显示当前所开的存档世界的基础信息,旁边是显示当前服务器里的玩家。

右边,指定tmod目录按钮是为了获取到tmod目录,下面是当前tmod检测状态,没检测到服务端程序就会显示红色,再下面就是服务端基础管理功能了。

服务端配置界面

这是开服配置界面,正常情况下,用服务端直接开服的话,这下面的选项要一个一个选,还是在控制台窗口,有多麻烦可想而知。

模组配置界面

用于快捷修改模组的配置。

核心功能及难点实现

启动服务器

1
2
3
4
5
6
7
8
9
10
11
12
void TmodServerHelperPlus::OnStartServerButtonClicked()
{
ui.logPlainText->clear();
OnStopServerButtonClicked();
QString box = tmodServerPath + "/LaunchUtils/busybox64.exe";
QStringList arguments;
arguments << "sh" << tmodServerPath + "/start-tModLoaderServer.sh" << "-nosteam";
//ui.logPlainText->appendPlainText("开始启动");

tmodServerProcess->start(box, arguments, QProcess::Unbuffered | QIODevice::ReadOnly);
active = true;
}

停止服务器

1
2
3
4
5
6
7
void TmodServerHelperPlus::OnStopServerButtonClicked()
{
active = false;
tmodServerProcess->kill();
//tmodServerProcess->terminate();
KillCustomProcess(tmodServerChildProcessName);
}

强制关闭服务器

1
2
3
4
5
6
7
8
9
void TmodServerHelperPlus::OnForceStopServerButtonClicked()
{
active = false;
//tmodServerProcess->terminate();
tmodServerProcess->kill();
//tmodServerProcess->close();
KillCustomProcess(tmodServerChildProcessName);
ui.logPlainText->appendPlainText("已强制终止服务器");
}

强制关闭指定进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
void TmodServerHelperPlus::KillCustomProcess(QString processName)
{
HANDLE hProcessSnap;
PROCESSENTRY32 pe32;

ui.logPlainText->appendPlainText("开始查杀 " + processName + " 进程");
// Take a snapshot of all processes in the system
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE) {
ui.logPlainText->appendPlainText("获取系统进程快照失败");
return;
}

pe32.dwSize = sizeof(PROCESSENTRY32);

// Retrieve information about the first process
if (!Process32First(hProcessSnap, &pe32)) {
ui.logPlainText->appendPlainText("无法检索第一个进程");
CloseHandle(hProcessSnap);
return;
}

// Iterate through the processes
do {
// Convert process name to QString for comparison
QString exeFile = QString::fromWCharArray(pe32.szExeFile);
if (exeFile.compare(processName, Qt::CaseInsensitive) == 0) {
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pe32.th32ProcessID);
if (hProcess != NULL) {
// 终止进程
if (TerminateProcess(hProcess, 0)) {
ui.logPlainText->appendPlainText("已终止进程 : " + exeFile);
}
else {
ui.logPlainText->appendPlainText("终止进程失败 : " + exeFile);
}
CloseHandle(hProcess);
}
else {
ui.logPlainText->appendPlainText("打开进程失败");
}
break;
}
} while (Process32Next(hProcessSnap, &pe32));

}

监控输出 & 监控进程

这段是为了实现获取玩家在线情况以及崩溃重启,原理是监控每一句输出,检测到关键词就记录,比如玩家进入游戏会有“playerID has joined”,那么检测“has joined”并且记录前面的玩家id,这就意味着该玩家进入了服务器,同理“playerID has disconnected”就是玩家退出。同理崩溃的时候会有相关崩溃信息,检测到以后就执行自动重启(关闭再启动)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
void TmodServerHelperPlus::Slot_ReadyReadOutput()
{
/*QByteArray qba = tmodServerProcess->readAllStandardOutput();
QString str = qba;
ui.logPlainText->insertPlainText(str);*/

if (ui.logPlainText->blockCount() > 300)
{
ui.logPlainText->clear();
}

QByteArray output = tmodServerProcess->readAllStandardOutput();
//ui.logPlainText->appendPlainText(output);
QString outputString(output);
QStringList outputStringList = outputString.split("\n");
bool isCrash = false;
for (QString crashString : crashStringList)
{
if (outputString.contains(crashString))
{
isCrash = true;
break;
}
}
QFont font;
font.setFamily("微软雅黑");
font.setPointSize(10);

for (QString s : outputStringList)
{
QString info = s.trimmed();
if (!info.isEmpty())
{
ui.logPlainText->appendPlainText(info);
if (info.contains("is connecting"))
{
playerIP = (info.split(" "))[0];
}
if (info.contains("has joined"))
{
playerSteamID = (info.split(" "))[0];
if (!playerIP.isEmpty() && !playerSteamID.isEmpty())
{
QTableWidgetItem* ip = new QTableWidgetItem(playerIP);
QTableWidgetItem* id = new QTableWidgetItem(playerSteamID);
ip->setFont(font);
id->setFont(font);
ip->setTextAlignment(Qt::AlignCenter);
id->setTextAlignment(Qt::AlignCenter);
int rowCount = ui.currentPlayersTableWidget->rowCount();
ui.currentPlayersTableWidget->insertRow(rowCount);
ui.currentPlayersTableWidget->setItem(rowCount, 0, ip);
ui.currentPlayersTableWidget->setItem(rowCount, 1, id);
playerIP = "";
playerSteamID = "";
}
}
else if (info.contains("has left"))
{
int rowCount = ui.currentPlayersTableWidget->rowCount();
for (int i = 0; i < rowCount; ++i)
{
QTableWidgetItem* item = ui.currentPlayersTableWidget->item(i, 1);
if (item)
{
if (item->text() == (info.split(" "))[0])
{
ui.currentPlayersTableWidget->removeRow(i);
}
}

}
}
}

}
if (isCrash)
{
Slot_RestartServer();
return;
}
}

效益

极大方便腐竹运维游戏服务端。

项目演示

  • 标题: 基于Qt的泰拉开服器-TmodServerHelperPlus
  • 作者: 起风了
  • 创建于 : 2024-11-07 09:09:42
  • 更新于 : 2026-09-25 23:20:08
  • 链接: https://www.wangcac.me/2024/11/07/基于Qt的泰拉开服器-TmodServerHelperPlus/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论