ASIO协程的思维转变

Posted on April 13, 2013

avbot 是一个纯粹的单线程程序。在设计 avbot 最初的时候,我就给自己下了一个明确的目标:必须单线程。

但是,它的逻辑可不简单。它需要处理 XMPP 协议,处理 IRC 协议,处理 WebQQ 协议,处理pop3协议,处理 SMTP 协议。 所有的处理都必须异步。绝对不能因为 IO 阻塞。

可能很多人会不以为然,这有什么, select() 一下就好了。但是你知道这意味着多少代码么?

你需要编写大量的函数,大量的回调函数。别说复杂的 WebQQ 协议了,就是一个简单的 IRC , 你知道要多少代码完成异步处理么?

而且除了 WebQQ 以外(WebQQ的代理支持也在添加计划中), XMPP IRC POP3 SMTP 都是支持代理的。你知道要多少代码完成代理么?

想必很多吧?

所幸的是,avbot 没有使用C开发,也没有选择 ACE MFC 这样的不合格的类库开发。而是选择了 Boost。尤其是 ASIO, ASIO让异步过程大大简化。

而让异步过程更加简化的,就是 ASIO 作者发明的 stackless coroutine!

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行代码就全部完成了以上复杂的逻辑,而且,全部都是异步的哦~ 。

Comments