libevent学习
学习网站
epoll 学习
epoll 函数
- int epoll_create(int size); //size只要是正数就行
- int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); //epfd为epoll_create 返回的epoll文件描述符 op有EPOLL_CTL_ADD(注册新的fd到epfd)、EPOLL_CTL_MOD(修改已经注册的fd的监听事件)、EPOLL_CTL_DEL(epfd删除一个fd) fd要监听的文件描述符 event要监听文件描述符上的什么事件
- int epoll_wait(int epfd, struct epoll_event *event, int maxevents, int timeout); //epfd为epoll_create 返回的epoll文件描述符 event 内核将检测到的事件写入到此输出参数中 maxevents event的大小 timeout 最长等待事件发生事件,如果为-1则为一直等待
触发模式
- 水平触发模式:如果缓冲区里面有数据就会一直通知
- 边缘触发模式:当有事件到达只会通知一次,所以要一次读完所有数据
代码示例
点击查看代码
#include <iostream>
#include <sys/epoll.h>
#include <arpa/inet.h>
using namespace std;
int main()
{
int port = 8888;
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if(listen_fd == -1)
{
cout<<"socket error"<<endl;
return -1;
}
struct sockaddr_in saddr;
saddr.sin_addr.s_addr = INADDR_ANY;
//inet_pton(AF_INET, ip.c_str(), &saddr.sin_addr.s_addr);
saddr.sin_port = htons(port);
saddr.sin_family = AF_INET;
int ret = bind(listen_fd, (struct sockaddr *)&saddr, sizeof(saddr));
if(ret == -1)
{
cout<<"bind error"<<endl;
return -1;
}
ret = listen(listen_fd, 5);
if(ret == -1)
{
cout<<"listen error"<<endl;
return -1;
}
int epfd = epoll_create(5);
if(epfd == -1)
{
cout<<"epoll_create error"<<endl;
return -1;
}
struct epoll_event event;
event.events = EPOLLIN | EPOLLOUT;
event.data.fd = listen_fd;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &event);
if(ret == -1)
{
cout<<"epoll_ctl error"<<endl;
return -1;
}
const int maxfd = 1000;
struct epoll_event events[maxfd];
while (1) {
int count = epoll_wait(epfd, events, maxfd, -1);
if(count < 0)
{
cout<<"epoll_wait error"<<endl;
return -1;
}
for(int i = 0; i < count; i++)
{
if (events[i].data.fd == listen_fd) {
//accept. 并且将新accept 的fd 加进epoll中.
}
else if (events[i].events & EPOLLIN) {
//对此fd 进行读操作
}
else if (events[i].events & EPOLLOUT) {
//对此fd 进行写操作
}
}
}
cout << "Hello World!" << endl;
return 0;
}
reactor网络模型
模型图
单线程模型
多线程模型
五个参与者
- 描述符(handle):由操作系统提供,用于识别每一个事件,如Socket描述符、文件描述符等。
- 同步事件分离器(demultiplexer):是一个函数,用来等待一个或多个事件的发生。调用者会被阻塞,直到分离器分离的描述符集上有事件发生。linux下常见的有select、poll、epoll(linux)、kqueue(unix)
- 事件处理器接口(event handler):是由一个或多个模板函数组成的接口。这些模板函数描述了和应用程序相关的对某个事件的操作。
- 具体的事件处理器:是事件处理器接口的实现。它实现了应用程序提供的某个服务。每个具体的事件处理器总和一个描述符相关。它使用描述符来识别事件、识别应用程序提供的服务。
- Reactor 管理器(reactor):定义了一些接口,用于应用程序控制事件调度,以及应用程序注册、删除事件处理器和相关的描述符。它是事件处理器的调度核心。 Reactor管理器使用同步事件分离器来等待事件的发生。一旦事件发生,Reactor管理器先是分离每个事件,然后调度事件处理器,最后调用相关的模 板函数来处理这个事件。
代码示例(摘录自libevent深入浅出)
点击查看代码
#include <stdlib.h>
#include <stdio.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#define MAX_EVENTS 1024
#define BUFLEN 128
#define SERV_PORT 8080
/*
* status:1表示在监听事件中,0表示不在
* last_active:记录最后一次响应时间,做超时处理
*/
struct myevent_s {
int fd; //cfd listenfd
int events; //EPOLLIN EPLLOUT
void *arg; //指向自己结构体指针
void (*call_back)(int fd, int events, void *arg);
int status;
char buf[BUFLEN];
int len;
long last_active;
};
int g_efd; /* epoll_create返回的句柄 */
struct myevent_s g_events[MAX_EVENTS+1]; /* +1 最后一个用于 listen fd */
void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg)
{
ev->fd = fd;
ev->call_back = call_back;
ev->events = 0;
ev->arg = arg;
ev->status = 0;
//memset(ev->buf, 0, sizeof(ev->buf));
//ev->len = 0;
ev->last_active = time(NULL);
return;
}
void recvdata(int fd, int events, void *arg);
void senddata(int fd, int events, void *arg);
void eventadd(int efd, int events, struct myevent_s *ev)
{
struct epoll_event epv = {0, {0}};
int op;
epv.data.ptr = ev;
epv.events = ev->events = events;
if (ev->status == 1) {
op = EPOLL_CTL_MOD;
}
else {
op = EPOLL_CTL_ADD;
ev->status = 1;
}
if (epoll_ctl(efd, op, ev->fd, &epv) < 0)
printf("event add failed [fd=%d], events[%d]\n", ev->fd, events);
else
printf("event add OK [fd=%d], op=%d, events[%0X]\n", ev->fd, op, events);
return;
}
void eventdel(int efd, struct myevent_s *ev)
{
struct epoll_event epv = {0, {0}};
if (ev->status != 1)
return;
epv.data.ptr = ev;
ev->status = 0;
epoll_ctl(efd, EPOLL_CTL_DEL, ev->fd, &epv);
return;
}
void acceptconn(int lfd, int events, void *arg)
{
struct sockaddr_in cin;
socklen_t len = sizeof(cin);
int cfd, i;
if ((cfd = accept(lfd, (struct sockaddr *)&cin, &len)) == -1) {
if (errno != EAGAIN && errno != EINTR) {
/* 暂时不做出错处理 */
}
printf("%s: accept, %s\n", __func__, strerror(errno));
return;
}
do {
for (i = 0; i < MAX_EVENTS; i++) {
if (g_events[i].status == 0)
break;
}
if (i == MAX_EVENTS) {
printf("%s: max connect limit[%d]\n", __func__, MAX_EVENTS);
break;
}
int flag = 0;
if ((flag = fcntl(cfd, F_SETFL, O_NONBLOCK)) < 0)
{
printf("%s: fcntl nonblocking failed, %s\n", __func__, strerror(errno));
break;
}
eventset(&g_events[i], cfd, recvdata, &g_events[i]);
eventadd(g_efd, EPOLLIN, &g_events[i]);
} while(0);
printf("new connect [%s:%d][time:%ld], pos[%d]\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), g_events[i].last_active, i);
return;
}
void recvdata(int fd, int events, void *arg)
{
struct myevent_s *ev = (struct myevent_s *)arg;
int len;
len = recv(fd, ev->buf, sizeof(ev->buf), 0);
eventdel(g_efd, ev);
if (len > 0) {
ev->len = len;
ev->buf[len] = '\0';
printf("C[%d]:%s\n", fd, ev->buf);
/* 转换为发送事件 */
eventset(ev, fd, senddata, ev);
eventadd(g_efd, EPOLLOUT, ev);
}
else if (len == 0) {
close(ev->fd);
/* ev-g_events 地址相减得到偏移元素位置 */
printf("[fd=%d] pos[%d], closed\n", fd, (int)(ev - g_events));
}
else {
close(ev->fd);
printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
}
return;
}
void senddata(int fd, int events, void *arg)
{
struct myevent_s *ev = (struct myevent_s *)arg;
int len;
len = send(fd, ev->buf, ev->len, 0);
//printf("fd=%d\tev->buf=%s\ttev->len=%d\n", fd, ev->buf, ev->len);
//printf("send len = %d\n", len);
eventdel(g_efd, ev);
if (len > 0) {
printf("send[fd=%d], [%d]%s\n", fd, len, ev->buf);
eventset(ev, fd, recvdata, ev);
eventadd(g_efd, EPOLLIN, ev);
}
else {
close(ev->fd);
printf("send[fd=%d] error %s\n", fd, strerror(errno));
}
return;
}
void initlistensocket(int efd, short port)
{
int lfd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(lfd, F_SETFL, O_NONBLOCK);
eventset(&g_events[MAX_EVENTS], lfd, acceptconn, &g_events[MAX_EVENTS]);
eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]);
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons(port);
bind(lfd, (struct sockaddr *)&sin, sizeof(sin));
listen(lfd, 20);
return;
}
int main(int argc, char *argv[])
{
unsigned short port = SERV_PORT;
if (argc == 2)
port = atoi(argv[1]);
g_efd = epoll_create(MAX_EVENTS+1);
if (g_efd <= 0)
printf("create efd in %s err %s\n", __func__, strerror(errno));
initlistensocket(g_efd, port);
/* 事件循环 */
struct epoll_event events[MAX_EVENTS+1];
printf("server running:port[%d]\n", port);
int checkpos = 0, i;
while (1) {
/* 超时验证,每次测试100个链接,不测试listenfd 当客户端60秒内没有和服务器通信,则关闭此客户端链接 */
long now = time(NULL);
for (i = 0; i < 100; i++, checkpos++) {
if (checkpos == MAX_EVENTS)
checkpos = 0;
if (g_events[checkpos].status != 1)
continue;
long duration = now - g_events[checkpos].last_active;
if (duration >= 60) {
close(g_events[checkpos].fd);
printf("[fd=%d] timeout\n", g_events[checkpos].fd);
eventdel(g_efd, &g_events[checkpos]);
}
}
/* 等待事件发生 */
int nfd = epoll_wait(g_efd, events, MAX_EVENTS+1, 1000);
if (nfd < 0) {
printf("epoll_wait error, exit\n");
break;
}
for (i = 0; i < nfd; i++) {
struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr;
if ((events[i].events & EPOLLIN) && (ev->events & EPOLLIN)) {
ev->call_back(ev->fd, events[i].events, ev->arg);
}
if ((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT)) {
ev->call_back(ev->fd, events[i].events, ev->arg);
}
}
}
/* 退出前释放所有资源 */
return 0;
}
event_base
作用
event_base 相当于是一个底座,只要向底座上插入事件,然后不断的监控事件,等待事件发生调用回调函数即可
创建event_base
创建默认event_base
- struct event_base *event_base_new(void); //函数分配并且返回一个新的具有默认设置的 event_base。函数会检测环境变量,创建成功返回一个event_base的指针,失败返回NULL,选择各种方法时,函数会选择 OS 支持的最快方法。
创建复杂event_base
如果要对event_base有更多控制,就需要使用event_config
event_config的使用
- struct event_config *event_config_new(void); //创建event_config
- //对 event_config 调用其它函数,设置所需要的 event_base 特征
- struct event_base *event_base_new_with_config(const struct event_config *cfg); //使用event_config对象cfg创建event_base
- void event_config_free(struct event_config *cfg); //释放创建的event_config
对event_config的设置
- int event_config_avoid_method(struct event_config *cfg, const char *method); //可以通过名字让 libevent 避免使用特定的可用后端
- int event_config_require_features(struct event_config *cfg, enum event_method_feature feature); //让 libevent 不使用不能提供所有指定特征的后端。 enum event_method_feature {EV_FEATURE_ET = 0x01 (要求支持边沿触发的后端), EV_FEATURE_O1 = 0x02(要求添加、删除单个事件,或者确定哪个事件激活的操作是 O(1)复杂度的后端), EV_FEATURE_FDS = 0x04(要求支持任意文件描述符,而不仅仅是套接字的后端)};
- int event_config_set_flag(struct event_config cfg, enum event_base_config_flag flag); //让 libevent 在创建 event_base 时设置一个或者多个将在下面介绍的运行时标志。enum event_base_config_flag {EVENT_BASE_FLAG_NOLOCK = 0x01(不要为 event_base 分配锁), EVENT_BASE_FLAG_IGNORE_ENV = 0x02(选择使用的后端时,不要检测 EVENT 环境 变量,使用时要注意), EVENT_BASE_FLAG_STARTUP_IOCP = 0x04(仅用于 Windows,让 libevent 在启动时就 启用任何必需的 IOCP 分发逻辑,而不是按需启用), EVENT_BASE_FLAG_NO_CACHE_TIME = 0x08(不是在事件循环每次准备执行超时回调时 检测当前时间,而是在每次超时回调后进行检测), EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST = 0x10(告诉 libevent ,如果决定使 用 epoll 后端,可以安全地使用更快的基于 changelist 的后端), EVENT_BASE_FLAG_PRECISE_TIMER = 0x20(环境变量来 打开 epoll-changelist 选项)};
检查event_base后端
- const char **event_get_supported_methods(void); //函数返回一个指针 ,指向 libevent 支持的方法名字数组 。 这个数组的最后一个元素是 NULL。
点击查看示例代码
int i;
const char **methods = event_get_supported_methods();
printf("Starting Libevent %s. Available methods are:\n",
event_get_version());
for (i=0; methods[i] != NULL; ++i) {
printf(" %s\n", methods[i]);
}
- const char *event_base_get_method(const struct event_base *base); //返回 event_base 正在使用的方法。 enum event_method_feature event_base_get_features(const struct event_base *base); //返回 event_base 支持的特征的比特掩码。
点击查看示例代码
struct event_base *base;
enum event_method_feature f;
base = event_base_new();
if (!base) {
puts("Couldn't get an event_base!");
} else {
printf("Using Libevent with backend method %s.",
event_base_get_method(base));
f = event_base_get_features(base);
if ((f & EV_FEATURE_ET))
printf(" Edge-triggered events are supported.");
if ((f & EV_FEATURE_O1))
printf(" O(1) event notification is supported.");
if ((f & EV_FEATURE_FDS))
printf(" All FD types are supported.");
puts("");
}
释放event_base
void event_base_free(struct event_base *base);
设置event_base的优先级
- event_base 默认支持单个优先级
- int event_base_priority_init(struct event_base *base, int n_priorities); //base 是要修改的 event_base,n_priorities 是要支 持的优先级数目,这个数目至少是 1 。每个新的事件可用的优先级将从 0 (最高) 到 n_priorities-1(最低)。常量 EVENT_MAX_PRIORITIES 表示 n_priorities 的上限。
event_base 与 fork
当调用系统函数创建新的进程,如果在新进程中仍想使用event_base则需要重新初始化event_base;
- int event_reinit(struct event_base *base); //传入需要继续使用的event_base句柄
事件循环event_loop
运行循环
- int event_base_loop(struct event_base *base, int flags); //默认情况下,event_base_loop()函数运行 event_base 直到其中没有已经注册的事件为止。 flags:EVLOOP_NO_EXIT_ON_EMPTY 0x04(一直循环直到没有已经注册的事件) EVLOOP_ONCE 0x01(循环将等待某些事件成为激活的 ,执行激活的事件直到没有更多的事件可以执行,然会返回) EVLOOP_NONBLOCK 0x02(循环不会等待事件被触发: 循环将仅仅检测是否有事件已经就绪,可以立即触发,如果有,则执行事件的回调)
- int event_base_dispatch(struct event_base *base); //一直循环直到没有已经注册的事件为止,,或者调用 了 event_base_loopbreak()或者 event_base_loopexit()为止。 (此为老版本调用)
停止循环
函数
- int event_base_loopexit(struct event_base *base, const struct timeval *tv); //让 event_base 在给定时间之后停止循环。如果 tv 参数为 NULL, event_base 会立即停止循环,没有延时。如果正在执行回调函数,则在执行完所有激活事件后退出
- int event_base_loopbreak(struct event_base *base); //让 event_base 立即退出循环。它与 event_base_loopexit (base,NULL)的不同在于,如果 event_base 当前正在执行激活事件的回调 ,它将在执行完当前正在处理的事件后立即退出。
查看调用那个退出函数
- int event_base_got_exit(struct event_base *base); //如果是调用event_base_loopexit则返回true,否则返回false
- int event_base_got_break(struct event_base *base); //如果是调用event_base_loopbreak则返回true,否则返回false
转储event_base的状态
- void event_base_dump_events(struct event_base *base, FILE *f); //将加入到 event_base 的事件及其状态的完整列表,存储到f指向的文件
事件event
libevent 的基本操作单元是事件。每个事件代表一组条件的集合,这些条件包括:文件描述符已经就绪,可以读取或者写入、文件描述符变为就绪状态,可以读取或者写入(仅对于边沿触发 IO)、超时事件、发生某信号、用户触发事件
事件生命周期
- 已初始化(initialized)状态:调用 libevent 函数设置事件并且关联到 event_base
- 未决(pending)状态:将事件添加到 event_base
- 激活(active)状态:在未决状态下,如果触发事件的条件发生(比如说,文件描述符的状态改变,或者超时时间到达,(用户提供的)事件回调函数将被执行,如果配置为“持久的(persistent)”,事件将保持为未决状态,否则, 执行完回调后,事件不再是未决的。删除操作可以让未决事件成为非未决(已初始化)的。
创建事件
生成新事件
- typedef void (*event_callback_fn)(evutil_socket_t, short, void *); //回调函数的声明
- struct event *event_new(struct event_base *base, evutil_socket_t fd, short what, event_callback_fn cb, void *arg); //event_new()试图分配和构造一个用于 base 的新的事件。 what 参数是上述标志的集合。(EV_TIMEOUT 0x01,EV_READ 0x02,EV_WRITE 0x04,EV_SIGNAL 0x08,EV_PERSIST 0x10,EV_ET 0x20)如果 fd 非负,则它是将被观察其读写事件的文件。事件被激活时, libevent 将调用 cb 函数,传递这些参数:文件描述符 fd,表示所有被触发事件的位字段 ,以及构造事件时的 arg 参数。发生内部错误,或者传入无效参数时, event_new()将返回 NULL。
- void event_free(struct event *event); //对未决或者激活状态的事件调用 event_free()是安全 的:在释放事件之前,函数将会使事件成为非激活和非未决的。
点击查看示例代码
void cb_func(evutil_socket_t fd, short what, void *arg)
{
const char *data = (const char *)arg;
printf("Got an event on socket %d:%s%s%s%s [%s]",
(int) fd,
(what&EV_TIMEOUT) ? " timeout" : "",
(what&EV_READ) ? " read" : "",
(what&EV_WRITE) ? " write" : "",
(what&EV_SIGNAL) ? " signal" : "",
data);
}
void main_loop(evutil_socket_t fd1, evutil_socket_t fd2)
{
struct event *ev1, *ev2;
struct timeval five_seconds = {5,0};
struct event_base *base = event_base_new();
/* The caller has already set up fd1, fd2 somehow, and make them
nonblocking. */
ev1 = event_new(base, fd1, EV_TIMEOUT|EV_READ|EV_PERSIST, cb_func,
(char*)"Reading event");
ev2 = event_new(base, fd2, EV_WRITE|EV_PERSIST, cb_func,
(char*)"Writing event");
event_add(ev1, &five_seconds);
event_add(ev2, NULL);
event_base_dispatch(base);
}
事件标志
- EV_TIMEOUT //这个标志表示某超时时间流逝后事件成为激活的。构造事件的时候,EV_TIMEOUT 标志是被忽略的:可以在添加事件的时候设置超时 ,也可以不设置。超时发生时,回调函数的 what 参数将带有这个标志。
- EV_READ //表示指定的文件描述符已经就绪,可以读取的时候,事件将成为激活的。
- EV_WRITE //表示指定的文件描述符已经就绪,可以写入的时候,事件将成为激活的。
- EV_SIGNAL //用于实现信号检测
- EV_PERSIST //表示事件是“持久的”
- EV_ET //表示如果底层的 event_base 后端支持边沿触发事件,则事件应该是边沿触发的。这个标志 影响 EV_READ 和 EV_WRITE 的语义
事件持久性
- 默认情况下,每当未决事件成为激活的(因为 fd 已经准备好读取或者写入,或者因为超时), 事件将在其回调被执行前成为非未决的。如果想让事件再次成为未决的 ,可以在回调函数中 再次对其调用 event_add()。
- 然而,如果设置了 EV_PERSIST 标志,事件就是持久的。这意味着即使其回调被激活 ,事件还是会保持为未决状态 。如果想在回调中让事件成为非未决的 ,可以对其调用 event_del ()。
- 每次执行事件回调的时候,持久事件的超时值会被复位。因此,如果具有 EV_READ|EV_PERSIST 标志,以及5秒的超时值,则事件将在以下情况下成为激活的: 套接字已经准备好被读取的时候、 从最后一次成为激活的开始,已经逝去 5秒
信号事件
libevent 也可以监测 POSIX 风格的信号。要构造信号处理器,使用:#define evsignal_new(base, signum, cb, arg) event_new(base, signum, EV_SIGNAL|EV_PERSIST, cb, arg)除了提供一个信号编号代替文件描述符之外,各个参数与 event_new()相同。
点击查看示例代码
struct event *hup_event;
struct event_base *base = event_base_new();
/* call sighup_function on a HUP signal */
hup_event = evsignal_new(base, SIGHUP, sighup_function, NULL);
libevent提供的处理信号的宏
- #define evsignal_add(ev, tv) event_add((ev),(tv))
- #define evsignal_del(ev) event_del(ev)
- #define evsignal_pending(ev, what, tv_out) event_pending((ev), (what), (tv_out))
事件的未决和非未决
构造事件之后,在将其添加到 event_base 之前实际上是不能对其做任何操作的。使用event_add()将事件添加到 event_base。
设置未决事件
- int event_add(struct event *ev, const struct timeval *tv); //在非未决的事件上调用 event_add()将使其在配置的 event_base 中成为未决的。成功时 函数返回0,失败时返回-1。如果 tv 为 NULL,添加的事件不会超时。否则, tv 以秒和微秒指定超时值。如果在未决事件上调用event_add()将保持未决状态,并在指定的超时时间被重新调度
设置非未决事件
- int event_del(struct event *ev); //对已经初始化的事件调用 event_del()将使其成为非未决和非激活的。如果事件不是未决的或者激活的,调用将没有效果。成功时函数返回 0,失败时返回-1。(如果在事件激活后,其回调被执行前删除事件,回调将不会执行。)
事件的优先级
- int event_priority_set(struct event *event, int priority); //在将事件添加进event_base前设置event的优先级,priority的取值为0到event_base的priority-1,如果成功返回0,失败返回-1;当有不同优先级的事件同时触发时,优先执行优先级高的事件,当所有优先级高的事件执行完后才执行优先级低的事件;如果不为事件设置优先级,则默认的优先级将会是 event_base 的优先级数目除以2。
点击查看代码
#include <event2/event.h>
void read_cb(evutil_socket_t, short, void *);
void write_cb(evutil_socket_t, short, void *);
void main_loop(evutil_socket_t fd)
{
struct event *important, *unimportant;
struct event_base *base;
base = event_base_new();
event_base_priority_init(base, 2);
/* Now base has priority 0, and priority 1 */
important = event_new(base, fd, EV_WRITE|EV_PERSIST, write_cb, NULL);
unimportant = event_new(base, fd, EV_READ|EV_PERSIST, read_cb, NULL);
event_priority_set(important, 0);
event_priority_set(unimportant, 1);
/* Now, whenever the fd is ready for writing, the write callback will
happen before the read callback. The read callback won't happen at
all until the write callback is no longer active. */
}
检测事件状态
- int event_pending(const struct event *ev, short what, struct timeval *tv_out); //确定给定的事件是否是未决的或者激活的。如果是,而且 what 参 数设置了 EV_READ、EV_WRITE、EV_SIGNAL 或者 EV_TIMEOUT 等标志,则函数会返回事件当前为之未决或者激活的所有标志 。如果提供了 tv_out 参数,并且 what 参数中设置了 EV_TIMEOUT 标志,而事件当前正因超时事件而未决或者激活,则 tv_out 会返回事件 的超时值
-
define event_get_signal(ev) /* ... */ //返回为事件配置的信号值
- evutil_socket_t event_get_fd(const struct event *ev); //返回为事件配置的文件描述符
- struct event_base *event_get_base(const struct event *ev); //返回为事件配置的 event_base
- short event_get_events(const struct event *ev); //返回事件的标志(EV_READ、EV_WRITE 等)
- event_callback_fn event_get_callback(const struct event *ev); //返回事件的回调函数
- void *event_get_callback_arg(const struct event *ev); //返回事件的回调函数参数指针
- int event_get_priority(const struct event *ev); //返回事件的优先级
- void event_get_assignment(const struct event *event, struct event_base **base_out, evutil_socket_t *fd_out, short *events_out, event_callback_fn *callback_out, void **arg_out); //复制所有为事件分配的字段到提供的指针中。任何为 NULL 的参数会被忽略。
点击查看示例代码
#include <event2/event.h>
#include <stdio.h>
/* Change the callback and callback_arg of 'ev', which must not be
* pending. */
int replace_callback(struct event *ev, event_callback_fn new_callback,
void *new_callback_arg)
{
struct event_base *base;
evutil_socket_t fd;
short events;
int pending;
pending = event_pending(ev, EV_READ|EV_WRITE|EV_SIGNAL|EV_TIMEOUT,
NULL);
if (pending) {
/* We want to catch this here so that we do not re-assign a
* pending event. That would be very very bad. */
fprintf(stderr,
"Error! replace_callback called on a pending event!\n");
return -1;
}
event_get_assignment(ev, &base, &fd, &events,
NULL /* ignore old callback */ ,
NULL /* ignore old callback argument */);
event_assign(ev, base, fd, events, new_callback, new_callback_arg);
return 0;
}
一次触发事件
- int event_base_once(struct event_base , evutil_socket_t, short, void ()(evutil_socket_t, short, void *), void *, const struct timeval *); //除了不支持 EV_SIGNAL 或者 EV_PERSIST 之外,这个函数的接口与 event_new()相同。 安排的事件将以默认的优先级加入到 event_base并执行。回调被执行后,libevent内部将 会释放 event 结构。成功时函数返回0,失败时返回-1。不能删除或者手动激活使用 event_base_once ()插入的事件:如果希望能够取消事件, 应该使用 event_new()或者 event_assign()。
手动激活事件
- void event_active(struct event *ev, int what, short ncalls); //这个函数让事件 ev 带有标志 what(EV_READ、EV_WRITE 和 EV_TIMEOUT 的组合)成为激活的。事件不需要已经处于未决状态,激活事件也不会让它成为未决的。
数据缓冲Bufferevent
描述
- 很多时候,除了响应事件之外,应用还希望做一定的数据缓冲。比如说,写入数据的时候 ,通常的运行模式是:决定要向连接写入一些数据,把数据放入到缓冲区中、等待连接可以写入、写入尽量多的数据、记住写入了多少数据,如果还有更多数据要写入,等待连接再次可以写入、这种缓冲 IO 模式很通用,libevent 为此提供了一种通用机制,即bufferevent。
- 共享公用接口的 bufferevent 类型:基于套接字的 bufferevent:使用 event_*接口作为后端,通过底层流式套接字发送或者接收数据的 bufferevent、异步 IO bufferevent:使用 Windows IOCP 接口,通过底层流式套接字发送或者接收数据的 bufferevent(仅用于 Windows,试验中)、过滤型 bufferevent:将数据传输到底层 bufferevent 对象之前,处理输入或者输出数据的 bufferevent:比如说,为了压缩或者转换数据。、成对的 bufferevent:相互传输数据的两个 bufferevent。
- bufferevent和evbuffer:每个 bufferevent 都有一个输入缓冲区和一个输出缓冲区 ,它们的类型都是“struct evbuffer”。 有数据要写入到 bufferevent 时,添加数据到输出缓冲区 ;bufferevent 中有数据供读取的时候,从输入缓冲区抽取(drain)数据。
回调和watermarks
- 每个 bufferevent 有两个数据相关的回调:一个读取回调和一个写入回调。
- 默认情况下,从底层传输端口读取了任意量的数据之后会调用读取回调 ;
- 输出缓冲区中足够量的数据被清空到底层传输端口后写入回调会被调用。
- 通过调整 bufferevent 的读取和写入 “水位 (watermarks )”可以覆盖这些函数的默认行为。
- 读取低水位 :读取操作使得输入缓冲区的数据量在此级别或者更高时 ,读取回调将被调用。默认值为 0,所以每个读取操作都会导致读取回调被调用。
- 读取高水位 :输入缓冲区中的数据量达到此级别后, bufferevent 将停止读取,直到输入缓冲区中足够量的数据被抽取 ,使得数据量低于此级别 。默认值是无限 ,所以永远不会因为输入缓冲区的大小而停止读取。
- 写入低水位 :写入操作使得输出缓冲区的数据量达到或者低于此级别时 ,写入回调将被调用。默认值是 0,所以只有输出缓冲区空的时候才会调用写入回调。
- 写入高水位 :bufferevent 没有直接使用这个水位。它在 bufferevent 用作另外一 个 bufferevent 的底层传输端口时有特殊意义。请看后面关于过滤型 bufferevent 的介绍。
延迟回调
- 默认情况下,bufferevent 的回调在相应的条件发生时立即被执行 。(evbuffer 的回调也是这样的,随后会介绍)在依赖关系复杂的情况下 ,这种立即调用会制造麻烦 。比如说,假如某个回调在 evbuffer A 空的时候向其中移入数据 ,而另一个回调在 evbuffer A 满的时候从中取出数据。这些调用都是在栈上发生的,在依赖关系足够复杂的时候,有栈溢出的风险。要解决此问题,可以请求 bufferevent(或者 evbuffer)延迟其回调。条件满足时,延迟回调不会立即调用,而是在 event_loop()调用中被排队,然后在通常的事件回调之后执行 。默认情况下,bufferevent 的回调在相应的条件发生时立即被执行 。(evbuffer 的回调也是这样的,随后会介绍)在依赖关系复杂的情况下 ,这种立即调用会制造麻烦 。比如说,假如某个回调在 evbuffer A 空的时候向其中移入数据 ,而另一个回调在 evbuffer A 满的时候从中取出数据。这些调用都是在栈上发生的,在依赖关系足够复杂的时候,有栈溢出的风险。要解决此问题,可以请求 bufferevent(或者 evbuffer)延迟其回调。条件满足时,延迟回调不会立即调用,而是在 event_loop()调用中被排队,然后在通常的事件回调之后执行 。
bufferevent 选项标志
- 创建 bufferevent 时可以使用一个或者多个标志修改其行为。可识别的标志有:
- BEV_OPT_CLOSE_ON_FREE :释放 bufferevent 时关闭底层传输端口。这将关闭底层套接字,释放底层 bufferevent 等。
- BEV_OPT_THREADSAFE :自动为 bufferevent 分配锁,这样就可以安全地在多个线程中使用 bufferevent。
- BEV_OPT_DEFER_CALLBACKS :设置这个标志时, bufferevent 延迟所有回调,如上所述。
- BEV_OPT_UNLOCK_CALLBACKS :默认情况下,如果设置 bufferevent 为线程安全 的,则 bufferevent 会在调用用户提供的回调时进行锁定。设置这个选项会让 libevent 在执行回调的时候不进行锁定。
使用bufferevent
基于套接字的 bufferevent 是最简单的,它使用 libevent 的底层事件机制来检测底层网络套接字是否已经就绪,可以进行读写操作,并且使用底层网络调用(如 readv 、 writev 、 WSASend、WSARecv)来发送和接收数据。
创建基于套接字的bufferevent
- struct bufferevent *bufferevent_socket_new(struct event_base *base, evutil_socket_t fd, enum bufferevent_options options); //创建基于套接字的 bufferevent,base 是 event_base,options 是表示 bufferevent 选项(BEV_OPT_CLOSE_ON_FREE 等) 的位掩码, fd是一个可选的表示套接字的文件描述符。如果想以后设置文件描述符,可以设置fd为-1。成功时函数返回一个 bufferevent,失败则返回 NULL。
在bufferevent上启动链接
- int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *address, int addrlen); //address 和 addrlen 参数跟标准调用 connect()的参数相同。如果还没有为 bufferevent 设置套接字,调用函数将为其分配一个新的流套接字,并且设置为非阻塞的。如果已经为 bufferevent 设置套接字,调用bufferevent_socket_connect() 将告知 libevent 套接字还未连接,直到连接成功之前不应该对其进行读取或者写入操作。连接完成之前可以向输出缓冲区添加数据。如果连接成功启动,函数返回 0;如果发生错误则返回 -1。
点击查看示例代码
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <sys/socket.h>
#include <string.h>
void eventcb(struct bufferevent *bev, short events, void *ptr)
{
if (events & BEV_EVENT_CONNECTED) {
/* We're connected to 127.0.0.1:8080. Ordinarily we'd do
something here, like start reading or writing. */
} else if (events & BEV_EVENT_ERROR) {
/* An error occured while connecting. */
}
}
int main_loop(void)
{
struct event_base *base;
struct bufferevent *bev;
struct sockaddr_in sin;
base = event_base_new();
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */
sin.sin_port = htons(8080); /* Port 8080 */
bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev, NULL, NULL, eventcb, NULL);
if (bufferevent_socket_connect(bev,
(struct sockaddr *)&sin, sizeof(sin)) < 0) {
/* Error starting connection */
bufferevent_free(bev);
return -1;
}
event_base_dispatch(base);
return 0;
}
释放bufferevent操作
- void bufferevent_free(struct bufferevent *bev); //这个函数释放 bufferevent。bufferevent 内部具有引用计数,所以,如果释放时还有未决的延迟回调,则在回调完成之前 bufferevent 不会被删除。如果设置了 BEV_OPT_CLOSE_ON_FREE 标志,并且 bufferevent 有一个套接字或者底层 bufferevent 作为其传输端口,则释放 bufferevent 将关闭这个传输端口
操作回调、水位(watermarks)和启用/禁用
- typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);
- typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short events, void *ctx);
- void bufferevent_setcb(struct bufferevent *bufev, bufferevent_data_cb readcb, bufferevent_data_cb writecb, bufferevent_event_cb eventcb, void *cbarg); //bufferevent_setcb()函数修改 bufferevent 的一个或者多个回调 。readcb、writecb和eventcb函数将分别在已经读取足够的数据 、已经写入足够的数据 ,或者发生错误时被调用 。每个回调函数的第一个参数都是发生了事件的bufferevent ,最后一个参数都是调用bufferevent_setcb()时用户提供的 cbarg 参数:可以通过它向回调传递数据。事件回调 的 events 参数是一个表示事件标志的位掩码:请看前面的 “回调和水位”节。要禁用回调,传递 NULL 而不是回调函数 。注意:bufferevent 的所有回调函数共享单个 cbarg, 所以修改它将影响所有回调函数。
- void bufferevent_getcb(struct bufferevent *bufev, bufferevent_data_cb *readcb_ptr, bufferevent_data_cb *writecb_ptr, bufferevent_event_cb *eventcb_ptr, void **cbarg_ptr);
- void bufferevent_enable(struct bufferevent *bufev, short events); //可以启用或者禁用 bufferevent 上的 EV_READ、EV_WRITE 或者 EV_READ | EV_WRITE 事件。没有启用读取或者写入事件时, bufferevent 将不会试图进行数据读取或者写入。
- void bufferevent_disable(struct bufferevent *bufev, short events); //没有必要在输出缓冲区空时禁用写入事件: bufferevent 将自动停止写入,然后在有数据等待写入时重新开始。
- short bufferevent_get_enabled(struct bufferevent *bufev); //类似地,没有必要在输入缓冲区高于高水位时禁用读取事件 :bufferevent 将自动停止读取, 然后在有空间用于读取时重新开始读取。默认情况下,新创建的 bufferevent 的写入是启用的,但是读取没有启用。 可以调用bufferevent_get_enabled()确定 bufferevent 上当前启用的事件。
- void bufferevent_setwatermark(struct bufferevent *bufev, short events, size_t lowmark, size_t highmark); //bufferevent_setwatermark()函数调整单个 bufferevent 的读取水位、写入水位,或者同时调 整二者。(如果 events 参数设置了 EV_READ,调整读取水位。如果 events 设置了 EV_WRITE 标志,调整写入水位).对于高水位,0表示“无限”。
点击查看代码
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/util.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
struct info {
const char *name;
size_t total_drained;
};
void read_callback(struct bufferevent *bev, void *ctx)
{
struct info *inf = ctx;
struct evbuffer *input = bufferevent_get_input(bev);
size_t len = evbuffer_get_length(input);
if (len) {
inf->total_drained += len;
evbuffer_drain(input, len);
printf("Drained %lu bytes from %s\n",
(unsigned long) len, inf->name);
}
}
void event_callback(struct bufferevent *bev, short events, void *ctx)
{
struct info *inf = ctx;
struct evbuffer *input = bufferevent_get_input(bev);
int finished = 0;
if (events & BEV_EVENT_EOF) {
size_t len = evbuffer_get_length(input);
printf("Got a close from %s. We drained %lu bytes from it, "
"and have %lu left.\n", inf->name,
(unsigned long)inf->total_drained, (unsigned long)len);
finished = 1;
}
if (events & BEV_EVENT_ERROR) {
printf("Got an error from %s: %s\n",
inf->name, evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
finished = 1;
}
if (finished) {
free(ctx);
bufferevent_free(bev);
}
}
struct bufferevent *setup_bufferevent(void)
{
struct bufferevent *b1 = NULL;
struct info *info1;
info1 = malloc(sizeof(struct info));
info1->name = "buffer 1";
info1->total_drained = 0;
/* ... Here we should set up the bufferevent and make sure it gets
connected... */
/* Trigger the read callback only whenever there is at least 128 bytes
of data in the buffer. */
bufferevent_setwatermark(b1, EV_READ, 128, 0);
bufferevent_setcb(b1, read_callback, NULL, event_callback, info1);
bufferevent_enable(b1, EV_READ); /* Start reading. */
return b1;
}
操作bufferevent中的数据
通过bufferevent得到evbuffer
- bufferevent 提供了下列函数用于观察要写入或者读取的数据。
- struct evbuffer *bufferevent_get_input(struct bufferevent *bufev); //返回输入缓冲区,如果读取操作因为太多数据而停止,则从输入缓冲区移除数据将自动重启操作。
- struct evbuffer *bufferevent_get_output(struct bufferevent *bufev); //返回输出缓冲区,如果写入操作因为数据量太少而停止,则向输出缓冲区添加数据将自动重启操作。
向bufferevent的输出缓冲区添加数据
- int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size); //向 bufferevent 的输出缓冲区添加数据;将内存中从 data 处开始的 size 字节数据添加到输出缓冲区的末尾。
- int bufferevent_write_buffer(struct bufferevent *bufev, struct evbuffer *buf); //向 bufferevent 的输出缓冲区添加数据;移除 buf 的所有内容,将其放置到输出缓冲区的末尾。
从bufferevent的输入缓冲区移除数据
- size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size); //至多从输入缓冲区移除 size 字节的数据,将其存储到内存中 data 处。函数返回实际移除的字节数。data 处的内存块必须有足够的空间容纳 size 字节数据。
- int bufferevent_read_buffer(struct bufferevent *bufev, struct evbuffer *buf); //函数抽空输入缓冲区的所有内容,将其放置到 buf 中,成功时返 回0,失败时返回 -1。
点击查看代码
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <ctype.h>
void
read_callback_uppercase(struct bufferevent *bev, void *ctx)
{
/* This callback removes the data from bev's input buffer 128
bytes at a time, uppercases it, and starts sending it
back.
(Watch out! In practice, you shouldn't use toupper to implement
a network protocol, unless you know for a fact that the current
locale is the one you want to be using.)
*/
char tmp[128];
size_t n;
int i;
while (1) {
n = bufferevent_read(bev, tmp, sizeof(tmp));
if (n <= 0)
break; /* No more data. */
for (i=0; i<n; ++i)
tmp[i] = toupper(tmp[i]);
bufferevent_write(bev, tmp, n);
}
}
struct proxy_info {
struct bufferevent *other_bev;
};
void
read_callback_proxy(struct bufferevent *bev, void *ctx)
{
/* You might use a function like this if you're implementing
a simple proxy: it will take data from one connection (on
bev), and write it to another, copying as little as
possible. */
struct proxy_info *inf = ctx;
bufferevent_read_buffer(bev,
bufferevent_get_output(inf->other_bev));
}
struct count {
unsigned long last_fib[2];
};
void
write_callback_fibonacci(struct bufferevent *bev, void *ctx)
{
/* Here's a callback that adds some Fibonacci numbers to the
output buffer of bev. It stops once we have added 1k of
data; once this data is drained, we'll add more. */
struct count *c = ctx;
struct evbuffer *tmp = evbuffer_new();
while (evbuffer_get_length(tmp) < 1024) {
unsigned long next = c->last_fib[0] + c->last_fib[1];
c->last_fib[0] = c->last_fib[1];
c->last_fib[1] = next;
evbuffer_add_printf(tmp, "%lu", next);
}
/* Now we add the whole contents of tmp to bev. */
bufferevent_write_buffer(bev, tmp);
/* We don't need tmp any longer. */
evbuffer_free(tmp);
}
bufferevent的清空操作
- int bufferevent_flush(struct bufferevent *bufev, short iotype, enum bufferevent_flush_mode state); //清空 bufferevent 要求 bufferevent 强制从底层传输端口读取或者写入尽可能多的数据 ,而忽略其他可能保持数据不被写入的限制条件 。函数的细节功能依赖于 bufferevent 的具体类型。iotype 参数应该是 EV_READ、EV_WRITE 或者 EV_READ | EV_WRITE,用于指示应该处 理读取、写入,还是二者都处理。 state 参数可以是 BEV_NORMAL、BEV_FLUSH 或者 BEV_FINISHED。BEV_FINISHED 指示应该告知另一端,没有更多数据需要发送了; 而 BEV_NORMAL 和 BEV_FLUSH 的区别依赖于具体的 bufferevent 类型。失败时 bufferevent_flush()返回-1,如果没有数据被清空则返回 0,有数据被清空则返回 1。
数据封装evBuffer
- libevent 的 evbuffer 实现了为向后面添加数据和从前面移除数据而优化的字节队列。evbuffer 用于处理缓冲网络 IO 的“缓冲”部分。它不提供调度 IO 或者当 IO 就绪时触发 IO 的 功能:这是 bufferevent 的工作。除非特别说明,本章描述的函数都在 event2/buffer.h中声明。
创建和释放evbuffer
- struct evbuffer *evbuffer_new(void); //分配和返回一个新的空 evbuffer
- void evbuffer_free(struct evbuffer *buf); //释放 evbuffer 和其内容。
evbuffer与线程安全
- int evbuffer_enable_locking(struct evbuffer *buf, void *lock); //默认情况下,在多个线程中同时访问 evbuffer 是不安全的。使用该函数就能在多线程中访问,如果 lock 参数为 NULL , libevent 会使evthread_set_lock_creation_callback 提供的锁创建函数创建一个锁 。否则,libevent 将 lock 参数用作锁。
- 可以使用下面这两个函数让一系列操作是原子的。如果 evbuffer 没有启用锁,这两个函数不做任何操作。(注意:对于单个操作,不需要调用 evbuffer_lock()和evbuffer_unlock(): 如果 evbuffer启用了锁,单个操作就已经是原子的 。只有在需要多个操作连续执行 ,不让其他线程介入的 时候,才需要手动锁定 evbuffer)
- void evbuffer_lock(struct evbuffer *buf); //请求 evbuffer 上的锁。
- void evbuffer_unlock(struct evbuffer *buf); //释放 evbuffer 上的锁.
检查evbuffer
- size_t evbuffer_get_length(const struct evbuffer *buf); //这个函数返回 evbuffer 存储的字节数,它在2.0.1-alpha 版本中引入。
- int evbuffer_add(struct evbuffer *buf, const void *data, size_t datlen); //这个函数返回连续地存储在 evbuffer前面的字节数。 evbuffer中的数据可能存储在多个分隔开的内存块中,这个函数返回当前第一个块中的字节数。
向evbuffer添加数据
- int evbuffer_add(struct evbuffer *buf, const void *data, size_t datlen); //这个函数添加 data 处的 datalen 字节到 buf 的末尾,成功时返回0,失败时返回-1。
- //这些函数添加格式化的数据到 buf 末尾。格式参数和其他参数的处理分别与 C 库函数 printf 和 vprintf 相同。函数返回添加的字节数。
- int evbuffer_add_printf(struct evbuffer *buf, const char *fmt, ...)
- int evbuffer_add_vprintf(struct evbuffer *buf, const char *fmt, va_list ap);
- int evbuffer_expand(struct evbuffer *buf, size_t datlen); //这个函数修改缓冲区的最后一块,或者添加一个新的块,使得缓冲区足以容纳 datlen 字节, 而不需要更多的内存分配。
点击查看代码
/* Here are two ways to add "Hello world 2.0.1" to a buffer. */
/* Directly: */
evbuffer_add(buf, "Hello world 2.0.1", 17);
/* Via printf: */
evbuffer_add_printf(buf, "Hello %s %d.%d.%d", "world", 2, 0, 1);
evbuffer数据移动
- int evbuffer_add_buffer(struct evbuffer *dst, struct evbuffer *src); //将 src 中的所有数据移动到 dst 末尾,成功时返回0,失败时返回-1。
- int evbuffer_remove_buffer(struct evbuffer *src, struct evbuffer *dst, size_t datlen); //函数从 src 中移动 datlen 字节到 dst 末尾,尽量少进行复制。如果字节数小于 datlen,所有字节被移动。函数返回移动的字节数。
链接监听器evconnlistener
- evconnlistener 机制提供了监听和接受 TCP 连接的方法。本章的所有函数和类型都在 event2/listener.h 中声明,除非特别说明。
创建和释放evconnlistener
- 分配和返回一个新的连接监听器对象。连接监听器使 用 event_base 来得知什么时候在给定的监听套接字上有新的 TCP 连接。新连接到达时,监听 器调用你给出的回调函数。以下两个函数中,base参数都是监听器用于监听连接的 event_base。cb是收到新连接时要调用的回调函数;如果 cb 为 NULL,则监听器是禁用的,直到设置了回调函数为止。ptr 指针将传递给回调函数。flags 参数控制回调函数的行为,下面会更详细论述。backlog 是任何时刻网络栈允许处于还未接受状态的最大未决连接数。如果 backlog 是负的,libevent 会试图挑选一个较好的值 ; 如果为0,libevent 认为已 经对提供的套接字调用了listen()。两个函数的不同在于如何建立监听套接字。 evconnlistener_new()函数假定已经将套接字绑定到要监听的端口,然后通过 fd 传入这个套接字。如果要 libevent 分配和绑定套接字,可以调用 evconnlistener_new_bind() ,传输要绑定到的地址和地址长度。
- struct evconnlistener *evconnlistener_new(struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog, evutil_socket_t fd);
- struct evconnlistener *evconnlistener_new_bind(struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog, const struct sockaddr *sa, int socklen);
- void evconnlistener_free(struct evconnlistener *lev); //释放连接监听器
- 可识别的标志
- LEV_OPT_LEAVE_SOCKETS_BLOCKING //默认情况下,连接监听器接收新套接字后,会将其设置为非阻塞的,以便将其用于 libevent。如果不想要这种行为,可以设置这个标志。
- LEV_OPT_CLOSE_ON_FREE //如果设置了这个选项,释放连接监听器会关闭底层套接字。
- LEV_OPT_CLOSE_ON_EXEC //如果设置了这个选项,连接监听器会为底层套接字设置 close-on-exec 标志。更多信息请查 看 fcntl 和 FD_CLOEXEC 的平台文档。
- LEV_OPT_REUSEABLE //某些平台在默认情况下 ,关闭某监听套接字后 ,要过一会儿其他套接字才可以绑定到同一个 端口。设置这个标志会让 libevent 标记套接字是可重用的,这样一旦关闭,可以立即打开其 他套接字,在相同端口进行监听。
- LEV_OPT_THREADSAFE //为监听器分配锁,这样就可以在多个线程中安全地使用了。这是 2.0.8-rc 的新功能。
- 链接监听器回调
- typedef void (*evconnlistener_cb)(struct evconnlistener *listener, evutil_socket_t sock, struct sockaddr *addr, int len, void *ptr); //接收到新连接会调用提供的回调函数 。listener 参数是接收连接的连接监听器 。sock 参数是新接收的套接字。addr 和 len 参数是接收连接的地址和地址长度。ptr 是调用 evconnlistener_new() 时用户提供的指针。
启用和禁用 evconnlistener
- int evconnlistener_disable(struct evconnlistener *lev); //暂时禁止监听新连接。
- int evconnlistener_enable(struct evconnlistener *lev); //暂时重新允许监听新连接。
调整 evconnlistener 的回调函数
- void evconnlistener_set_cb(struct evconnlistener *lev, evconnlistener_cb cb, void *arg); //函数调整 evconnlistener 的回调函数和其参数。它是 2.0.9-rc 版本引入的。
检测 evconnlistener
- evutil_socket_t evconnlistener_get_fd(struct evconnlistener *lev); //返回监听器关联的套接字
- struct event_base *evconnlistener_get_base(struct evconnlistener *lev); //返回监听器关联的 event_base
侦测错误
- typedef void (*evconnlistener_errorcb)(struct evconnlistener *lis, void *ptr); //回调函数
- void evconnlistener_set_error_cb(struct evconnlistener *lev, evconnlistener_errorcb errorcb); //如果使用 evconnlistener_set_error_cb() 为监听器设置了错误回调函数,则监听器发生错误 时回调函数就会被调用。第一个参数是监听器,第二个参数是调用 evconnlistener_new() 时传入的 ptr。
libevent常用设置
日志消息回调设置
- typedef void (*event_log_cb)(int severity, const char *msg); //回调函数
- #define EVENT_LOG_DEBUG 0
- #define EVENT_LOG_MSG 1
- #define EVENT_LOG_WARN 2
- #define EVENT_LOG_ERR 3
- void event_set_log_callback(event_log_cb cb); //要覆盖libevent 的日志行为,编写匹配event_log_cb 签名的定制函数,将其作为参数传递 给event_set_log_callback()。随后libevent 在日志信息的时候,将会把信息传递给你提供的函数。再次调用event_set_log_callback(),传递参数NULL,就可以恢复默认行为。
点击查看代码
#include <event2/event.h>
#include <stdio.h>
static void discard_cb(int severity, const char *msg)
{
/* This callback does nothing. */
}
static FILE *logfile = NULL;
static void write_to_file_cb(int severity, const char *msg)
{
const char *s;
if (!logfile)
return;
switch (severity) {
case _EVENT_LOG_DEBUG: s = "debug"; break;
case _EVENT_LOG_MSG: s = "msg"; break;
case _EVENT_LOG_WARN: s = "warn"; break;
case _EVENT_LOG_ERR: s = "error"; break;
default: s = "?"; break; /* never reached */
}
fprintf(logfile, "[%s] %s\n", s, msg);
}
/* Turn off all logging from Libevent. */
void suppress_logging(void)
{
event_set_log_callback(discard_cb);
}
/* Redirect all Libevent log messages to the C stdio file 'f'. */
void set_logfile(FILE *f)
{
logfile = f;
event_set_log_callback(write_to_file_cb);
}
- 在用户提供的event_log_cb 回调函数中调用libevent 函数是不安全的。比如说,如果试图编写一个使用bufferevent 将警告信息发送给某个套接字的日志回调函数,可能会遇到奇怪 而难以诊断的bug。未来版本libevent 的某些函数可能会移除这个限制。
致命错误回调设置
- libevent 在检测到不可恢复的内部错误时的默认行为是调用exit()或者abort(),退出正在运行的进程。这类错误通常意味着某处有bug:要么在你的代码中,要么在libevent 中。如果希望更优雅地处理致命错误,可以为libevent 提供在退出时应该调用的函数,覆盖默认 行为。
- typedef void (*event_fatal_cb)(int err); //回调函数
- void event_set_fatal_callback(event_fatal_cb cb); //随后libevent 在遇到致命错误时将调用你提供的函数。 你的函数不应该将控制返回到libevent:这样做可能导致不确定的行为。为了避免崩溃,libevent 还是会退出。你的函数被不应该调用其它libevent 函数。
内存管理回调设置
- 默认情况下,libevent 使用C 库的内存管理函数在堆上分配内存。通过提供malloc、realloc和free 的替代函数,可以让libevent 使用其他的内存管理器。希望libevent 使用一个更高效的分配器时;或者希望libevent 使用一个工具分配器,以便检查内存泄漏时,可能需要这样做。
- void event_set_mem_functions(void (malloc_fn)(size_t sz), void (realloc_fn)(void ptr, size_t sz), void (free_fn)(void *ptr));
这里有个替换libevent 分配器函数的示例,它可以计算已经分配的字节数。实际应用中可能 需要添加锁,以避免运行在多个线程中时发生错误。
点击查看代码
#include <event2/event.h>
#include <sys/types.h>
#include <stdlib.h>
/* This union's purpose is to be as big as the largest of all the
* types it contains. */
union alignment {
size_t sz;
void *ptr;
double dbl;
};
/* We need to make sure that everything we return is on the right
alignment to hold anything, including a double. */
#define ALIGNMENT sizeof(union alignment)
/* We need to do this cast-to-char* trick on our pointers to adjust
them; doing arithmetic on a void* is not standard. */
#define OUTPTR(ptr) (((char*)ptr)+ALIGNMENT)
#define INPTR(ptr) (((char*)ptr)-ALIGNMENT)
static size_t total_allocated = 0;
static void *replacement_malloc(size_t sz)
{
void *chunk = malloc(sz + ALIGNMENT);
if (!chunk) return chunk;
total_allocated += sz;
*(size_t*)chunk = sz;
return OUTPTR(chunk);
}
static void *replacement_realloc(void *ptr, size_t sz)
{
size_t old_size = 0;
if (ptr) {
ptr = INPTR(ptr);
old_size = *(size_t*)ptr;
}
ptr = realloc(ptr, sz + ALIGNMENT);
if (!ptr)
return NULL;
*(size_t*)ptr = sz;
total_allocated = total_allocated - old_size + sz;
return OUTPTR(ptr);
}
static void replacement_free(void *ptr)
{
ptr = INPTR(ptr);
total_allocated -= *(size_t*)ptr;
free(ptr);
}
void start_counting_bytes(void)
{
event_set_mem_functions(replacement_malloc,
replacement_realloc,
replacement_free);
}
- 注意
- 替换内存管理函数影响libevent 随后的所有分配、调整大小和释放内存操作。所以,必须保证在调用任何其他libevent 函数之前进行替换。否则,libevent 可能用你的free 函数释放用C 库的malloc 分配的内存。
- 你的malloc 和realloc 函数返回的内存块应该具有和C 库返回的内存块一样的地址对齐。
- 你的realloc 函数应该正确处理realloc(NULL,sz)(也就是当作malloc(sz)处理)
- 你的realloc 函数应该正确处理realloc(ptr,0)(也就是当作free(ptr)处理)
- 你的free 函数不必处理free(NULL)
- 你的malloc 函数不必处理malloc(0)
- 如果在多个线程中使用libevent,替代的内存管理函数需要是线程安全的。
- libevent 将使用这些函数分配返回给你的内存。所以,如果要释放由libevent 函数分配和返回的内存,而你已经替换malloc 和realloc 函数,那么应该使用替代的free 函数。
锁和线程的设置
- libevent 的结构体在多线程下通常有三种工作方式:
- 某些结构体内在地是单线程的:同时在多个线程中使用它们总是不安全的。
- 某些结构体具有可选的锁:可以告知 libevent 是否需要在多个线程中使用每个对象。
- 某些结构体总是锁定的 :如果 libevent 在支持锁的配置下运行 ,在多个线程中使用它们 总是安全的。
- 接口
- 如果使用不同的线程库,则需要一些额外的工作,必须使用你的线程库来定义函数去实现并使用 evthread_set_lock_callbacks 和 evthread_set_id_callback 接口告知 libevent 这些函数。
- 锁
- 锁定
- 解锁
- 分配锁
- 析构锁
- 条件变量
- 创建条件变量
- 析构条件变量
- 等待条件变量
- 触发/广播某条件变量
- 线程
- 线程ID检测
调试事件的使用
- libevent 可以检测使用事件时的一些常见错误并且进行报告。这些错误包括:
- 将未初始化的 event 结构体当作已经初始化的
- 试图重新初始化未决的 event 结构体
- void event_enable_debug_mode(void); //必须在创建任何 event_base 之前调用这个函数。如果在调试模式下使用大量由 event_assign(而不是 event_new)创建的事件,程序可能 会耗尽内存,这是因为没有方式可以告知 libevent 由 event_assign 创建的事件不会再被使 用了(可以调用 event_free 告知由 event_new 创建的事件已经无效了 )
- void event_debug_unassign(struct event *ev); //显式告知 libevent 这些事件不再被当作已分配的了,没有启用调试的时候调用 event_debug_unassign 没有效果。
点击查看代码
#include <event2/event.h>
#include <event2/event_struct.h>
#include <stdlib.h>
void cb(evutil_socket_t fd, short what, void *ptr)
{
/* We pass 'NULL' as the callback pointer for the heap allocated
* event, and we pass the event itself as the callback pointer
* for the stack-allocated event. */
struct event *ev = ptr;
if (ev)
event_debug_unassign(ev);
}
/* Here's a simple mainloop that waits until fd1 and fd2 are both
* ready to read. */
void mainloop(evutil_socket_t fd1, evutil_socket_t fd2, int debug_mode)
{
struct event_base *base;
struct event event_on_stack, *event_on_heap;
if (debug_mode)
event_enable_debug_mode();
base = event_base_new();
event_on_heap = event_new(base, fd1, EV_READ, cb, NULL);
event_assign(&event_on_stack, base, fd2, EV_READ, cb, &event_on_stack);
event_add(event_on_heap, NULL);
event_add(&event_on_stack, NULL);
event_base_dispatch(base);
event_free(event_on_heap);
event_base_free(base);
}
使用技巧
- event_self_cbarg() 能够将当前event当成回调函数的参数传递给回调函数
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)