一.Libevent在window上的实现-->iocp
1.iocp是什么
IOCP (Input/Output Completion Ports) 是 Windows 操作系统提供的一种高性能、可扩展的异步 I/O 模型。这种模型基于事件驱动,可以大幅提升 I/O 操作的效率和并发量,尤其适合处理大量连接和大量数据流的网络应用。IOCP 主要包含三个组件:I/O 端口、完成端口和工作者线程池。I/O 端口用来描述文件句柄或套接字,完成端口用来存储已完成的 I/O 请求以及通知工作者线程处理请求,工作者线程池则负责执行 I/O 操作和相关的业务逻辑。
2.iocp基本原理
IOCP 的基本原理是通过事件驱动的方式来处理 I/O 请求,以避免传统的阻塞式 I/O 操作带来的性能问题。具体来说,IOCP 通过以下几个步骤完成异步 I/O 操作:
(1)应用程序首先创建一个或多个 I/O 端口,并将它们关联到套接字或文件句柄上。
(2)当应用程序需要进行 I/O 操作时,它调用系统级别的 API,将请求提交到 I/O 端口上。
(3)操作系统内核将 I/O 请求与相应的 I/O 端口关联,并立即返回,使得应用程序可以继续执行其他操作。
(4)内核在后台异步地执行 I/O 操作,并将结果存储在完成队列中。
(5)当 I/O 操作完成时,内核会通知完成端口,并将完成信息添加到完成队列中。
(6)应用程序通过调用 GetQueuedCompletionStatus() 函数获取完成队列中的已完成请求,并按需处理它们。
(7)如果完成队列为空,则应用程序可以等待新的完成事件发生,或者继续执行其他操作。
以上过程中,工作者线程负责从完成队列中取出已完成的 I/O 请求,并进行相应的处理。由于这些线程都是从线程池中获取的,因此可以有效地管理和控制线程的数量和使用情况,从而提高系统的效率和稳定性。
3.应用场景
IOCP 的高性能、可扩展特性使其广泛应用于以下几个领域:
(1)网络编程。IOCP 可以帮助网络应用程序实现高并发、低延迟的数据传输,尤其适合处理大量连接和大量数据流的场景,如网络游戏、在线聊天、视频流媒体等。
(2)数据库操作。IOCP 可以加速数据库操作中的文件读写、网络传输等 I/O 操作,提升数据库系统的性能和稳定性。
(3)高性能服务器开发。IOCP 可以作为一种高效的事件驱动模型,帮助服务器应用程序快速响应客户端请求,提高系统的吞吐量和并发度。
(4)多线程编程。IOCP 可以帮助开发者实现多线程编程中的任务分配和负载均衡,提高代码执行效率和并行度。
总之,IOCP 在需要高性能、高并发、低延迟的应用场景下具有很大的优势,并且可以有效地避免传统阻塞式 I/O 带来的性能问题。
二.reactor简介
reactor最重要的两个元素:IO和事件。既然libevent封装了reactor,那么需要了解reactor中IO、事件以及它们之间的关系。
1、IO
IO处理是同步的;不管是阻塞模式还是非阻塞模式,都是同步操作。reactor中IO分为: (1)IO检测:通过IO多路复用来检测,检测IO是否就绪。 (2)IO操作:从网络协议栈中读取数据、用户层将数据写入网络协议栈中、接收连接、发起建立连接等。
2、事件
事件处理是异步的;reactor中处理事件是先注册事件,然后有一个事件循环,在事件循环中检测事件是否就绪,如果事件就绪了才会处理事件。注册事件和事件循环是在两个不同的流程中进行的。 reactor网络编程的主体思想是将对IO的处理转换为对事件的处理。事件分为: (1)读事件 (2)写事件 (3)异常事件
3、IO与事件的关系
先注册事件,事件就绪时操作IO。IO与事件的关系主要体现在 (1)检测IO是否就绪(通过IO多路复用)是在事件循环中进行的;(2)事件触发之后开始处理事件,处理事件过程中涉及到IO操作。
三 .Libevent工作流程
Libevent 的工作流程可以概括为以下几个步骤:
初始化:
调用 event_base_new()(或旧版本的 event_init())来创建和初始化一个 event_base 实例,这是 Libevent 的核心结构,它代表了一个事件循环。
创建事件:
使用 event_new() 或 event_assign() 创建一个 event 实例,并设置其文件描述符、事件类型(如 EV_READ、EV_WRITE)、事件回调函数以及用户数据。
添加事件:
调用 event_add() 将事件添加到 event_base 中,可以指定一个超时时间,这样事件会在指定的时间后被触发。
事件循环:
调用 event_base_dispatch() 开始事件循环。这个函数会阻塞,直到至少有一个事件准备好。
在内部,Libevent 会根据操作系统的支持选择合适的事件多路复用机制(如 epoll、select、kqueue)来等待事件。
事件处理:
当事件准备好时,Libevent 会调用与之关联的回调函数。
回调函数执行完毕后,Libevent 会继续监听其他事件。
修改或删除事件:
可以通过 event_del() 从 event_base 中删除事件,或者通过 event_add() 修改事件的超时时间或重新添加事件。
清理:
当不再需要事件循环时,可以调用 event_base_free() 来释放 event_base 实例,这将清理所有关联的资源。
在整个工作流程中,Libevent 提供了高效的事件管理机制,使得开发者可以专注于事件的处理,而无需关心底层的IO多路复用细节。此外,Libevent 还提供了缓冲事件(bufferevent)等高级接口,进一步简化了非阻塞IO的处理。
四. libevent解决了网络编程哪些痛点?
1.高效的网络缓冲区
在内核中有读缓冲区和写缓冲区,减少用户态和内核态的切换。
用户态读缓冲区的存在是为了处理粘包的问题,因为网络协议栈是不知道用户界定数据包的格式,没法确定一个完整的数据包。
用户态写缓冲区的存在是因为用户根本不清楚内核写缓存区的状态,需要把没有写出去的数据缓存起来等待下次写事件时把数据写出去。
buffer的设计有三种类型: (1)固定数组,固定长度。限定了处理数据包的能力,没有动态伸缩的能力;需要频繁挪动数据。 (2)ring buffer(环形缓冲区)。可伸缩性差。 (3)chain buffer(链式缓冲区)。解决可伸缩性差的问题,避免频繁挪动数据;同时也引进了新的问题,一个数据可能在多个buffer中都有,即数据分割,这会导致多次系统调用,从而引起中断上下文的切换。解决办法是使用readv()将内核中连续的buffer读到用户态不连续的buffer中,writev()把用户态不连续的buffer写到内核连续的buffer中;从而减少系统调用。
2.IO函数使用与网络原理
(1)有了libevent可以不使用IO函数。因为如果使用IO函数,既需要知道这些IO函数里面的系统调用返回值的含义。 (2)有了libevent就可以不清楚数据拷贝原理。 (3)有了libevent就可以不清楚网络原理以及网络编程流程。 (4)有了libevent只需要知道事件处理,IO操作完全交由libevent处理。
3.多线程
加锁的效果比较好。 一个线程尽量只处理一个reactor的事件。 (1)buffer加锁时,读要读出一个完整的数据包。 (2)buffer加锁时,写要写一个完整的数据包。
总结
-
libevent是一个事件通知库,目标是对事件编程;它封装了reactor。
-
reactor需要关注的几个点:IO分为IO检测(IO多路复用检测IO是否就绪)、IO操作;必须明白IO处理是同步到,而事件处理是异步的(注册事件和事件就绪时触发事件回调是不在同一个流程进行的)。
-
libevent使用层次有:在事件中自己处理IO和只需要处理业务逻辑(libevent内部处理IO)两种方式。
-
libevent的封装层次:通常,把事件对象绑定到reactor对象上面;需要熟悉事件操作、事件处理、事件循环。
-
libevent解决了网络编程中IO函数处理的痛点,因为多线程环境下很难处理IO函数的返回值,以及很难控制数据安全性。
6 .libevent具有高效的网络缓冲区,evbuffer采用chain buffer(链式缓冲区)类型的缓冲区,伸缩性好而且不需要频繁挪动数据