一、进程级架构与职责划分
Legend of Mir(Mir2)服务端不是单体程序,而是多进程协同系统,源码按组件拆工程:
- DBSvr(DBServer.exe):角色数据持久层,处理 GetHumanRcd / SaveHumanRcd / MakeNewItem,数据落 Mud2\DBSrv200\FDB\Hum.DB 或 HeroDB(DBC2000),内部端口 6000/7300,不对外暴露
- LoginSrv(LoginSrv.exe):账号认证,读 IDDB\ID.DB,分配游戏服入口,端口 5500
• LoginGate(LoginGate.exe):客户端第一入口,监听 7000,WSAAsyncSelect + 线程转发,只做加解密与路由
- SelGate(SelGate.exe):角色选择期网关,端口 7100/7101,从 DBSvr 拉角色列表
• RunGate / GameGate(RunGate.exe):游戏运行期操作枢纽,监听 7200/7300 等,转发移动/攻击/交易封包给 M2
- M2Server(GameSvr / Mir200\M2Server.exe):世界模拟核心,地图/怪物/NPC/战斗/脚本全在这
• LogServer(LogDataServer.exe):行为日志落地,端口 10000
进程间通信用 TCP 命名管道 + 自定义封包(Def/Protocol.h 定义 CM_/SM_/DB_/DBR_ 指令字),网关层屏蔽客户端直连 M2,逻辑层与网络层物理分离。
二、标准启动顺序与依赖链(源码级视角)
初始化顺序错一位就全红:
1. DBServer 先起 → 连 HeroDB / 打开 FDB,建监听套接字,等 LoginSrv/M2 来取数据
2. LoginSrv 起 → 读 ID.DB,连 DBSvr 校验通道,开 5500
3. LoginGate → 绑 7000,连 LoginSrv 5000/5500,等客户端
4. SelGate → 绑 7100,连 LoginSrv + DBSvr
5. RunGate(可多实例 7200/7300/7400…)→ 绑端口,等 M2 回连注册
6. M2Server 最后起 → 读 !Setup.txt、Envir、Map、MonGen、MerChant,初始化完成发“Gate 0 Opened / 服务器启动完成”,回连各 RunGate 建立会话
M2 若先于 DBSvr 起,角色加载阶段直接报“Cannot open database”或卡在等待 DBR_HUMAN_REC 回应;RunGate 先起则 M2 启动时报“Gate connection failed”。
三、M2Server 内部初始化流程(InitServer 主线)
WinMain → 读命令行/!Setup.txt → 初始化全局管理器:
1. 日志系统 InitLog → Mir200\Log\ 建当日文件
2. 加载配置 LoadConfig(!Setup.txt / !ServerInfo.txt / !AddrTable.txt)→ ServerName、IP、端口、经验倍率、爆率、存盘间隔入全局变量
3. 数据库通道 InitDBSvrSock → 连 DBServer 内部端口,发 DB_HELLO 握手
4. 地图系统 InitMap → 读 Envir\MapInfo.txt,逐个加载 Map\*.map 到 CMapMgr,解析格子阻挡/物件/区域触发
5. 数据表加载 LoadStdItems / LoadMonsterDB / LoadMagicDB → 读 StdItems.txt + StdItems.DB、Monster.txt + Monster.DB、Magic.txt + Magic.DB 进内存哈希表
6. 刷怪配置 LoadMonGen → 解析 MonGen.txt,建定时器,按坐标/数量/间隔注册刷新点
7. NPC/机器人加载 LoadMerChant → 解析 Envir\MerChant.txt,按“脚本名 地图号 X Y…”绑定 Market_Def\脚本名-地图号.txt;LoadRobot → Robot.txt + Robot_def\
8. 脚本引擎 InitScriptEngine → 预编译 QuestDiary / QFunction / QManage 标签树,语法错打行号
9. 沙巴克/行会/排行榜 InitCastle / InitGuild / InitTop → 读 Castle\SabukW.txt、GuildBase\Guilds、顶榜文件
10. 网络层 InitGateSocks → 等 RunGate 回连,建 CGateInfo 会话表;起主循环 Timer(通常 100-200ms 逻辑帧)
11. 控制台输出“服务器启动完成”,接受玩家接入
四、多线程架构与分工
老版 VC++6 / DELPHI 源码线程模型:
• 主线程:Windows 消息循环 + 逻辑帧 Timer(UpdateWorld:移动、战斗、AI、掉落、脚本触发)
- 网络线程(LoginGate / RunGate):WSAAsyncSelect 或 IOCP 完成端口(CheckAvailableIOCP 判断 NT/2000 后启用),收包入队列 → 主线程按帧消费;发包反向推送
- DBSvr 工作线程:ProcessDBMsg 循环,从套接字读 DB_ 消息 → 查/写 Hum.DB → 回 DBR_,每条角色存档带 GUID 锁防并发覆盖
• M2 怪物AI线程(部分版本/OpenMir2):MobThread 调度,按 OperateTime 差分更新,无锁链表遍历;老版多在主帧里串行跑
- 定时线程:存盘线程(默认 10-30min 写 FDB/SQL)、刷怪线程、机器人触发线程(Robot_def SEC/MIN/HOUR)
• 日志线程:LogServer 独立进程,M2 发 UDP/TCP 日志包,异步落盘不阻塞主帧
同步原语:全局对象表(g_HumanList / g_MonList / g_ItemList)用临界区/Monitor 保护;DB 回写用读写锁;网关会话 CGateInfo 用 Interlocked 计数。
五、封包流转示例(登录到进图)
客户端 → LoginGate:7000(CM_IDLOGIN)→ LoginSrv 验 ID.DB → DBSvr 取角色列表 → SelGate 展示 → 选角色进服 → RunGate:7200(CM_LOGIN 带角色名)→ M2 发 DB_LOADHUMAN → DBSvr 回 DBR_HUMAN_REC(_THUMANRCD:属性/装备/_TUSERITEMRCD/技能 _THUMANMAGICRCD)→ M2 构 CPlayerObject 入世界 → 下发 SM_LOGINOK + 地图数据 → 客户端进比奇
实时操作:玩家按方向键 → RunGate → CM_WALK → M2 校验地图阻挡/速度/负重 → 更新 CPlayerObject 坐标 → 广播 SM_GOODWALK 给视野内会话 → RunGate 下发客户端
六、核心数据结构(源码可见)
• CPlayerObject:移动/战斗/背包/魔法/状态机主体(ObjectPlayer.cpp)
• CCharObject:怪/人/NPC 公共基类,含地图坐标、视野、仇恨表
- _THUMANRCD / _TUSERITEMRCD / _THUMANMAGICRCD:DB 持久化结构,GUID 唯一标识物品
• CGateInfo:网关会话,含 SendBuf/RecvBuf/ConnState
- CMapCell:地图格子,bFlag 位标记可走/阻挡/传送/安全区
七、现代重构版(OpenMir2 / .NET / C#)差异点
架构同构但实现升级:DBSvr 改 EF Core + MySQL(mir2_db.sql 建表),网关仍分离可水平扩 RunGate 实例;主循环用 ChannelMessageHandler 消息队列替代裸 switch;锁细化到 AccountLock/LoadLock,加对象池、环形任务队列、增量存档;启动顺序仍严守 DBSvr → LoginSrv → Gate → GameSvr。
八、初始化失败硬排查点
• M2 起不来:HeroDB 别名 PATH 错、Envir\MapInfo.txt 地图号无对应 .map、MerChant.txt 字段错位、脚本 UTF-8 乱码、端口被占(netstat -ano 查 7000/7200)
- 卡在加载角色:DBSvr 没起或 !AddrTable.txt 没放行 M2 IP
• 进图黑屏:Map\*.map 与 MapInfo.txt 不一致、Objects.wil/Tiles.wil 缺帧
- 网关连不上:Config.ini 里 ListenAddr/IP/Port 与 M2 !Setup.txt 不一致
Legend of Mir 服务端初始化本质是一条“数据层先行 → 网关暴露端口 → 逻辑层最后拉起世界”的硬依赖链;多线程不在 M2 内卷并发,而是把网络、持久化、日志、定时全拆到独立进程/线程,主帧只跑世界模拟,这是它能撑百人在线且不炸的核心原因。
Legend of Mir服务端源码拆解:初始化流程与多线程网关架构全链路
来源:
作者:
点击:

