一个轻量级Actor并发框架的c++实现, libgsc(Game Server Communication Library)(一)

来源:互联网 发布:移动端js框架有哪些 编辑:程序博客网 时间:2024/04/30 01:06

从名字上可以看出, libgsc是一个游戏服务器端通信框架, 具体的说, 它是一个单进程, 多线程的服务器通信框架, 不一定只适用于游戏行业. 

暂时只有linux版本.


市场上至少有两个比较有名的actor的开源实现, 一个是基于scala的akka, 另一个是基于c++的caf.  功能都类似, 消息盒子, 同步等待等特性.


libgsc库的实现主要出于以下几个目的:

1. 希望深入的体会actor模型在实际代码设计中的优劣.

2. 寻求对传统多线程中死锁问题的彻底解决方案, 多线程的死锁问题, 非常难以解决,  如果假设这样一句话成立的话, 那么死锁可能有时是无解的. 这句

话是: 如果你在编码时把聪明用尽的话, 那你就没有能力调试它了.

3. 希望实现一种能对消息处理过程和结果进行完全跟踪的日志系统, 方便两件事, 一, bug跟踪, 二. 玩家日志输出, 理想中的状态类似于高阻跨接, 即全镜像

进程中的所有数据流动. 在不影响业务的同时, 又能完全满足日志分析需求. 

4. 提高进程的并发能力. 

5. 降低用人成本.


在实现完libgsc并尝试着使用之后, 发现上面的2, 3, 4问题都得到了非常好的解决.  问题1的结论是,  纯粹的actor模型写起代码来是比较啰嗦的, 代码量可

能更大, 未习惯它前, 感觉束手束脚. 问题5还没有结论, 除了我, 还未有第二个人使用过此库.


接下来将从libgsc的原理, 实现, 和使用上对其作详细描述.


一. 原理.

1. epoll.

epoll在libgsc中作为一个消息总线. 除了接收外部网络连接的数据之外, actor之间的通信也是通过epoll进行前转. 


2. worker, 工作线程.

如下图, epoll接口上有4个工作线程, 分别用于处理网络上的消息接收和异常处理. 红色的worker表示它还处理accept事件. 也就是说, 四个工作线程中, 

有一个额外担负着分配网络连接任务, 如果这是一个领导者与跟随者的模型, 那么这个红色的worker永远是领导, 工作线程的数量是可指定的.

如果你的机器有8个处理器, 你就可以指定8个或16个工作线程,  过多的工作线程在libgsc中没有意义, 反而可能带来线程间切换时的开销.


每个worker都持有一个unix-pipe, 并将读端置入epoll, 将写端暴露给其它的工作线程, 也就是说, 这些工作线程间通过管道来通信.


worker没有消息队列, 从epoll上返回的任何消息都是同步处理, 不存在或者不允许存在I/O等待.


每个worker下面都挂有n个actor,  两个挂在同一个worker下的actor可以直接调用, 没有任何通信成本. 跨worker的actor则需要通过pipe到epoll再到目的

actor的工作线程上, 由于是在同一个进程内,  它们中间只需要传递消息/事务的对象指针地址即可, 实现上, 管道之间的通信协议被设计成与外部通信协议

一致.


3. actor.

libgsc中有四种不同类型的actor.

a). 一般actor, 就叫actor. 也是其它actor类型的基类, 它没有被设计成像akka中的actor那样有个消息队列.  貌似不需要, 原因是任何消息都是同步处理的.

且不需要(也不能)等待响应, 发完就忘. 完全忘记.

b). actor-auto, 暂时叫做actor驱动器, 被设计成一个单例类. 意思是在进程中, 只需要一个这样的actor. 由于epoll消息总线本身在无外力的作用下无法运

转, 因此这个actor有一个属于自己的线程. 滴滴嗒嗒的作为一个定时任务调度器在运行, 驱动整个进程运转. 如果你在某天的某个时间点需要做点什么事, 

就可以在它的loop函数中处理. 理论上, 你不需发消息给这个actor, 当然它也不处理.

c). actor-independent, 暂时叫做独立的actor, 主要用于同步处理IO等待的操作, 如数据库访问, 它有一个队列, 例如, 当你要往数据库里面塞一条记录的

时候. 就可以给它发个消息, 独立的actor也拥有一个属于自己的线程, 与actor驱动器不同的是, 它可以有多个实例, 就好像你觉得一个数据库连接不足以

处理更多的业务, 就可以多创建几个.

d). actor-transaction, 事务型的actor, 与一般actor类似, 仅增加了一个散列表, 用于存放未完成的事务, 例如, 当你要求上面的数据库actor在执行完某个插

入操作后再返回一个响应, 这时就可以用到事务型的actor了.

4. message.

actor之间的基本通信单位,  一个message对象包括消息发起方, 消息接收方, 消息命令字, 消息正文和消息处理完成之后的结果.


5. transaction
事务, 任意两个actor之间的通信, 都是以事务开始, 以事务结束, 一个事务由一个或多个message组成, 任何actor都可以对事务进行三种操作.

a). begin, 开始, 也就是new一个新的事务.

b). forward,  前转, 当某个业务需要经多个actor才能完成时, 中间的actor就会选择将事务往下传递.

c). end, 结束, 业务处理完成后, 需要结束或者响应给客户端连接, 就可以显示或者暗示事务结束.


6. protobuf

任何actor之间通信的消息正文和网络上传输的协议报文都是一个protobuf消息对象, 选用protobuf基于以下几个事实:

a). 跨语言, 至少c++, java得到了官方支持, 这样我们可在合适的地方, 如日志分析模块中使用开发效率较高的java语言.

b). 在json, xml, 甚至html之间可自由转换.

c). 在c++中, 可以达到类似java中的反射效果. 

d). 转换成json后, 与mongo交互非常方便.


好像差不多了,  欢迎各位提意见. 代码已上传到github: https://github.com/xzwdev/libgsc

0 0