Libevent 是一个用C语言编写的、轻量级的开源高性能网络库,主要有以下几个亮点:事件驱动( event-driven),高性能;轻量级,专注于网络,不如 ACE 那么臃肿庞大;源代码相当精炼、易读;跨平台,支持 Windows、 Linux、 *BSD 和 Mac Os;支持多种 I/O 多路复用技术, epoll、 poll、 dev/poll、 select 和 kqueue 等;支持 I/O,定时器和信号等事件;注册事件优先级。
Libevent 已经被广泛的应用,作为底层的网络库;比如 memcached、 Vomit、 Nylon、 Netchat等等。Libevent之于C语言网络编程,类似于Nettty之于Java Web编程。学习Netty的小伙伴,不防看下Libevent的实现,会加深对Netty框架的理解~
Libevent的安装教程网上较多,LZ在此就不再赘述,下面直接来点干货-Libevent如何使用。
检查Libevent支持的IO复用方法
Libevent作为一个高性能网络库,内部封装了多种IO复用技术,如果想看下Libevent在当前系统下支持哪些IO复用技术呢?
int main(int argc, char **argv) { // 版本信息 cout << event_get_version() << endl; // 所支持的IO复用方法 const char **methods = event_get_supported_methods(); for (int i = 0; methods[i] != NULL; i++) { cout << methods[i] << endl; } return 0; }
输出结果为:(Centos7 Clion 2016.1.3环境)
event_get_supported_methods()函数返回Libevent支持的IO复用方法名称数组,以NULL结尾。该函数实际返回的是全局变量eventops数组,eventops数组存放的是所有支持的IO复用函数,eventops声明部分的代码如下:
/* Array of backends in order of preference. */ /* Libevent通过遍历eventops数组来选择其后端IO复用技术,遍历的顺序是从数组的第一个元素开始, * 到最后一个元素结束。Linux系统下,默认选择的后端IO复用技术是epoll。*/ static const struct eventop *eventops[] = { #ifdef _EVENT_HAVE_EVENT_PORTS &evportops, #endif #ifdef _EVENT_HAVE_WORKING_KQUEUE &kqops, #endif #ifdef _EVENT_HAVE_EPOLL &epollops, #endif #ifdef _EVENT_HAVE_DEVPOLL &devpollops, #endif #ifdef _EVENT_HAVE_POLL &pollops, #endif #ifdef _EVENT_HAVE_SELECT &selectops, #endif #ifdef WIN32 &win32ops, #endif NULL };
Libevent是如何打日志的
libevent的错误处理底层调用的是va_start/va_end等相关宏,它们所在的头文件是<stdarg.h>,使用C函数库提供的这些函数,我们也可以实现一个自己的打日志程序,以下是一个使用va_start/va_end的测试程序:
void log(const char *fmt, ...) { char buff[512]; va_list ap; va_start(ap, fmt); int len = vsnprintf(buff, sizeof(buff), fmt, ap); buff[len] = '\0'; va_end(ap); cout << buff << endl; }
- va_start:宏定义,引用最后一个固定参数所以它能够对可变参数进行定位。
- va_end:宏定义,函数返回之前一定要调用va_end,这是因为某些实现在函数返回之前需要调整控制信息。
使用上述函数,我们就可以愉快地打日志了,比如按照如下形式来调用:
log("hi, are you %s?", "luxon28"); log("name=%s, age=%d", "luoxn28", 23);
更多va_start/va_end信息请点击:va_start和va_end使用详解 - Healtheon - 博客园。
定时器的使用
#include <iostream> #include <event.h> #include <event2/http.h> using namespace std; // Time callback function void onTime(int sock, short event, void *arg) { static int cnt = 0; cout << "Game Over! " << cnt++ << endl; struct timeval tv; tv.tv_sec = 1; tv.tv_usec = 0; if (cnt < 5) { // Add timer event event_add((struct event *) arg, &tv); } else { cout << "onTime is over" << endl; } } int main(int argc, char **argv) { cout << event_get_version() << endl; struct event_base *base = event_init(); struct event ev; evtimer_set(&ev, onTime, &ev); struct timeval timeevent; timeevent.tv_sec = 1; timeevent.tv_usec = 0; event_add(&ev, &timeevent); // Start event loop event_base_dispatch(base); event_base_free(base); return 0; }
输出结果如下:
LZ安装的是Libevent版本是2.0版本,event_init()函数初始化一个事件类结构体,其中已经选择好了IO复用函数,比如Linux下一般是epoll;初始化了一个事件活动队列,当事件发生时,会被加入到该事件活动队列中,然后统一执行事件活动队列中的所有事件(也就是调用对应的回调函数)。event_base结构体详细内容如下:
/* 结构体event_base是Libevent的Reactor */ struct event_base { /* 初始化Reactor时选择的一种后端IO复用机制,并记录在如下字段中 */ const struct eventop *evsel; /* 指向IO复用机制真正存储的数据,它通过evsel成员的init函数来进行初始化 */ void *evbase; /* 事件变化队列,其用途是:如果一个文件描述符上注册的事件被多次修改,则可以使用缓冲区来避免重复的 * 系统调用(比如epoll_wait)。它仅能用于时间复杂度为O(1)的IO复用技术 */ struct event_changelist changelist; /* 指向信号的后端处理机制,目前仅在singal.h文件中定义了一种处理方法 */ const struct eventop *evsigsel; /* 信号事件处理器使用的数据结构,其中封装了一个由socketpair创建的管道。它用于信号处理函数和 * 事件多路分发器之间的通信 */ struct evsig_info sig; /* 以下3个成员是添加到该event_base的虚拟事件、所有事件和激活事件的数量 */ int virtual_event_count; int event_count; int event_count_active; /* 是否执行完活动事件队列上的剩余的任务之后就退出事件处理 */ int event_gotterm; /* 是否立即退出事件循环,而不管是否还有任务需要处理 */ int event_break; /* 是否应该启动一个新的事件循环 */ int event_continue; /* 目前正在处理的活动事件队列的优先级 */ int event_running_priority; /* 事件循环是否启动 */ int running_loop; /* 活动事件队列数组,索引值越小的队列,优先级越高。高优先级的活动事件队列中的事件处理器将被优先处理 */ struct event_list *activequeues; /* 活动事件队列数组的大小,即该event_base共有nactivequeues个不同优先级的活动事件队列 */ int nactivequeues; /* common timeout logic */ /* 以下3个成员用于管理通用定时器队列 */ struct common_timeout_list **common_timeout_queues; int n_common_timeouts; int n_common_timeouts_allocated; /* 存放延时回调函数的链表,事件循环每次成功处理完一个活动事件队列中的所有事件之后, * 就调用一次延迟回调函数 */ struct deferred_cb_queue defer_queue; /* 文件描述符和IO事件之间的映射关系表 */ struct event_io_map io; /* 信号值和信号事件之间的映射关系表 */ struct event_signal_map sigmap; /* 注册事件队列,存放IO事件处理器和信号事件处理器 */ struct event_list eventqueue; struct timeval event_tv; /* 时间堆 */ struct min_heap timeheap; struct timeval tv_cache; #if defined(_EVENT_HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) /** Difference between internal time (maybe from clock_gettime) and * gettimeofday. */ struct timeval tv_clock_diff; /** Second in which we last updated tv_clock_diff, in monotonic time. */ time_t last_updated_clock_diff; #endif /* 多线程支持 */ #ifndef _EVENT_DISABLE_THREAD_SUPPORT /* threading support */ /** The thread currently running the event_loop for this base */ /* 当前运行该event_base的事件循环的线程 */ unsigned long th_owner_id; /** A lock to prevent conflicting accesses to this event_base */ void *th_base_lock; /* 锁变量 */ /** The event whose callback is executing right now */ /* 当前事件循环正在执行哪个事件处理器的回调函数 */ struct event *current_event; /** A condition that gets signalled when we're done processing an * event with waiters on it. */ /* 条件变量,用于唤醒正在等待某个事件处理完毕的线程 */ void *current_event_cond; /** Number of threads blocking on current_event_cond. */ int current_event_waiters; /* 等待current_event_cond的线程数 */ #endif /** Flags that this base was configured with */ /* 该vent_base的一些配置参数 */ enum event_base_config_flag flags; /* 下面这些成员变量给工作线程唤醒主线程提供了方法(使用socketpair创建的管道) */ /* Notify main thread to wake up break, etc. */ /** True if the base already has a pending notify, and we don't need * to add any more. */ int is_notify_pending; /** A socketpair used by some th_notify functions to wake up the main * thread. */ evutil_socket_t th_notify_fd[2]; /** An event used by some th_notify functions to wake up the main * thread. */ struct event th_notify; /** A function used to wake up the main thread from another thread. */ int (*th_notify_fn)(struct event_base *base); }
event_add()函数是往事件结构体中加入监听的一个事件,这里是定时事件,当定时事件到时,就会执行对应的回调函数。event_base_dispatch()函数开始执行事件监听,对应于epoll的话也就是调用epoll_wait了。最后,当程序执行完毕后,需要调用event_base_free()函数来执行资源的销毁操作,至此,整个定时器事件就执行完毕了。
简单的HTTP服务器
使用Libevent,我们可以用不超过50行代码实现一个简单的HTTP服务器程序,没有听错,就是几十行代码,不像Java那样,需要配置Tomcat,然后编写对应的Servet,配置web.xml等等(如果使用SSM或者SSH的话步骤或许更多一点呦 :( )。下面就是一个简单的HTTP服务器示例代码:
#include <iostream> #include <event2/event.h> #include <event2/buffer.h> #include <event2/http.h> using namespace std; #define INFO 1 #define ERR 3 static void log(int level, string info) { switch (level) { case INFO: cout << "[info] tid[" << pthread_self() << "]: " << info << endl; break; case ERR: cout << "[err] tid[" << pthread_self() << "]: " << info << endl; break; default: break; } } /** * http callback function */ void httpHandler(struct evhttp_request *request, void *arg) { struct evbuffer *buff = evbuffer_new(); if (!buff) { log(INFO, "evbuffer_new error"); return; } evbuffer_add_printf(buff, "Hello world</br>"); evbuffer_add_printf(buff, "Server Responsed.</br> Requested: %s<br/>", evhttp_request_get_uri(request)); evbuffer_add_printf(buff, " Host: %s<br/>", evhttp_request_get_host(request)); evbuffer_add_printf(buff, " Command: %d", evhttp_request_get_command(request)); evhttp_send_reply(request, HTTP_OK, "OK", buff); evbuffer_free(buff); } int main(int argc, char **argv) { struct event_base *base = event_base_new(); struct evhttp *httpServer = evhttp_new(base); int result = evhttp_bind_socket(httpServer, NULL, 8080); if (result != 0) { log(ERR, "evhttp_bind_socket error"); return -1; } /* 这是http回调函数 */ evhttp_set_gencb(httpServer, httpHandler, NULL); cout << "Http server start OK..." << endl; event_base_dispatch(base); evhttp_free(httpServer); event_base_free(base); return 0; }
访问页面如下,192.168.1.150主机是linux服务器。
看到这里,学习Java Web的小伙伴是不是觉得很熟悉,没错,就是像Servlet。LZ个人觉得,对于小型程序来说,使用C/C++的网络库编程程序更爽一点,因为更加"接地气 "一点,也就操作起来更加灵活,使用Java的话肯定要使用Servet容器了,比如Tomcat或者Jboss等,然后各种配置等。但是对于动态Web技术来说,使用Java更爽一点。
参考资料
2、Libevent部分源码