- 论坛徽章:
- 3
|
本帖最后由 蔡万钊 于 2013-04-16 18:05 编辑
avbot 发布了许久了, 最近突然有个用户跑来说,希望能增加个调用 “外部脚本” 的功能,方便扩展。
我一向对设计一个 plugin 机制极力的避免,不喜欢动态载入的模块扩展程序本身的功能。何况 avbot 是 c++开发的,调用脚本并不是容易的事情。(好吧,真实的原因是我被 mingw (VC 不支持 utf8源码,我已经抛弃了) 折腾怕了,不想再搞个 python 。windows实在是恐怖的平台,写点程序麻烦的要死,编译麻烦的要死。可是 avbot 又必须跨平台,结果是我一天写好的东西要在 windows (虚拟机) 里折腾好几天,累死人 )
于是我决定提供一个 JSON 接口,内置一个简单的 HTTP Server, 用脚本(python应该 HTTP JSON 模块有的是,对吧)连接到 avbot ,然后 avbot 将发生的每条消息以 json 的形式返回给 外部脚本。
另外,默认使用 HTTP 的connection: keep-alive 模式,所以保持一个长连接即可。
那么,avbot 需要支持不确定数目的消息接收方了。
对于链接到 avbot 的客户端而言, avbot 并不保留之前的所有消息,而是从连接上的那一刻开始,后续的消息才能通知到。
一个很明显的思路就是,将链接上的客户端做成一个链表/列队, avbot 收到消息后,遍历这个列队执行消息发送。
这个思路很简单,可是如果要求 : 必须单线程异步呢?
avbot 是一个纯粹的单线程程序,绝对不允许多线程化。所有的逻辑必须使用异步处理。
那么,这个问题就复杂化了, “avbot 收到消息后,遍历这个列队执行消息发送” 这个做法,不可避免的带来了阻塞。好吧,异步遍历吧。
要是异步遍历还没遍历完,又来一个消息呢? 考虑这个问题,你会发疯的。因为异步,太多的细节需要考虑了。真的。
好吧,又有个好主意了,为每个客户端建立一个列队,每次遍历就是把要发送的消息挂入列队即可。这样也不需要异步遍历了,同步就可以。解决了异步遍历的时候又来一个消息导致的痛苦的调度。
然后细分,考虑每个客户端,就是等待 “发送列队” 不为空!等等,一直这么等待也不行,如果客户断开了链接呢? 所以要 “同时等待发送列队不为空&&客户正常在线,并且已经发送了 HTTP 请求头部”
好绕口,不过也只能如此了。
avbot 因为默认使用了 keep-alive , 所以发送是一个死循环,知道客户端主动断开链接或者网络发生错误。如果 客户端死了,那么,发送列队兴许会出现 爆队 的情况。所以要限制发送列队的大小。不是满了就不发送,而是满了后就把早的消息踢掉,也就是让 客户端发生“暂时性卡死”后,还能继续处理最后的几条信息。
诶,复杂的逻辑终于理清了,代码呢?!
啊累?
靠,这么复杂的 逻辑,得写一长段代码,调试几百年了吧?
错,我只花了几个小时,不到 100 行的代码就轻松实现了全部要求。
!!!!!!!!!!!!!!!!!!! WHAT !!!!!!!!!!!!!!!!!!!
这种功能不可能不用个千把行代码的吧?!
如果使用以前的老办法,确实如此。
可是,自从发现了 ASIO 后,我被 ASIO 爸爸发明的协程深深的震惊了!
利用 ASIO 爸爸提出的协程思想,我只用了不到 100行代码就全部完成了以上复杂的逻辑,而且,全部都是异步的哦~ 。
好,废话不多,先贴代码。然后解释。复制代码 首先这个 avbot_rpc_server 由一个 acceptor_service 辅助类调用。 acceptor_service 是一个模板,大家可以去 acceptor_server.hpp 膜拜。
acceptor_service 以 Protocol 和一个 处理类 为模板。在 main.cpp里,我以 asio::ip::tcp 作为 Protocl 的参数 avbot_rpc_server为 ProtocolProcesser的参数 调用acceptor_service。acceptor_service 进入一个死循环(协程的)不停的 accept , 然后将 accept 到的 socket 交给 ProtocolProcesser,也就是 avbot_rpc_server 。
avbot_rpc_server 处理一下客户的请求头,然后把自己注册到 on_message 信号处理。
然后,然后就没然后了。
on_message 在 avbot 接收到消息的时候发出。结果就是 avbot_rpc_server 的 第二个 operator() 被调用。然后就继续发送了。
当然,并不是每一个 on_message 都会导致 avbot_rpc_server 的 第二个 operator() 被调用的,必须是列队为空的时候。不为空的时候就不需要调用。发送循环会继续循环的,避免竞争出现
|
|