Libevent 编程指南

如何安装

  Libevent 是一个高性能,跨平台的 C 语言网络库。Libevent 当前最新的版本是 2.1.8,下面是在 Linux 安装 Libevent 的步骤:

1
2
3
4
5
6
$ wget https://github.com/libevent/libevent/releases/download/release-2.1.8-stable/libevent-2.1.8-stable.tar.gz
$ tar -xzvf libevent-2.1.8-stable.tar.gz
$ cd libevent-2.1.8-stable
$ ./configure
$ make
$ sudo make install

基本概念

  Libevent是基于 Reactor 模式的网络库,在 Reactor 模式中,通常都有一个事件循环(Event Loop),在 Libevent 中,这个事件循环就是event_base结构体:

1
2
3
struct event_base *event_base_new(void); // 创建事件循环
void event_base_free(struct event_base *base); // 销毁事件循环
int event_base_dispatch(struct event_base *base); // 运行事件循环

  通常来说,事件循环主要有两个作用:

  • 用来管理事件,比如说添加我们感兴趣的事件,修改事件或删除事件。
  • 用来轮询它管理的所有事件,如果发现有事件活跃 (avtive),就调用相应的回调函数去处理事件。

  Libevent 使用event结构体来代表事件,可以使用event_new()创建一个事件:

1
2
3
4
5
struct event *event_new(struct event_base *base, // 事件循环
evutil_socket_t fd, // 文件描述符
short what, // 事件类型
event_callback_fn cb, // 回调函数
void *arg); // 传递给回调函数的参数

  创建一个事件之后,要怎么把它加入到事件循环呢?可以使用event_add()函数:

1
2
int event_add(struct event *ev, // 事件
const struct timeval *tv); // 超时时间

  默认情况下,当一个事件变得活跃时,Libevent 会执行这个事件的回调函数,但同时也会将这个事件从事件循环中移除,例如,下面的程序,定时器只会触发一次:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <event2/event.h>
#include <iostream>
#include <string>
void timer_cb(evutil_socket_t fd, short what, void *arg)
{
auto str = static_cast<std::string *>(arg);
std::cout << *str << std::endl;
}
int main()
{
std::string str = "Hello, World!";
auto *base = event_base_new();
struct timeval five_seconds = {1, 0};
auto *ev = event_new(base, -1, EV_TIMEOUT, timer_cb, (void *)&str);
event_add(ev, &five_seconds);
event_base_dispatch(base);
event_free(ev);
event_base_free(base);
return 0;
}

  那要怎么样才能让事件不被移除呢?当创建事件时,在事件类型加上EV_PERSIST就可以。让我们修改上面的程序,让定时器每秒就触发一次:

1
auto *ev = event_new(base, -1, EV_TIMEOUT|EV_PERSIST, timer_cb, (void *)&str);

TCP Server

  Libevent 使用evconnlistener结构来表示 TCP Server,创建 TCP Server 的做法很简单:

1
2
3
4
5
6
7
8
9
10
11
struct evconnlistener *evconnlistener_new_bind(
struct event_base *base, // 事件循环
evconnlistener_cb cb, // 回调函数,当 accept() 成功时会被调用
void *arg, // 传递给回调函数的参数
unsigned flags, // 选项
int backlog, // tcp backlog 参数
const struct sockaddr *sa, // 地址
int socklen
);
void evconnlistener_free(struct evconnlistener *lev);

  调用evconnlistener_new_bind()函数之后,listening socket 会自动被设置成非阻塞的。我们还通过flags参数设置一些有用的选项,例如:

  • LEV_OPT_CLOSE_ON_FREE表示当调用evconnlistener_free()时,相应的 listening socket 也会被close()掉。
  • LEV_OPT_REUSEABLE表示会自动对 listening socket 设置SO_REUSEADDR这个 TCP 选项。

  下面的程序创建了一个简单的 TCP Server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <event2/listener.h>
#include <arpa/inet.h>
#include <string.h>
#include <iostream>
void accept_conn_cb(struct evconnlistener *listener, evutil_socket_t fd,
struct sockaddr *address, int socklen, void *arg)
{
char addr[INET_ADDRSTRLEN];
auto *sin = reinterpret_cast<sockaddr_in *>(address);
inet_ntop(AF_INET, &sin->sin_addr, addr, INET_ADDRSTRLEN);
std::cout << "Accept TCP connection from: " << addr << std::endl;
}
void accept_error_cb(struct evconnlistener *listener, void *arg)
{
auto *base = evconnlistener_get_base(listener);
// 跨平台的错误处理
int err = EVUTIL_SOCKET_ERROR();
std::cerr << "Got an error on the listener: "
<< evutil_socket_error_to_string(err)
<< std::endl;
event_base_loopexit(base, NULL);
}
int main()
{
short port = 8000;
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(INADDR_ANY);
sin.sin_port = htons(port);
auto *base = event_base_new();
auto *listener = evconnlistener_new_bind(
base, accept_conn_cb, nullptr,
LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, -1,
reinterpret_cast<struct sockaddr *>(&sin), sizeof(sin)
);
if (listener == nullptr) {
std::cerr << "Couldn't create listener" << std::endl;
return 1;
}
evconnlistener_set_error_cb(listener, accept_error_cb);
event_base_dispatch(base);
return 0;
}

参考资料