libevent介绍(一)——同步/异步IO、阻塞/非阻塞IO

884 阅读3分钟

从同步IO开始

同步IO指的是,当调用后,IO会在完成时返回,也就是等待IO操作执行完成后再继续进行。

举例:当在TCP中调用connect()函数后,操作系统会发送SYN包到远端服务器,并等待远端服务器的返回,在这个过程中,操作系统会阻塞直到接收到返回或超时。在这个过程中,操作系统在等待返回时,应用程序会被阻塞。

同步的、阻塞的IO并非不好,相反,经典的同步IO、阻塞IO有代码编写简单,同时程序执行是按照顺序执行的等优点,因此在很多情况下都工作良好。

缺点

同步IO的缺点在于,应用程序会被阻塞,影响工作效率。

举例:在单线程中如果进行两次网络请求,尽管第二次网络请求的数据已经先准备好,但仍不得不等待第一次的请求返回。

有时人们会使用多线程或多进程服务器来解决这个问题,在linux中是调用fork()来创建新的进程的。
问题在于,如果程序需要一次处理数千乃至数万个连接,那么连接的效率将远远降低,错误率也会随之增高。

异步IO

除了多线程外,更好的解决方案是异步IO。

非阻塞socket

在Unix中,可以将socket设置为非阻塞的:fcntl(fd, F_SETFL, O_NONBLOCK)。其中,fd是文件描述符,是内核在打开套接字时分配给套接字的数字,使用此数字在Unix调用中引用套接字。
一旦设置了非阻塞后,只要对fd进行网络调用,调用要么完成操作,要么返回指示无法取得进展的错误代码,也就是没有接收到数据。
将socket设置为非阻塞后,我们就需要一种机制,来知道非阻塞IO是否获得数据,否则只能一次次的轮询,影响效率。

select、poll、epoll

select机制可以用于解决上述问题,当非阻塞IO获得数据时,select可以知道IO是否准备好。select调用会维护三个fd的集合:一个用于读数据、一个用于写数据、一个用于异常。
select并非最好,当socket数量增大时,select机制也会产生大量的时间开销。比select机制更好的方式是pollepoll

  • select: 监视文件描述符,使用数组存储且数量有上限限制,同时是使用轮询的方式查询,效率较低,同时需要维护存放fd的数据结构,导致在用户空间和内核空间传递该结构时开销大。
  • poll: 本质上类似select,但由于使用链表存储,没有上线限制,需要遍历查询监视符状态。
  • epoll: 支持水平触发和边缘触发,边缘触发会告知进程哪些监视符变为就绪态,并且只通知一次,同时使用事件的就绪通知方式,效率较高。

结语

在不同的系统中实现这些接口的方式不同,因此如果想要编写高性能的异步程序,就需要对这些接口都有所了解。这也就是libevent项目优点所在,libevent项目为各种操作系统中的非阻塞IO提供一个一致的接口,便于程序员使用。在后续的文章中将详细介绍libevent。