libevent, an event notification library

Table of Contents

1. libevent 简介

The libevent API provides a mechanism to execute a callback function when a specific event occurs on a file descriptor or after a timeout has been reached. Furthermore, libevent also support callbacks due to signals or regular timeouts.

libevent 是各种操作系统下不同 I/O 模型(如 select,poll,epoll,kqueue 等、Windows 的 IOCP 等)的封装,它提供了统一的 API,能简化程序开发过程。一般地,可以认为 libevent 是 Reactor 模式(如果使用 bufferevent 则也可以看作是 Proactor 模式)的实现。

libevent 有很多优点:

  • 轻量级,不像 ACE 那么庞大;
  • 支持多种 I/O 复用技术,如 select、epoll、poll、dev/poll、select 和 kqueue 等;
  • 开源跨平台,支持 Linux、*BSD、Mac OS 和 Windows 等系统;
  • 事件驱动方式(event-driven),性能高;

下面这些软件中使用了 libevent:

  • Google Chrome: Google's web browser (Mac and Linux versions)
  • Memcached: a high-performance, distributed memory object caching system
  • Transmission: an open-source BitTorrent client
  • ntpd: the Network Time Protocol daemon
  • Tor: an anonymous Internet communication system
  • tmux: a terminal multiplexer

参考:
http://libevent.org/
libevent Reference: http://www.wangafu.net/~nickm/libevent-2.0/doxygen/html/

2. 实例

在 Debian 系列操作系统中可以通过安装 libevent-dev 来准备开发环境:

$ sudo apt-get install libevent-dev

使用 libevent 编程,有三个主要步骤:
(1) Creating an event base (event_base_new())
(2) Event notification (event_new(), event_add())
(3) Dispaching events (event_base_dispatch())

参考:http://www.wangafu.net/~nickm/libevent-2.0/doxygen/html/

什么是事件集合(event base)?
Before you can use any interesting Libevent function, you need to allocate one or more event_base structures. Each event_base structure holds a set of events and can poll to determine which events are active.

Each event_base has a "method", or a backend that it uses to determine which events are ready. The recognized methods are:

  • select
  • poll
  • epoll
  • kqueue
  • devpoll
  • evport
  • win32

如何选择上面这些方法呢?使用 event_base_new() 创建 event base 时,会默认选择当前操作系统下最快的方法。

参考:http://www.wangafu.net/~nickm/libevent-book/Ref2_eventbase.html

2.1. 实例:小写字母转大写

下面是一个“小写字母转大写”的服务器实例。程序主要改编自:https://www.felix021.com/blog/read.php?2068

/*
 * 下面是一个“小字字母转大写”的程序实例
 * 程序改编自:https://www.felix021.com/blog/read.php?2068
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>

#include <event2/event.h>
#include <event2/bufferevent.h>

#define LISTEN_PORT     9999
#define LISTEN_BACKLOG  32

void accept_callback(evutil_socket_t listener, short event, void *arg);
void read_callback(struct bufferevent *bev, void *arg);
void write_callback(struct bufferevent *bev, void *arg);
void error_callback(struct bufferevent *bev, short event, void *arg);

int main(int argc, char *argv[])
{
  int ret;
  evutil_socket_t listener;
  listener = socket(AF_INET, SOCK_STREAM, 0);
  assert(listener > 0);
  evutil_make_listen_socket_reuseable(listener);

  struct sockaddr_in sin;
  sin.sin_family = AF_INET;
  sin.sin_addr.s_addr = 0;
  sin.sin_port = htons(LISTEN_PORT);

  if (bind(listener, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
    perror("bind");
    return 1;
  }

  if (listen(listener, LISTEN_BACKLOG) < 0) {
    perror("listen");
    return 1;
  }

  printf ("Listening, port is %d\n", LISTEN_PORT);

  evutil_make_socket_nonblocking(listener);

  /* 创建事件的集合对象 */
  struct event_base *base = event_base_new();
  assert(base != NULL);

  /* 新建事件
   * EV_PERSIST设置监听socket的读取事件(即新的客户连接)持续发生
   * 注册回调函数 accept_callback
   */
  struct event *listen_event;
  listen_event = event_new(base, listener, EV_READ|EV_PERSIST, accept_callback, (void*)base);

  /* 添加事件 */
  event_add(listen_event, NULL);

  /* 启动事件分派主循环 */
  event_base_dispatch(base);

  printf("The End.");
  return 0;
}

void accept_callback(evutil_socket_t listener, short event, void *arg)
{
  struct event_base *base = (struct event_base *)arg;
  evutil_socket_t fd;
  struct sockaddr_in sin;
  socklen_t slen = sizeof(sin);
  fd = accept(listener, (struct sockaddr *)&sin, &slen);
  if (fd < 0) {
    perror("accept");
    return;
  }

  printf("ACCEPT: fd = %u\n", fd);

  struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
  /* 为fd上“读、写和异常”注册不同回调函数 */
  bufferevent_setcb(bev, read_callback, write_callback, error_callback, arg);
  bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST);
}

void to_upper(char *p, int size)
{
  int i;
  for (i=0; i<size; i++) {
    if (p[i] >= 'a' && p[i] <= 'z') {
      p[i] = p[i] - ('a' - 'A');
    }
  }
}

void read_callback(struct bufferevent *bev, void *arg)
{
#define MAX_LINE    256
  char line[MAX_LINE+1];
  int n;
  evutil_socket_t fd = bufferevent_getfd(bev);

  while (n = bufferevent_read(bev, line, MAX_LINE), n > 0) {
    line[n] = '\0';
    printf("fd=%u, read line: %s", fd, line);

    /*  */
    to_upper(line, n);
    bufferevent_write(bev, line, n);
  }
}

void write_callback(struct bufferevent *bev, void *arg) {}

void error_callback(struct bufferevent *bev, short event, void *arg)
{
  evutil_socket_t fd = bufferevent_getfd(bev);
  printf("fd = %u, ", fd);
  if (event & BEV_EVENT_TIMEOUT) {
    printf("Timed out\n");    //if bufferevent_set_timeouts() called
  }
  else if (event & BEV_EVENT_EOF) {
    printf("connection closed\n");
  }
  else if (event & BEV_EVENT_ERROR) {
    printf("some other error\n");
  }
  bufferevent_free(bev);
}

编译并启动上面服务器程序后(注意编译时传递参数-levent 给链接器),服务端的日志的一个例子如下:

$ ./a.out
Listening, port is 9999
ACCEPT: fd = 7
fd=7, read line: dd
fd=7, read line: adfdfdf
fd=7, read line: 8883
fd=7, read line: fdfdf
ACCEPT: fd = 8
fd=8, read line: ldkfdf
fd = 8, connection closed
fd = 7, connection closed

进行上面测试时,同时使用了两个客户端,其中一个客户端的交互过程为:

$ nc localhost 9999
dd
DD
adfdfdf
ADFDFDF
8883
8883
fdfdf
FDFDF
^C

另一个客户端的交互过程为:

$ nc localhost 9999
ldkfdf
LDKFDF
^C

Author: cig01

Created: <2014-03-01 Sat>

Last updated: <2016-05-22 Sun>

Creator: Emacs 27.1 (Org mode 9.4)