网络库libevent、libev、libuv对比

来源:互联网 发布:网络团购的流程 编辑:程序博客网 时间:2024/05/16 03:14

http://blog.csdn.net/lijinqi1987/article/details/71214974

Libeventlibevlibuv三个网络库,都是c语言实现的异步事件库Asynchronousevent library

异步事件库本质上是提供异步事件通知(Asynchronous Event NotificationAEN)的。异步事件通知机制就是根据发生的事件,调用相应的回调函数进行处理。

事件(Event):事件是异步事件通知机制的核心,比如fd事件、超时事件、信号事件、定时器事件。有时候也称事件为事件处理器(EventHandler),这个名称更形象,因为Handler本身表示了包含处理所需数据(或数据的地址)和处理的方法(回调函数),更像是面向对象思想中的称谓。

事件循环(EventLoop):等待并分发事件。事件循环用于管理事件。

对于应用程序来说,这些只是异步事件库提供的API,封装了异步事件库跟操作系统的交互,异步事件库会选择一种操作系统提供的机制来实现某一种事件,比如利用Unix/Linux平台的epoll机制实现网络IO事件,在同时存在多种机制可以利用时,异步事件库会采用最优机制。

 

对比下三个库:

libevent :名气最大,应用最广泛,历史悠久的跨平台事件库;

libev :libevent而言,设计更简练,性能更好,但对Windows支持不够好;

libuv :开发node的过程中需要一个跨平台的事件库,他们首选了libev,但又要支持Windows,故重新封装了一套,linux下用libev实现,Windows下用IOCP实现;


在github上的影响力:


可见,目前libuv的影响力最大,其次是libevent,libev关注的人较少。


优先级、事件循环、线程安全维度的对比

   特性

                       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 )


这个对比对于libev和libuv更有意义,对于libevent,很多都是跟其设计思想有关的。 libev中的embed很少用,libuv没有也没关系;cleanup完全可以用libuv中的async_exit来替代;libuv没有fork事件。


可移植性

三个库都支持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


对于Unix/Linux平台,没有什么大不同,优先选择epoll,对于windows,libevent、libev都使用select检测和分发事件(不I/O),libuv在windows下使用IOCP。libevent有一个socket handle, 在windows上使用IOCP进行读写。libev没有类似的。但是libevent的IOCP支持也不是很好(性能不高)。所以如果是在windows平台下,使用原生的IOCP进行I/O,或者使用libuv。


异步架构程序设计原则

1、回调函数不可以执行过长时间,因为一个loop中可能包含其他事件,尤其是会影响一些准确度要求比较高的timer。

2、尽量采用库中所缓存的时间,有时候需要根据时间差来执行timeout之类的操作。当然能够利用库中的timer最好。



参考:http://zheolong.github.io/blog/libevent-libev-libuv/

    https://cpp.libhunt.com/


libevent

libev

libuv

node.js

 

工具库和框架之间的区别,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操作完成后通知用户。

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又是异步的




libuv 和 libev ,两个名字相当相近的 I/O Library,最近有幸用两个 Library 都写了一些东西,下面就来说一说我本人对两者共同与不同点的主观表述。

高性能网络编程这个话题已经被讨论烂了。异步,异步,还是异步。不管是 epoll 也好,kqueue 也罢,总是免不了异步这个话题。

libuv是异步的,libev是同步的多路IO复用。

libev 是系统I/O复用的简单封装,基本上来说,它解决了 epoll ,kqueuq 与 select 之间 API 不同的问题。保证使用 livev 的 API 编写出的程序可以在大多数 *nix 平台上运行。但是 libev 的缺点也是显而易见,由于基本只是封装了 Event Library,用起来有诸多不便。比如 accept(3) 连接以后需要手动 setnonblocking。从 socket 读写时需要检测 EAGAIN 、EWOULDBLOCK 和 EINTER 。这也是大多数人认为异步程序难写的根本原因。

