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_server 这个辅助类调用
- // 为其构造函数传入一个 m_socket, 是 shared_ptr 的.
- class avbot_rpc_server
- {
- public:
- typedef boost::signals2::signal on_message_signal_type;
- static on_message_signal_type on_message;
- typedef boost::asio::ip::tcp Protocol;
- typedef boost::asio::basic_stream_socket socket_type;
- typedef void result_type;
- avbot_rpc_server(boost::shared_ptr _socket)
- : m_socket(_socket)
- , m_request(new boost::asio::streambuf)
- , m_responses(new boost::circular_buffer_space_optimized(20) )
- {
- m_socket->get_io_service().post(
- boost::asio::detail::bind_handler(*this, boost::coro::coroutine(), boost::system::error_code(), 0)
- );
- }
- // 数据操作跑这里,嘻嘻.
- void operator()(boost::coro::coroutine coro, boost::system::error_code ec, std::size_t bytestransfered)
- {
- boost::shared_ptr sendbuf;
- if (ec){
- m_socket->close(ec);
- // 看来不是 HTTP 客户端,诶,滚蛋啊!
- // 沉默,直接关闭链接. 取消信号注册.
- if (m_connect && m_connect->connected())
- m_connect->disconnect();
- return;
- }
- CORO_REENTER(&coro)
- {
- do{
- // 发起 HTTP 处理操作.
- _yield boost::asio::async_read_until(*m_socket, *m_request, "\r\n\r\n", boost::bind(*this, coro, _1, _2));
- m_request->consume(bytestransfered);
- // 解析 HTTP
- // 等待消息.
- if (m_responses->empty())
- {
- if (!m_connect){
- // 将自己注册到 avbot 的 signal 去
- // 等 有消息的时候,on_message 被调用,也就是下面的 operator() 被调用.
- _yield m_connect = boost::make_shared
- (on_message.connect(boost::bind(*this, coro, _1, _2, _3, _4, _5)));
- // 就这么退出了,但是消息来的时候,om_message 被调用,然后下面的那个
- // operator() 就被调用了,那个 operator() 接着就会重新回调本 operator()
- // 结果就是随着 coroutine 的作用,代码进入这一行,然后退出 if 判定
- // 然后进入发送过程.
- }else{
- // 如果已经注册,直接返回。时候如果消息来了,on_message 被调用,也就
- // 是下面的 operator() 被调用. 结果就是随着 coroutine 的作用,代码
- // 进入上面那行,然后退出 if 判定。然后进入发送过程.
- return;
- }
- // signals2 回调的时候会进入到这一行.
- }
- // 进入发送过程
- sendbuf = m_responses->front();
- _yield boost::asio::async_write(*m_socket, *sendbuf, boost::bind(*this, coro, _1, _2) );
- m_responses->pop_front();
- // 写好了,重新开始我们的处理吧!
- }while(1);
- }
- }
- // signal 的回调到了这里, 这里我们要区分对方是不是用了 keep-alive 呢.
- void operator()(boost::coro::coroutine coro, std::string protocol, std::string room, std::string who, std::string message, sender_flags)
- {
- pt::ptree jsonmessage;
- boost::shared_ptr buf(new boost::asio::streambuf);
- std::ostream stream(buf.get());
- std::stringstream teststream;
- jsonmessage.put("protocol", protocol);
- jsonmessage.put("root", room);
- jsonmessage.put("who", who);
- jsonmessage.put("msg", message);
- js::write_json(teststream, jsonmessage);
- // 直接写入 json 格式的消息吧!
- stream
关注打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?
立即登录/注册


微信扫码登录