libevent示例
基本概念
Libevent是基于 Reactor
模式的网络库,在 Reactor 模式中,通常都有一个事件循环(Event Loop),在 Libevent 中,这个事件循环就是event_base结构体:
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()创建一个事件:
struct event *event_new(struct event_base *base, // 事件循环
evutil_socket_t fd, // 文件描述符
short what, // 事件类型
event_callback_fn cb, // 回调函数
void *arg); // 传递给回调函数的参数
创建一个事件之后,要怎么把它加入到事件循环呢?可以使用event_add()函数:
int event_add(struct event *ev, // 事件
const struct timeval *tv); // 超时时间
默认情况下,当一个事件变得活跃时,Libevent 会执行这个事件的回调函数,但同时也会将这个事件从事件循环中移除,例如,下面的程序,定时器只会触发一次:
#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);
//EV_PERSIST可以控制时间不被移除
auto *ev = event_new(base, -1, EV_TIMEOUT|EV_PERSIST, timer_cb, (void *)&str);
event_add(ev, &five_seconds);
event_base_dispatch(base);
event_free(ev);
event_base_free(base);
return 0;
}
TCP Server
Libevent 使用evconnlistener
结构来表示 TCP Server,创建 TCP Server 的做法很简单:
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 选项。
#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;
}