libuv 则显得更为高层。libuv 是 joyent 给 Node 做的一套 I/O Library 。而这也导致了 libuv 最大的特点就是处处回调。基本上只要有可能阻塞的地方,libuv 都使用回调处理。这样做实际上大大减轻了程序员的工作量。因为当回调被 call 的时候,libuv 保证你有事可做,这样 EAGAIN 和 EWOULDBLOCK 之类的 handle 就不是程序员的工作了,libuv 会默默的帮你搞定。

libev 在 socket 发生读写事件时,只告诉你,“XX socket 可以读/写了,自己看着办吧”。往往我们需要自己申请内存并调用 read(3) 或者 write(3) 来响应 I/O 事件。

libuv 则稍微复杂一些,我们分读/写两个部分来描述。

当接口可读时,libuv 会调用你的 allocate callback 来申请内存并将读到的内容写入。当读取完毕后,libuv会 call 你为这个 socket 设置的回调函数,在参数中带着这个 buffer 的信息。你只需要负责处理这个 buffer 并且free 掉就OK了。因为是从 buffer 中读取数据,在你的 callback 被调用时数据已经 ready 了,所以程序员也就不用考虑阻塞的问题了。

而对写的处理则更显巧妙。libuv 没有 write callback ,如果你想写东西,直接 generate 一个 write request 连着要写的 buffer 一起丢给 libuv ,libuv 会把你的 write request 加进相应 socket 的 write queue ,在 I/O 可写时按顺序写入。

C 没有闭包,所以确定读写上下文是 libuv 的使用者需要面对的问题。否则程序面对汹涌而来的 buffer 也不能分得清哪个是哪个的数据。在这一点的处理上,libuv 跟 libev 一样,都是使用了一个 void *data来解决问题。你可以用 data 这个 member 存储任何东西,这样当 buffer 来的时候,只需要简单的把 data cast 到你需要的类型就 OK 了。

libev 没有异步 DNS 解析,这一点一直广为垢病。

libuv 有异步的 DNS 解析,解析结果也是通过回调的方式通知程序。

libev 完全是单线程的。

libuv 需要多线程库支持,因为其在内部维护了一个线程池来 handle 诸如 getaddrinfo(3) 这样的无法异步的调用。

libev 貌似是作者一个人在开发,版本管理使用的还是 CVS ,社区参与度明显不高。

libuv 社区十分活跃,几乎每天都有人提出 Issue 并贡献代码。

libev 不支持 IOCP ,如果需要在 Win 下运行的程序会很麻烦。

libuv 支持 IOCP ,有相应脚本编译 Win 下的库。

-----------------------------------------------------------------------------------

Q: 博主有没做过两者的benchmark,他们之前的性能对比如何?

A: 当时用 libev 和 libuv 写过一个简单的 HTTP Hello World Server 。具体结果记不清楚了但是可以说性能差距在 5% 以内。

Q:  libuv 在 unix 上应该是用 libev 作为 non-blocking IO 的实现的吧?libuv 中线程池里线程的数量会增加么,是否会有上限?如果上限到了是不是就会出现 block 的情况?

A: 1. libuv 在大概5个月前已经完全不使用 libev 了,参见 commit665a316aa9d551ffdd00d1192d0c3d9c88d7e866 ; 2. libuv 的线程池在BSS上,数量固定为4个,参见:https://github.com/joyent/libuv/blob/master/src/unix/threadpool.c#L28 ; 3. libuv 的线程池共享一个work queue ,所以不会出现 block 的情况



libevent : 名气最大,应用最广泛,历史悠久的跨平台事件库;libev : 较libevent而言,设计更简练,性能更好,但对Windows支持不够好;libuv : 开发node的过程中需要一个跨平台的事件库,他们首选了libev,但又要支持Windows,故重新封装了一套,*nix下用libev实现,Windows下用IOCP实现;

阅读全文
0 0
原创粉丝点击