[架构分享] 彻底告别 IO 阻塞!“零侵入”的 基于FluffOS 高并发网关开发!

一次对 MUD 底层的“大手术”:我把 IO 从 FluffOS 主线程里拆了出去

项目地址: [GitHub]

下载地址: [driver.exe]


核心总结 把接入层生生从 FluffOS 里剥离出来,让 Go Gateway 去扛脏活累活(网络 IO),让 FluffOS 回去专心跑它的游戏逻辑。

这段时间闭关,我结合 AI,对 MUD 的底层架构做了一次比较大的“手术”。这不是那种修修小 bug、补个新功能的常规迭代,也不是在 FluffOS 外面随便套个端口转发器,而是真正去动了一个陈年*病的根部:传统 FluffOS 的网络 IO、协议处理、连接生命周期和游戏逻辑,全特么挤在同一个主线程里。**

这个模型早年确实没毛病,简单直接,经典 MUD 都是这么跑起来的。但放到现在,只要在线人数上来、网络稍微一抖、多端接入开始复杂,再加上各种脚本和公网上的脏流量,这套单线程扛一切的代价就太明显了。

我这里 Gateway 是用 Go 撸的,但思路不绑死语言。你用 Rust、Python、Node.js 理论上都能跑。选 Go 纯粹是因为在这类高并发网络 IO、部署和开发效率上,它综合下来最顺手。

在往下聊细节之前,咱得先把几个核心边界说透,免得后面一激动,把“未来规划”当成“已经落地的牛逼”给吹出去了:

  • 🛡️ 边界一:治的是“接入层压力”,不是包治百病。 上了网关不代表 FluffOS 所有的性能瓶颈就蒸发了。如果你卡在 mudlib 算法太烂、对象过于庞大、heart_beat 过于密集、高频 call_out 死循环,那你还是得老老实实去治逻辑层的病。
  • 📦 边界二:目前的底层协议是 4 字节大端长度头 + JSON payload 没上 MsgPack。不是不能上,我也觉得以后该上,但现阶段我不想把没干完的事拿出来吹。先把 JSON 跑通,方便抓包排障才是王道。
  • ⚙️ 边界三:部署形态兼容。 单机 sidecar 模式下,Gateway 和驱动目前保持“单主连接多会话”,这对驱动最友好。但底层机制上,它是完全支持一个 Gateway 实例用多个主连接来承载海量会话的。
  • 🔌 边界四:低侵入,但不是“绝对零改动”。 你的战斗、任务、地图、门派几乎一行不用改。但你需要一次性对登录入口和接入层做点适配,补齐 gateway_logon()gateway_receive() 等几个关键接口。

如果你能接受上面这四个前提,那咱们接着往下看,干货全在后面。


一、为什么非要动这台“手术”?

搞过 MUD 的兄弟都知道,FluffOS 那套对象模型、状态机、房间角色关系,放在文字游戏领域依然是极其高效的。但它有个致命伤,越是长线运营的项目越要命:它太容易把“网络接入问题”放大成“全服卡顿问题”。

传统直连下,玩家一根网线直接扎进驱动:

  • 一个玩家,一条物理 TCP 连接。
  • 网络一抖,就是一波断开和重连。
  • Telnet、WebSocket、粘包拆包、甚至恶意的端口扫描,全都在驱动脸皮子底下发生。

结果呢? 驱动主线程频繁被底层的 TCP 杂务打断。协议层的脏活越多,留给 LPC 的时间片和心跳就被无情挤压。很多时候大家觉得卡,第一反应是“是不是逻辑写太重了”,其实真不一定,很多时候是驱动被外面的网络风暴给拖垮了。

所以我这次没搞局部优化,而是直接切断职责:“谁来面对公网连接”这个包袱,FluffOS 你别背了,交给 Gateway。你只管跑规则和状态。


二、改造之后,架构变成了什么样?

现在的推荐拓扑,客户端不再直接碰驱动了,而是这样:

客户端(Telnet / WebSocket / HTTP / App)
                │
                ▼
        Go Gateway (接入层)
  - 持有所有公网物理连接
  - 协议适配 / TLS
  - 心跳维持 / 限流防刷 / 流量清洗
  - 会话托管
                │
                ▼
Gateway 主连接(单主/多主,按需扩展)
  协议: [4-byte length][JSON payload]
                │
                ▼
         FluffOS Driver (逻辑层)
  - 创建虚拟 interactive
  - 接入 gateway_logon / gateway_receive
  - 专注执行 LPC 游戏逻辑

结合下面这两张图,看得很清楚:

架构概览图 数据流示意图

不管你是同机部署还是局域网独立部署,核心就一条:公网连接,再也砸不到 FluffOS 身上了。


三、驱动内部到底发生了什么(硬核部分)

