在类unix系统中有五大I/O模型,依次为阻塞IO(BIO)、非阻塞IO(NIO)、IO多路复用(linux下有select、poll、epoll三种方案)、信号驱动IO、异步IO(前面四种都是同步IO),本文主要介绍常用的C的IO库,几乎都是基于IO多路复用,因为它性能很高,并且稳定,异步IO虽说性能更高但不稳定。
从linux源码看socket的阻塞和非阻塞
ps:
对于IO多路复用,epoll机制是linux独有的,其他类unix系统(macOS、FreeBSD、OpenBSD、NetBSD)使用的是kqueue,但是SUNOS系列使用的是event ports(即evports)。
IOCP是window基于线程池技术实现的异步IO,非常稳定。
我们就顺着I/O模型分别认识一下这些库。
Libevent是一个用于开发可扩展性网络服务器的基于事件驱动(event-driven)模型的非阻塞网络库,Libevent有几个显著的特点:
Libevent是用于编写高速可移植非阻塞IO应用的库,其设计目标是:
Libevent 已经被广泛的应用,作为底层的网络库;比如 memcached、Vomit、Nylon、Netchat等等,你想实现一个TCP或HTTP服务,Libevent可以高效的协助你完成工作。
github源码
Libev是由大神Marc Lehmann独立完成的,对不同系统非阻塞模型的简单封装,解决了 epoll ,kqueuq 、evports等模型之间 API 不同的问题,保证使用 Libev 的 API 编写出的程序可以在大多数 *nix 平台上运行,设计非常精炼,非常轻量级,性能很不错。
Libev对类UNIX系统支持完善(如select、poll、epoll、kqueue、evports等I/O多路复用模型),对Windows支持的不好,只实现了Windows六种I/O模型中的select模型(没错,Windows有六种I/O模型:阻塞模型、选择模型、异步选择模型、事件选择模型、重叠I/O模型、IOCP模型),select效率是低下的,远远不如Libuv封装的IOCP。
Libev相比Libevent功能上来说几乎差不多,严格来说变弱了,比如不支持HTTP(S),DNS解析,那么为什么Marc Lehmann还要费时费力创作Libev呢,当然是因为Libevent有一些令人诟病的问题了,Libevent问题如下:
Libev试图改进所有这些缺陷,例如避免使用全局变量,转而在所有函数中,使用上下文变量来代替。每个事件类型,使用单独的watcher类型(一个I/O watcher在64位机器上,只需要56字节,而Libevent需要136字节)。允许额外的事件类型,例如基于挂钟的计时器,或者单调时间,线程内中断,准备并检查watchers来嵌入其他事件循环,或者被用于其他事件循环来嵌入。
简单来说,Libev的诞生,是为了修复Libevent设计上的一些错误决策。
github源码
libuv是一个跨平台(window,linux,macOS)、高性能,事件驱动的异步I/O库。它本身是由C语言编写的,封装了不同平台底层对于高性能IO模型的实现(epoll【Linux】,kqueue【macOS、BSD等】,IOCP【windows】,event ports【SUNOS系列】),具有很高的可移植性。
libuv专为Node.js而设计,但是后来因为它这种事件驱动的异步IO的高效模型逐步被很多语言和项目都采纳而作为自身的底层库而使用,像 Luvit, Julia, uvloop, pyuv等。
Nodejs刚出来的时候,底层并不是使用libuv,而是libev,libev本身也是一个异步IO的库,但是它只能在遵循POSIX规范系统下使用。随着nodejs被越来越多人使用,由于windows的用户量巨大,所以开始考虑Nodejs的跨平台能力。
于是Nodejs的作者 joyent大神提供了libuv来作为抽象封装层,在Unix系统上,通过封装libev,libeio来调用Linux的epoll 和类UNIX上的 kqueue,在Windows 平台上的IOCP进行封装,自此之后Nodejs具备了跨平台能力,由Libuv作为中间层本身提供的跨平台的抽象,来根据系统决定使用libev、libeio、IOCP,后来在node-v0.9.0版本中,libuv移除了libev的内容。
github源码
从上面的介绍中可以看出libevent、libev、libuv有一定的关联又有一定的区别,这里总结下:
影响力
根据github星标个数来看,目前libuv的影响力最大,其次是libevent,libev关注的人较少。
优先级、事件循环、线程安全维度的对比图
特性 | libevent | libev | libuv |
---|---|---|---|
优先级 | 激活的事件组织在优先级队列中,各类事件默认的优先级是相同的,可以通过设置事件的优先级使其优先被处理 | 也是通过优先级队列来管理激活的时间,也可以设置事件优先级 | 没有优先级概念,按照固定的顺序访问各类事件 |
事件循环 | event_base用于管理事件 | 激活的事件组织在优先级队列中,各类事件默认的优先级是相同的,可以通 过设置事件的优先级 使其优先被处理 | 顺序访问 |
线程安全 | libevent、libev、libuv 里的 event_base 和 loop 都不是线程安全的,也就是说一个 event_base 或 loop 实例只能在用户的一个线程内访问(一般是主线程),注册到 event_base 或 loop 的 event 都是串行访问的,即每个执行过程中,会按照优先级顺序访问已经激活的事件,执行其回调函数。所以在仅使用一个 event_base 或 loop 的情况下,回调函数的执行不存在并行关系。如果应用程序除了主 loop 外,没有自己启动任何线程,那么不用担心回调里的 “临界区”。如果使用了多个 event_base 或 loop(一般每个线程一个 event_base 或 loop),需要考虑共享数据的同步问题。 | 见左 | 见左 |
事件种类对比图
可移植性对比图
三个库都支持 Linux, *BSD, Mac OS X, Solaris, Windows。对于 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。
移步文章Linux异步IO实现方案总结
目前linux 异步IO方式实现有两种:
1:Native AIO(原生异步IO),但只支持O_DIRECT 方式,无法利用 Page cache,场景有限,很少用;
2:多线程模拟异步IO ,如Glibc AIO、libeio、io_uring;
libeio github源码
libeio源码分析 – 主流程
libeio异步I/O库初窥
1:*unix 5种IO模型
2:windows 6种IO模型
3:What’s the difference between libev and libevent
4:libev和libevent的设计差异
5:网络库libevent、libev、libuv对比
6:网络库 libevent、libev、libuv 对比
7:libevent库源码学习-evport(event ports)
8:深入分析Kqueue
9:Linux原生异步IO原理与实现(Native AIO)
10:全面了解Linux原生异步 IO 原理与使用