使用 libevent提高网络应用性能

来源:互联网 发布:田士永 知乎 编辑:程序博客网 时间:2024/04/30 06:41

处理多个客户端
处理多个连接有许多不同的传统方法,但是在处理大量连接时它们往往会产生问题,因为它们使用的内存或 CPU 太多,或者达到了某个操作系统限制。

使用的主要方法如下:

  • 循环:早期系统使用简单的循环选择解决方案,即循环遍历打开的网络连接的列表,判断是否有要读取的数据。这种方法既缓慢(尤其是随着连接数量增加越来越慢),又低效(因为在处理当前连接时其他连接可能正在发送请求并等待响应)。在系统循环遍历每个连接时,其他连接不得不等待。如果有 100 个连接,其中只有一个有数据,那么仍然必须处理其他 99 个连接,才能轮到真正需要处理的连接。
    poll、epoll 和变体:这是对循环方法的改进,它用一个结构保存要监视的每个连接的数组,当在网络套接字上发现数据时,通过回调机制调用处理函数。poll 的问题是这个结构会非常大,在列表中添加新的网络连接时,修改结构会增加负载并影响性能。

  • 选择:select() 函数调用使用一个静态结构,它事先被硬编码为相当小的数量(1024 个连接),因此不适用于非常大的部署。

  • 在各种平台上还有其他实现(比如 Solaris 上的 /dev/poll 或 FreeBSD/NetBSD 上的 kqueue),它们在各自的 OS 上性能可能更好,但是无法移植,也不一定能够解决处理请求的高层问题。

上面的所有解决方案都用简单的循环等待并处理请求,然后把请求分派给另一个函数以处理实际的网络交互。关键在于循环和网络套接字需要大量管理代码,这样才能监听、更新和控制不同的连接和接口。
处理许多连接的另一种方法是,利用现代内核中的多线程支持监听和处理连接,为每个连接启动一个新线程。这把责任直接交给操作系统,但是会在 RAM 和 CPU 方面增加相当大的开销,因为每个线程都需要自己的执行空间。另外,如果每个线程都忙于处理网络连接,线程之间的上下文切换会很频繁。最后,许多内核并不适于处理如此大量的活跃线程。

libevent 方法
libevent 库实际上没有更换 select()、poll() 或其他机制的基础。而是使用对于每个平台最高效的高性能解决方案在实现外加上一个包装器。

为了实际处理每个请求,libevent 库提供一种事件机制,它作为底层网络后端的包装器。事件系统让为连接添加处理函数变得非常简便,同时降低了底层 I/O 复杂性。这是 libevent 系统的核心。
libevent 库的其他组件提供其他功能,包括缓冲的事件系统(用于缓冲发送到客户端/从客户端接收的数据)以及 HTTP、DNS 和 RPC 系统的核心实现。

创建 libevent 服务器的基本方法是,注册当发生某一操作(比如接受来自客户端的连接)时应该执行的函数,然后调用主事件循环 event_dispatch()。执行过程的控制现在由 libevent 系统处理。注册事件和将调用的函数之后,事件系统开始自治;在应用程序运行时,可以在事件队列中添加(注册)或删除(取消注册)事件。事件注册非常方便,可以通过它添加新事件以处理新打开的连接,从而构建灵活的网络处理系统。

例如,可以打开一个监听套接字,然后注册一个回调函数,每当需要调用 accept() 函数以打开新连接时调用这个回调函数,这样就创建了一个网络服务器。清单 1 所示的代码片段说明基本过程:
清单 1. 打开监听套接字,注册一个回调函数(每当需要调用 accept() 函数以打开新连接时调用它),由此创建网络服务器

int main(int argc, char **argv){...    ev_init();    /* Setup listening socket */    event_set(&ev_accept, listen_fd, EV_READ|EV_PERSIST, on_accept, NULL);    event_add(&ev_accept, NULL);    /* Start the event loop. */    event_dispatch();}

event_set() 函数创建新的事件结构,event_add() 在事件队列机制中添加事件。然后,event_dispatch() 启动事件队列系统,开始监听(并接受)请求。

转载自:http://www.ibm.com/developerworks/cn/aix/library/au-libev/

0 0
原创粉丝点击