AzerothCore学习笔记·架构01:双进程架构——Auth 和 World 为什么是两个服务

你在《魔兽世界》里输入账号密码,点登录,屏幕闪了一下,你看到了服务器列表。选服、进入,角色出现在暴风城。

这三步背后,客户端和两个服务器程序打了两次交道——它们监听不同的端口、加载不同的数据库、运行不同的主循环。一个是 authserver,一个是 worldserver。

如果你打开 AzerothCore 编译后的 bin/ 目录,会看到这两个可执行文件。为什么必须是两个,不是一个?答案要从两份 Main.cpp 的代码差异说起。
在这里插入图片描述


两份 Main.cpp 的核心差异

src/server/apps/authserver/Main.cppsrc/server/apps/worldserver/Main.cpp 并排放在一起,差异一目了然。

Auth Server 的 StartDB()

bool StartDB()
{
    DatabaseLoader loader("server.authserver");
    loader
        .AddDatabase(LoginDatabase, "Login");
    // 只加载 LoginDatabase(即 Auth 库)
}

World Server 的 StartDB()

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.cppmain() 里:

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.cppmain() 里:

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 的代码里根本没有 CharacterDatabaseWorldDatabase 这两个对象。它只认 LoginDatabase

如果把两个进程合并,要么:

  • World Server 的代码要搬到 Auth 里(但 Auth 不需要这些逻辑)
  • 或者两个进程还是分开跑,只是打包成一个可执行文件(那还不如不合并)

2. 主循环模型不同

World Server 有一个游戏主循环 WorldUpdateLoop()

while (!World::IsStopped())
{
    sWorld->Update(diff);
    // diff 是两帧之间的时间差(毫秒)
}

这个循环是 World Server 的心跳:每隔几毫秒,更新全服所有 Entity 的状态(玩家移动、怪物AI、技能冷却、副本计时……)。

Auth Server 没有这个循环。它的 main() 最后一行是:

ioContext->run();  // 事件驱动,有连接来了才处理

Auth Server 是纯事件驱动的:客户端连上来 → 处理登录 → 返回 Realm 列表 → 断开。没有「持续更新游戏世界」的需求。

3. 线程模型不同

World Server 支持多线程:

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(冻结检测器):

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.cppLoadRealmInfo()

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 表:

// 启动时设自己为「在线」
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 的对象都没有,从编译层面就杜绝了越权访问的可能性。

下次点开客户端登录时,你可以想一下:不到三秒的登录过程,背后是两个独立进程协作完成的。一个验证你的身份然后把接力棒交出去,另一个接过棒子、加载你角色所在的整片天地。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

文韬博宇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值