【C++】libevent 、libev、 libuv 、asio、 muduo、 ace 等C++ 网络库
目录
选择建议
首先排除preactor模式的(难写),即asio和ACE;其次,尽量选用多进程单线程(简单可横向扩展)的,可以参考nginx。
整体感觉:ACE太庞大,asio 太赶时髦。
ACE太过庞大,使得你即便是只使用它的一小部分,也不得不引用它的全部。而且框架一大堆,模式一个加一个,很多编程习惯也要改变。学习曲线太陡,也难以将它作为一个模块集成自己的应用。
asio呢,有个牛大大说它是现时代的ACE,我觉得比较中肯。用bind做回调也并不比虚函数好,看上去灵活了,代价却更高了。我说的不光是运行时的内存和时间代价,更重要的编译时间难以忍受。
apr大约只是一个平台无关的api封装,相对来说比较轻量级。
libevent就更轻量级了,轻量级到无法把它当成一个平台无关的socket,还要写很多平台相关的代码。
相比而下,我觉得apr还好点,其实我需要的只是一个机制,而不是一个完整的策略,这是unix的哲学。如果仅就异步通讯来说,我觉得linux.epoll是最好的,简单、直接、有效,如果在每个平台上都有epoll可用,所有其它封装在我看来都是多余的了。
proactor真的就比reactor高效吗?它更多的是一种策略,据说Windows的操作系统级IOCP是用线程池实现的,它能高效到哪里去?
区别
简介
Libevent,libuv,libev,IOCP,asio,muduo,tbnet都是网络函数库
Libevent、libev、libuv三个网络库,都是C语言实现的异步事件库(Asynchronousevent library),异步事件通知机制就是根据发生的事件,调用相应的回调函数进行处理。
对比下三个库:
libevent :名气最大,应用最广泛,历史悠久的跨平台事件库;
libev :较libevent而言,设计更简练,性能更好,但对Windows支持不够好;
libuv :开发node的过程中需要一个跨平台的事件库,他们首选了libev,但又要支持Windows,故重新封装了一套,linux下用libev实现,Windows下用IOCP实现;
康康github上的start感觉感觉他们的影响力 (libuv >> libevent > libev):
https://github.com/libevent/libevent
https://github.com/enki/libev
https://github.com/libuv/libuv
5.boost::asio
C++语言跨平台。用bind做回调也并不比虚函数好,看上去灵活了,代价却更高了。不光是运行时的内存和时间代价,编译时间也更长。基于ASIO开发应用,要求程序员熟悉函数对象,函数指针,熟悉boost库中的boost::bind,内存管理控制方面。
asio是一个高性能的网络开发库,Windows下使用IOCP,Linux下使用epoll。
考虑到asio的使用,对现有系统的改造难度大,排除。
6.Muduo
这是一个用纯c++写的库,仅在linux下使用,one loop per thread的思想贯穿其中,将I/O 定时 信号都通过文件描述符的方式融合在一起,三类事件等同于一类事件来看待。因仅在linux下使用,故而排除。
特性 | libevent | libev | libuv |
优先级 | 激活的事件组织在优先级队列中,各类事 件默认的优先级是相同的,可以通过设置 事件的优先级使其优先被处理 | 也是通过优先级队列来管理激活的时间, 也可以设置事件优先级 | 没有优先级概念,按照固定的顺序访 问各类事件 |
事件循环 |
event_base用于管理事件 | 激活的事件组织在优先级队列中,各类事件默认的优先级是相同的, 可以通 过设置事件的优先级 使其优先被处理 | |
线程安全 | event_base和loop都不是线程安全的,一个event_base或loop实例只能在用户的一个线程内访问(一般是主线程),注册到event_base或者loop的event都是串行访问的,即每个执行过程中,会按照优先级顺序访问已经激活的事件,执行其回调函数。所以在仅使用一个event_base或loop的情况下,回调函数的执行不存在并行关系
|
事件种类
type | libevent | libev | libuv |
IO | fd | io | fs_event |
计时器(mono clock) | timer | timer | timter |
计时器(wall clock) | -- | periodic | -- |
信号 | signal | signal | signal |
进程控制 | -- | child | process |
文件stat | -- | stat | fs_poll |
每次循环都会执行的Idle事件 | -- | idle | idle |
循环block之前执行 | -- | prepare | prepare |
循环blcck之后执行 | -- | check | check |
嵌套loop | -- | embed | -- |
fork | -- | fork | -- |
loop销毁之前的清理工作 | -- | cleanup | -- |
操作另一个线程中的loop | -- | async | async |
stream ( tcp, pipe, tty ) | stream ( tcp, pipe, tty ) | stream ( tcp, pipe, tty ) | stream ( tcp, pipe, tty ) |
可移植性
三个库都支持Linux, *BSD, Mac OS X, Solaris, Windows
type | libevent | libev | libuv |
dev/poll (Solaris) | y | y | y |
event ports | y | y | y |
kqueue (*BSD) | y | y | y |
POSIX select | y | y | y |
Windows select | y | y | y |
Windows IOCP | y | N | y |
poll | y | y | y |
epoll | y | y | y |
摘自:https://blog.csdn.net/dianfu2892/article/details/101466823
网络库的前世今生
工具库和框架之间的区别,asio是被设计成一套工具库而不是框架。
什么是框架? 框架就是一套固定了编程结构的库,任何用户使用它,必须按照框架库的结构设计自己的应用,比如MFC中的OnOK, OnXXX之类,又或者ACE中的ACE_Handler::handle_xxx_yyy之类,用户通过在这些派生类的虚函数中实现自己需要的处理。
什么是工具库? 而工具库就是一套api,而不是框架,用户使用它,无需从指定的地方去填写代码。程序的框架由用户自己决定。类似的库有C++中的STL,C中的标准库,它们从来不提供编程框架,而只提供工具函数,让用户通过这些工具函数,正确的组织自己的程序结构。
框架的限制非常多,不够灵活
https://www.avboost.com/t/topic/654
select
select 实在是太慢了.
在这种背景下, IBM 老大哥带领着MS老弟先搞了 IOCP . 然而开源的人有开源的做法, 在 NIH 综合症(不信任他人,重复造轮子), BSD 的人敢为天下所不齿, 发明了 Kqueue. 同样在 NIH 综合症影响下, Linux 的一群 M* 的猴子捣鼓出了 epoll.
分裂, 让人头疼.
于是程序员们急需一个上天入地无所不能的法宝的法宝, 把这3家法宝给统御起来.率先站出来悳瑟的是 ACE.
ACE 过于复杂,甚至比它试图封装的对象更复杂
libevent 就如名字所言,是一个异步事件框架。
libevent把简单问题简单化,让异步网络编程反朴归真,应该来说,本是一个好库。
然而 libevent 因为设计缺陷,例如使用全局变量,定时器无法处理时间跳变,诸如此类的设计缺陷导致了 libev 的出现。
libev 就是为了克服libevent的缺陷而诞生的。然而,libev 就一定好了吗?
libev 带着对 libevent 的怨气出世了。
编写异步程序, 最需要的2个抽象能力, 其一为协程,其二是函数对象,包括匿名函数对象, 也就是lambda。
C统统没有。函数对象是实现闭包比不可少的,如果没有函数对象, 就只能通过携带 void*
指针的形式迂回完整,繁琐不说,还特别容易出错。
尽管C 有那么多缺点,然而 libev 还未来得及被C的缺点拖累,因为他不支持 IOCP. 于是 libuv 就出来给 libev 擦屁股了。
libuv 可以说是 C 语言的异步库所能达到的最高高度了。完完全全的触碰到了C语言的自身瓶颈,好在 libuv 只是 nodejs 的底层库,上层软件转移到 javascript 语言而逃避了 C 的禁锢。
ASIO 腾空出世
在地球最大的岛上,另一位少年开始拜读 ACE 的大作。那时候,没有 libuv 没有 libev 更没有 libevent . 有的只是 ACE.
少年在一次开发者大会的演讲上,再次透露,网络库不宜做成框架,而是要像系统的API那样,作为一个乐高积木。ACE 做成了一个框架,同样不妥。
buffers
有了闭包的支持,内存管理也变得轻轻松松起来。
ASIO 本身并不管理内存,所有的IO操作,只提交对用户管理的内存的引用,称 Buffers。asio::buffers 引用了用户提交的内存,保持整个 IO 期间,这块内存的有效性是用户的责任。然而这并不难!
因为回调是一个闭包。通过闭包持有内存,只要 asio 还未回调,闭包就在,闭包在,内存在。asio 在调用完回调后才删除相应的闭包。因此资源管理的责任可以丢给闭包,而闭包可以通过智能指针精确的控制内存。
不是 GC , 胜于 GC 千百倍!益于c++的 RAII机制,再无内存泄漏之忧!
asio采用proactor模式,而windows下的IOCP本身就是这个模式的体现
在高性能服务器并发模型设计中,Reactor和Proactor是两个经常用到的设计模式,前者用于同步IO,后者用于异步IO,前者在IO操作 就绪的情况下通知用户,用户再采取实际的IO操作,后者是在IO操作完成后通知用户。
(Reactor模式:用于同步IO,在IO操作 就绪的情况下通知用户,用户再采取实际的IO操作。
Proactor模式:用于异步IO,后者是在IO操作完成后通知用户。)
IOCP的设计就是Proactor 模式的完美体现,而epoll则很容易实现Reactor模式,asio设计为跨平台,并且在linux下采用epoll,我的印象中linux对异步 IO的支持没有windows那么完善(看看IOCP和epoll模型的区别就知道),那么asio是怎么用epoll机制实现proactor模式的 呢,刚开始想的是应该在应用层做了一层封装,就是asio内部应该有某个循环调用epoll_wait,当有IO事件就绪时帮用户做一些操作(比如把数据 拷贝到我们提交的缓冲区),然后在操作完成的时候调用我们的handler(后来看代码基本是这样).OK,不说废话了.
本质上来讲libevent应该是同步的,因为如果看到底层封装的select和epoll就会发现,里面仍然是个while循环,在不停的询问,是否准备就绪,而异步同步IO的主要区别就是,应用发起一个 IO 操作以后,不等待内核 IO 操作的完成,等内核完成 IO 操作以后会通知应用程序,这其实就是同步和异步最关键的区别,同步必须等待或者主动的去询问 IO 是否完成,显然select和epoll是同步的。
另外本身Reactor模式就是同步的模式不是吗?
如果你理解底层的select或者epoll。你会发现它其实是同步的。有一个while循环,等待事件触发条件。满足条件则调用相应的callback函数。因此本质上还是同步的。不过长了一个异步的脸
libevent本身是一个Reactor,是同步的。但libevent的bufferevent是用Reactor实现了一个Proactor,所以libevent又是异步的