这块我得写清楚,免得被当成“套壳忽悠”。目前的 Gateway 链路在驱动里是实打实跑通的,主流程如下:

  1. 驱动内部开了一个独立的 gateway 端口,不再和传统的 telnet/websocket 混用。
  2. Go Gateway 连上来,用定好的协议发 hellologindata 等指令。
  3. 驱动收到 login 后,会按 cid 创建一个虚拟 interactive 会话
  4. 🔥 【重点】 这个会话的 fd 会被置为 -1!这意味着在 LPC 逻辑里它是个完整的玩家对象,但在物理上,它压根不是个对外暴露的 socket。
  5. 驱动复用老一套登录链:master->connect() 返回对象,然后调对象的 gateway_logon(data)
  6. 玩家后续的操作输入,全部进入 gateway_receive(mixed data)
  7. 至于游戏内的输出(比如 write()tell_object()),会被驱动底层自动拦截,打包成 Gateway 协议包,扔回给 Go 网关去下发。

也就是说,我没有抛弃 FluffOS 的原生对象体系去搞什么“伪玩家模型”,而是保留了原生链路,把物理连接给虚拟化了。你如果想继续兼容纯文本的老命令,完全可以在 gateway_receive 里自己写个转发,丰俭由人。


四、这套架构解决了什么痛点?

1. 接入层压力彻底卸载

以前 1000 人在线,驱动扛 1000 个 FD,各种系统调用和异常包全糊在主线程脸上。现在,驱动眼里只有少数几个极度稳定的 Gateway 主连接,外加一堆虚拟玩家。没有杂务打扰,单线程跑得飞起。

2. 协议收口,结构化交互

目前主链用的是 [4-byte big-endian length][JSON payload]。现阶段用 JSON,图的就是好抓包、好排障、多端对接门槛低。先把接入层和逻辑层分干净,把 LPC 和 mixed 映射接通。等这套彻底稳了,再去换 MsgPack 压榨极限性能,这才是合理的工程演进。

3. 网络与逻辑真正的物理隔离

这是我觉得最值钱的地方。过去 interactive = 公网 socket,现在不是了。真实 IP、端口这些只作为元数据挂在虚拟会话上。Gateway 是纯正的前置接入层,FluffOS 成了纯正的后端逻辑引擎。这是在保护老引擎,不是在削弱它。


五、关于性能和稳定性,我说点实在的

目前在单机内网压测环境下,表现相当恐怖:

QPS:68 万 \~ 86 万 延迟:微秒级

但我不想拿着这个数字乱吹。各位都是懂行的,这是纯净内网、没有复杂落盘和重度业务开销测出来的极限值。上了真业务肯定会有折损。但它至少证明了一点:网络 IO 绝对不会再成为你系统的瓶颈。

更重要的是安全和稳定性的提升。以前驱动天天在公网裸奔,现在各种端口扫描、畸形包、连接风暴、CC 攻击,全被前置的 Go Gateway 吸收了。TLS 解析、封禁限流都在网关解决。系统老是被压垮,往往不是因为玩家多,而是被公网脏流量折磨太久了。

💡 补充个经常被问的:Gateway 托管了,是不是能做到“驱动热重启不断线”? 答案是:基础已经打好了。物理连接在网关手里,只要网关做好会话缓存,配合 mudlib 的状态重放,是可以实现无感恢复的,但这需要你自己写策略,不是一键开启的魔法。


六、开发体验:UI 和逻辑终于分家了

以前写 UI 简直是受刑,天天在 LPC 里硬拼 ANSI 颜色码、终端排版。代码臃肿得没法看。

现在既然接入层独立了,LPC 就只需要负责产出“业务语义”。比如你想弹个窗:

// 这是业务层封装示意,不是驱动内置 efun
me->send_ui(([
    "title":   "系统提示",
    "content": "确认购买吗?",
    "btns":    ({
        (["name": "确认", "cmd": "ok"]),
        (["name": "取消", "cmd": "cancel"])
    })
]));

LPC 只负责告诉系统“我要让玩家确认购买”。至于这个弹窗在 Web 端用前端组件画,在 App 端用原生画,还是在老 Telnet 里用字符画,那是 Gateway 和各类客户端该操心的事。这种分层,如果你以后要搞多端互通,绝对是救命的。


七、写在最后

折腾完这一轮,我的判断越来越清晰: 不要再用 FluffOS 去硬扛接入层了。用 Go/Rust 把接入层接管掉,让 FluffOS 更纯粹、更稳定,继续承载复杂的大型 MUD 业务。

MUD 不是什么早该进博物馆的老古董,它只是长期缺少一次符合现代工程现实的底层重构而已。把该拆的拆了,你会发现很多以前痛苦的雷区,一下子就全顺了。

全当抛砖引玉,也想听听还在折腾线上 MUD 的老哥们的实战经验:

  1. 你们线上真实碰到的瓶颈,到底更多是在 IO、内存,还是纯纯的 mudlib 脏代码上?
  2. 如果要给手头的老项目做现代化改造,你最想先拆掉哪一层?
  3. 对“网关托管连接 + FluffOS 专注逻辑”这套路子,你们觉得最大的工程风险会出在哪?

欢迎交流:279631638

京ICP备13031296号-4