boost.asio包装类st_asio_wrapper开发教程(2016.10.8更新)(五)

来源:互联网 发布:sql server 2005 iis 编辑:程序博客网 时间:2024/05/03 18:10
如果你偶然浏览到这里,请先看 boost.asio包装类st_asio_wrapper开发教程(一)
源代码及例程下载地址:
git:https://github.com/youngwolf-project/st_asio_wrapper/,另外,我的资源里面也有下载,但不是最新的。
QQ交流群:198941541
十三:高级应用
        如果必须要处理一个耗时业务,如何避免影响所有连接,包括自己的数据收发呢,推荐的方法是,当要处理耗时业务时,在on_msg_handler里面开一个线程,然后马上退出,耗时业务在线程中处理,处理完了自动退出,这就带来了一个问题,这个耗时业务还没处理完的时候,后面的消息可能已经到达,这样就出现了消息乱序的问题,就是说你无法按顺序处理消息了(绝大多数情况下,顺序处理消息是必须的),此时,暂停消息派发就有用了,可以很容易的写出如下伪代码:
void run(shared_ptr<...> client_ptr)
{
        pop message from a global list
        handle message
        client_ptr->suspend_dispatch_msg(false);
}

virtual void on_msg_handle(out_msg_type& msg)
{
        push message to a global list
        suspend_dispatch_msg(true);
        thread t(bind(&run, shard_from_this());
}

        这样就做到了既不影响所有连接上的数据收发,又不至于让消息乱序。当然,如果你需要非常频繁的执行以上代码,最好的办法还是直接为st_service_pump多指定一些线程(并在on_msg_handle里面处理消息),多指定多少,要看你的业务,如果平均来说,有两个被创建用于处理耗时业务,那么多指定两个service线程即可,同样可以做到不影响所有连接上的数据收发,还简单很多。如果你的耗时业务是非常稀少的执行(意味着不值得多指定一个service线程,在一定吞吐量的情况下,用得线程越少,越说明你的代码写的好,线程越多不代表你的水平越高,况且很多时候,多线程是影响效率的),那么上面的代码值得你一试。
        还有一种情况跟耗时业务差不多,就是如果我暂时不方便处理业务怎么办?典型的例子就是:我的业务是将消息发送到一个队列里面供其它模块使用,但是这个队列满了,我只知道它会在某个时候变得可用,但不知道要等多久,这怎么办呢?方法之一当然就像上面说的,开个线程等待队列可用,这期间一直暂停消息的派发。这个方法显得有些怪异,因为开线程其实是用于等待,而不一定马上能处理消息。方法二是在on_msg_handle函数里面,如果你发现目前暂时不方便处理消息,可以在on_msg_handle里面直接返回false,这样的效果就是,st_socket会延迟一小段时间之后,尝试再次派发消息,这就完美解决了前面所提的问题(而不再需要开线程了)。
十四:拥塞控制
       前面说了,如果因为发送缓存满而阻塞在业务处理上(on_msg和on_msg_handle),就会有死锁的可能,如果我的业务就是把消息原样返回(比如echo_server)并且要缓存可控(不能以can_overflow为true调用send_msg),那么我必定要在on_msg和on_msg_handle中发送消息,且解除死锁的条件就是发送缓存可用,岂不是非得面对死锁的可能?答案是否定的:首先,消息总有个主动发起方,它要保证自己的内存占用可控,那么它一定会以can_overflow为false调用send_msg,并且处理失败的情况,这又分两种处理方式:一是调用safe_send_msg(但不要在service线程调用它,任何时候阻塞service线程都不是明智的,哪怕你不是在等发送缓存可用,此时虽然你可以阻塞service线程,但会严重影响其它连接上的数据收发和处理,况且,你可以阻塞,别人也阻塞,service线程很快就全阻塞了,那你的系统就不影响了)或者类似的处理方式;二是在on_msg_send中发送消息(只发送一条),此时send_msg肯定能成功(因为我们成功发送了一条消息,缓存里面应该有了至少一个空位),但推荐以can_overflow为true调用,因为st_asio_wrapper未做精确的缓存控制,即没有把对缓存大小的判断和对缓存的插入进行统一互斥,因为对于lock-free队列,由于对其操作都不带锁,所以也无法精确控制,我们不能为此给lock-free队列加一把锁吧?我写点代码示例一下:
一:精确控制:
lock
check buffer's size
insert message into buffer
unlock

二:st_asio_wrapper采用的控制:
check buffer's size
lock //对于lock-free队列,没有这行
insert message into buffer
unlock //对于lock-free队列,没有这行
这样一来,如果有10个线程同时调用send_msg,则缓存最多可以溢出9个消息,这也算是缓存可控了,因为溢出的数量是有上限的。从示例我们可以看出,即使在on_msg_send中发送消息,也有可能遇到缓存满(因为缓存可以最多举出9个消息),所以为了保证成功,我们以can_overflow为true来调用send_msg是明智的,它不会造成更大的缓存溢出(最多永远只溢出9个)。


现在看看接收方,如果瓶颈在接收方处理消息(特指回送消息之外的处理),那么拥塞已经控制住了,发送方一定会遇到一些send_msg失败的情况(发送方采用了第一种处理方式),并重发,这就是拥塞控制;如果瓶颈在接收方的上行速度(有些网络的上行和下行速度是不一样的)上,即在接收方的on_msg或者on_msg_handle里面send_msg失败,此时怎么办呢?首先我们不能阻塞on_msg和on_msg_handle,那么我们可以:
如果在on_msg里面send_msg失败,则开启拥塞控制,并返回false,如下:
        virtual bool on_msg(out_msg_type& msg)
        {
                auto re = send_msg(msg.data(), msg.size());
                if (!re)
                        congestion_control(true);
                return re;       
       }
然后消息将进入接收缓存,on_msg_handle被回调:
       virtual bool on_msg_handle(out_msg_type& msg, bool link_down)
       {
              auto re = send_msg(msg.data(), msg.size());
              if (re)
                     congestion_control(false);
              return re;
       }

如果开启了接收缓存,则直接在on_msg_handle里面返回false,如下:
virtual bool on_msg_handle(out_msg_type& msg, bool link_down) {return send_msg(msg.data(), msg.size());}

另外,有些业务本身就是天然的自带拥塞控制,比如pingpong测试,如果你的业务刚好是这种,那无需做任何处理就天然的具有了拥塞控制,但这种业务会严重限制IO吞吐量。
十五:小技巧
        在不定义ST_ASIO_FORCE_TO_USE_MSG_RECV_BUFFER宏的时候,通过on_msg,可以实现消息的分类处理,假设你有两类消息,它们之间没有任何先后关系,那么可以在on_msg处理简单消息,比如游戏中的聊天中转,可以在on_msg里面直接发给另一个玩家,而另一类消息则在on_msg中直接返回false,以便在on_msg_handle中顺序处理,这样做一定要记住一点,消息的顺序已经被打破(但是所有在on_msg中处理的消息是顺序的,所有在on_msg_handle中处理的消息也是顺序的),换句话说,后收到的聊天消息,可能先于先收到的杀怪消息而被处理。

boost.asio包装类st_asio_wrapper开发教程(一)

原创粉丝点击