项目地址: [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 链路在驱动里是实打实跑通的,主流程如下:
- 驱动内部开了一个独立的 gateway 端口,不再和传统的 telnet/websocket 混用。
- Go Gateway 连上来,用定好的协议发
hello、login、data等指令。 - 驱动收到
login后,会按cid创建一个虚拟 interactive 会话。 - 🔥 【重点】 这个会话的
fd会被置为-1!这意味着在 LPC 逻辑里它是个完整的玩家对象,但在物理上,它压根不是个对外暴露的 socket。 - 驱动复用老一套登录链:
master->connect()返回对象,然后调对象的gateway_logon(data)。 - 玩家后续的操作输入,全部进入
gateway_receive(mixed data)。 - 至于游戏内的输出(比如
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 的老哥们的实战经验:
- 你们线上真实碰到的瓶颈,到底更多是在 IO、内存,还是纯纯的 mudlib 脏代码上?
- 如果要给手头的老项目做现代化改造,你最想先拆掉哪一层?
- 对“网关托管连接 + FluffOS 专注逻辑”这套路子,你们觉得最大的工程风险会出在哪?
欢迎交流:279631638