你在《魔兽世界》里输入账号密码,点登录,屏幕闪了一下,你看到了服务器列表。选服、进入,角色出现在暴风城。
这三步背后,客户端和两个服务器程序打了两次交道------它们监听不同的端口、加载不同的数据库、运行不同的主循环。一个是 authserver,一个是 worldserver。
如果你打开 AzerothCore 编译后的 bin/ 目录,会看到这两个可执行文件。为什么必须是两个,不是一个?答案要从两份 Main.cpp 的代码差异说起。

两份 Main.cpp 的核心差异
把 src/server/apps/authserver/Main.cpp 和 src/server/apps/worldserver/Main.cpp 并排放在一起,差异一目了然。
Auth Server 的 StartDB():
cpp
bool StartDB()
{
DatabaseLoader loader("server.authserver");
loader
.AddDatabase(LoginDatabase, "Login");
// 只加载 LoginDatabase(即 Auth 库)
}
World Server 的 StartDB():
cpp
bool StartDB()
{
DatabaseLoader loader("server.worldserver", ...);
loader
.AddDatabase(LoginDatabase, "Login")
.AddDatabase(CharacterDatabase, "Character")
.AddDatabase(WorldDatabase, "World");
// 加载全部三个库
}
Auth Server 只连 Auth 库,World Server 连全部三个库。这不是配置问题,是代码层面就写死了的。
登录流程:代码视角
玩家点击「登录」后,客户端做了两次连接,对应两份代码:
第一次连接:Auth Server
authserver/Main.cpp 的 main() 里:
cpp
int32 port = sConfigMgr->GetOption<int32>("RealmServerPort", 3724);
sAuthSocketMgr.StartNetwork(*ioContext, bindIp, port);
Auth Server 启动后,只做一件事:监听 3724 端口,等待客户端连接。
连接建立后,AuthSocketMgr 处理登录请求:验证账号密码(SRP6 协议,密码不以明文传输),查询 realmlist 表返回可用服务器列表。
验证完成后,Auth Server 不断开连接------客户端自己断开,去连 World Server。
第二次连接:World Server
worldserver/Main.cpp 的 main() 里:
cpp
uint16 worldPort = uint16(sWorld->getIntConfig(CONFIG_PORT_WORLD));
sWorldSocketMgr.StartWorldNetwork(*ioContext, worldListener, worldPort, networkThreads);
World Server 监听 8085 端口。客户端带着 Auth Server 签发的 Session Token 来连接,World Server 验证 Token 合法后,允许进入游戏。
为什么不合并:代码层面的五个原因
1. 数据库访问权限不同
Auth Server 的代码里根本没有 CharacterDatabase 和 WorldDatabase 这两个对象 。它只认 LoginDatabase。
如果把两个进程合并,要么:
- World Server 的代码要搬到 Auth 里(但 Auth 不需要这些逻辑)
- 或者两个进程还是分开跑,只是打包成一个可执行文件(那还不如不合并)
2. 主循环模型不同
World Server 有一个游戏主循环 WorldUpdateLoop():
cpp
while (!World::IsStopped())
{
sWorld->Update(diff);
// diff 是两帧之间的时间差(毫秒)
}
这个循环是 World Server 的心跳:每隔几毫秒,更新全服所有 Entity 的状态(玩家移动、怪物AI、技能冷却、副本计时......)。
Auth Server 没有这个循环 。它的 main() 最后一行是:
cpp
ioContext->run(); // 事件驱动,有连接来了才处理
Auth Server 是纯事件驱动的:客户端连上来 → 处理登录 → 返回 Realm 列表 → 断开。没有「持续更新游戏世界」的需求。
3. 线程模型不同
World Server 支持多线程:
cpp
int numThreads = sConfigMgr->GetOption<int32>("ThreadPool", 2);
for (int i = 0; i < numThreads; ++i)
{
threadPool->push_back(std::thread([ioContext]()
{
ioContext->run();
}));
}
Auth Server 是单线程的(代码注释里写了:NOTE: While authserver is singlethreaded you should keep synch_threads == 1.)------因为登录请求量相对小,单线程足够。
4. 故障隔离
World Server 有一个 FreezeDetector(冻结检测器):
cpp
if (msTimeDiff > freezeDetector->_maxCoreStuckTimeInMs)
{
LOG_ERROR("server.worldserver", "World Thread hangs for {} ms, forcing a crash!", msTimeDiff);
ABORT("World Thread hangs for {} ms, forcing a crash!", msTimeDiff);
}
如果 World 主循环卡住超过设定时间,直接主动崩溃(crash),让守护进程重启它。
Auth Server 没有这个机制。如果 Auth 卡住了,已经在游戏里的玩家不受影响------因为他们的连接是跟 World Server 建立的,跟 Auth 无关。
5. 横向扩展方式不同
一个 Auth Server 可以对应多个 World Server。看 worldserver/Main.cpp 的 LoadRealmInfo():
cpp
QueryResult result = LoginDatabase.Query(
"SELECT id, name, address, port FROM realmlist WHERE id = {}",
realm.Id.Realm
);
每个 World Server 进程只加载自己的 realm.Id.Realm(从配置文件里读)。多个 World Server 可以连同一个 Auth 库,共享同一份 realmlist 表。
这种架构下,Auth 可以只部署一个,World 可以按 Realm 横向扩展。
进程间通信:几乎没有
Auth 和 World 之间没有直接通信。
严格来说,World Server 启动时会修改 realmlist 表:
cpp
// 启动时设自己为「在线」
LoginDatabase.DirectExecute(
"UPDATE realmlist SET flag = flag & ~{} WHERE id = '{}'",
REALM_FLAG_VERSION_MISMATCH, realm.Id.Realm
);
// 关闭时设自己为「离线」
LoginDatabase.DirectExecute(
"UPDATE realmlist SET flag = flag | {} WHERE id = '{}'",
REALM_FLAG_OFFLINE, realm.Id.Realm
);
但这是写数据库 ,不是进程间直接通信。Auth Server 读 realmlist 表来获取 World 的状态,中间隔着数据库这一层。
这种「无直接通信」设计的好处:Auth 和 World 可以部署在不同的机器上,只要它们能访问同一个 Auth 数据库。
回到开头:为什么是两个进程
答案已经从代码里找到了:
- 数据库访问:Auth 只连 Auth 库,World 连全部三个库,代码层面就分开了
- 主循环:World 有游戏主循环,Auth 是纯事件驱动,模型不同
- 线程模型:World 支持线程池,Auth 是单线程
- 故障隔离:World 有冻结检测主动崩溃重启,Auth 崩了不影响已在线玩家
- 横向扩展:Auth 可以只部署一个,World 可以按 Realm 横向扩展
这套设计不是 AzerothCore 独创的,而是从 MaNGOS 时代就定下来的------但 AzerothCore 的代码把「职责分离」做得更彻底了:Auth Server 的 StartDB() 里连 CharacterDatabase 的对象都没有,从编译层面就杜绝了越权访问的可能性。
下次点开客户端登录时,你可以想一下:不到三秒的登录过程,背后是两个独立进程协作完成的。一个验证你的身份然后把接力棒交出去,另一个接过棒子、加载你角色所在的整片天地。