语言: C++ 和 Lua 逻辑粘合剂。
如果使用正确的 LuaJIT 版本,该代码已经过测试,可以在 Ubuntu x86、Ubuntu x86_64 和 Gentoo x86_64 上编译和运行。对 libjansson 进行了修改,以删除在整数完成后用 .0 强制格式化浮点数,以正确支持“整数”,并且仍然具有从仅支持双精度的 Lua 的自动转换。
鉴于 libjansson 和 google-glog 是手动构建的,它还可以在 Ubuntu 12.04 LTS x86_64 上编译。
LuaJIT
利布夫
evhttpclient
谷歌-glog
google-perftools (tcmalloc) -可选,仅对源代码和 makefile 进行少量修改
利布扬松
赫里迪斯
libicu - 查看本地包管理器
curl - 查看本地包管理器
boost -如果您用自己的东西替换侵入性指针,则可选。
可能需要修改 src/Makefile 以反映您的安装文件夹。欢迎更强大的构建系统,但在编写项目时并不需要。 (CMake?)
我(@zwagoth)在这里学到了一些东西:在撰写本文时始终发表评论。为了弥补这一点,我将记录该程序的基本结构以帮助理解。
对于代码库的混乱表示歉意。这也是我的第一个任何规模和复杂性的 C++ 项目之一。
包含协议消息、RTB消息以及连接、断开等各种基本事件回调的消息处理的几乎所有核心逻辑。
该文件包含匿名函数表,这些函数以它们处理的事件命名。该文件不应存储任何状态,仅被视为逻辑存储区域。该文件的基本设计是允许胶合逻辑,而不强迫 Lua 做任何繁重的工作,并最大限度地减少 Lua 传入和传出的数据量,但代价是对 C++ 进行更多函数调用。
全局文件命名空间中注入了四个表,它们的名称都很短,以便于输入:
u
:连接/字符(用户)函数
s
:服务器/全局状态函数
c
: 通道状态函数
const
:错误 ID 和常量值
协议回调函数接受两个参数:与命令关联的连接以及所提供的 json 参数的表副本。
连接是不透明的数字,不应以任何方式修改。更改连接的值是不安全的。你已被警告过。
聊天守护程序启动和操作期间使用的配置变量的 lua 文件。
简约的闪存策略服务器。如果您需要支持 Flash,那么您就可以运行它。根据您的需求自定义内嵌策略。
程序入口点。处理后台线程和curl 的初始化。
做的事情太多了。代码流从Server::run()
开始。
连接流程如下:
listenCallback handshakeCallback connectionWriteCallback connectionReadCallback connectionwriteCallback
listenCallback
设置每个连接事件处理程序并将流程传递到...
handshakeCallback
,处理 websocket 握手的读取。
connectionWriteCallback
在连接准备好写入时进行处理,并在队列中存在要写入连接的项目时启用。处理缓冲。
当握手阶段结束时, connectionReadCallback
处理所有读取事件。所有协议解析都发生在这里,并且命令是从该函数内部调度和运行的。
pingCallback
处理向客户端发送 ping 事件。如果协议发生变化,这应该是首先要做的事情之一。
connectionTimerCallback
会定期触发并检查连接是否已失效并清理它。
runLuaEvent
是纽特魔法。也是每个命令发生魔法的地方。这是每个命令从 C++ 代码转换为 Lua 代码的地方。处理所有 Lua 状态和回调,以确保 Lua 不会陷入无限循环。处理 Lua 内部的打印错误。
该文件存储与频道、连接、禁止和审核相关的所有状态数据。
连接分为已识别和未识别的连接,因为在登录服务器验证它们存在之前,字符名称是未知的,并将它们排除在可按名称查找的字符池之外。
处理状态保存和恢复到磁盘。
作为后台线程运行,处理登录请求和回复并将它们传递回主线程。
序列化登录系统,使用全局登录队列。这可以改进很多。
转换为curl_multi 会很好,但是涉及到与libev 的一些有趣的交互或大量的盲目轮询。由于线程的原因,队列在被访问之前必须被锁定。
基本通道类,在通道的生命周期内维护有关通道的状态数据。所有与通道相关的低级操作都通过 Lua 中的钩子发生在该文件中。通常包装在侵入式指针中来管理实例生命周期。
这就是 json 通道的序列化和反序列化发生的地方。
处理所有连接网络和调试 Lua 状态。
维护扭结列表、状态、状态消息和性别。
维护忽略和好友列表。
每个连接的句柄节流阀。
这是输出数据缓冲发生的地方。
通常使用侵入式指针来管理实例生命周期。
维护已加入频道的内部列表。这必须与实际的频道用户列表保持同步。
该文件是为少数需要原始速度而不是可定制的功能而保留的。处理登录 ( IDN
) 命令。处理调试 ( ZZZ
) 命令。处理搜索 ( FKS
) 命令。
Lua 文件中属于s
类别的所有 Lua 包装器命令。
Lua 文件中属于u
类别的所有 Lua 包装器命令。
Lua 文件中属于c
类别的所有 Lua 包装器命令。
Lua 文件中属于const
类别的所有 Lua 包装器值。
错误消息和定义。
使用定义的宏进行错误和类型检查!如果错误的类型意外地作为轻量级数据传递到函数中,这是防止崩溃的唯一方法。
确保您的 Lua 堆栈平衡。我已经尽力确保这是真的,但很容易犯错误。
避免将表返回到 Lua 代码,它们的构建成本很高,而且使用时间通常很短。使用最佳判断来平衡函数调用的成本和建表的成本。
避免不必要地将字符串传递给 Lua。由于内存副本的存在,成本可能会很高。
滥用 lua 接受多个返回值作为本机功能。参见上面两个注释。
处理仅推送的 Redis 线程。是 redis 命令及其返回值的小包装。
使用输入队列来接收命令。命令以周期性方式运行,并且不保证可靠性。可以禁用,禁用时忽略输入。
gdb 非常适合调试该应用程序。禁用 tcmalloc 和 LuaJIT 的 JIT 部分应该极大地有助于调试崩溃(tcmalloc 可以隐藏一些轻微的堆损坏)。
tcmalloc 中的内存分析工具非常好。有关如何使用它的更多信息,请参阅 tcmalloc 文档。