libevent

编程入门 行业动态 更新时间:2024-10-28 01:25:12

<a href=https://www.elefans.com/category/jswz/34/1743317.html style=libevent"/>

libevent

libevent

库概念和特点

        开源。精简。跨平台(Windows、Linux、maxos、unix)。专注于网络通信(不一定非用在网络当中,比如下面的读写管道)。

        libevent特性:基于"事件",面向“文件描述符”的异步(回调)通信模型。

        异步:函数"注册"时间和函数真正被执行的时间不同,函数真正是被内核调用(等待某一条件满足)

事件库:就干一件事,监听文件描述符的库机制。

        先创建一个事件底座,然后创建你想监听的时间,插到底座上,libevent底座就会帮忙监听事件,当事件发生时,就会自动调用事件的回调函数。

libevent使用框架

常规事件

常规事件函数
//创建event_base(事件底座)
struct event_base* base = event_base_new();//创建事件event
常规事件event:event_new();
例如:
struct event* signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);buffer event:bufferevent_socket_new();//将事件添加到base上
int event_add(struct event* ev, const struct timeval* tv);//循环监听事件满足
int event_base_dispatch(struct event_base* base);
event_base_dispatch(base);					//调用//释放event_base
event_base_free(base);
其他函数

 

 libevent函数使用的一个简单的例子:
/*This example program provides a trivial server program that listens for TCPconnections on port 9995.  When they arrive, it writes a short message toeach client connection, and closes each connection once it is flushed.Where possible, it exits cleanly in response to a SIGINT (ctrl-c).
*/#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#ifndef _WIN32
#include <netinet/in.h>
# ifdef _XOPEN_SOURCE_EXTENDED
#  include <arpa/inet.h>
# endif
#include <sys/socket.h>
#endif#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/listener.h>
#include <event2/util.h>
#include <event2/event.h>static const char MESSAGE[] = "Hello, World!\n";static const int PORT = 9995;static void listener_cb(struct evconnlistener *, evutil_socket_t,struct sockaddr *, int socklen, void *);
static void conn_writecb(struct bufferevent *, void *);
static void conn_eventcb(struct bufferevent *, short, void *);
static void signal_cb(evutil_socket_t, short, void *);//main函数中是使用框架,剩下外面的函数就是事件的回调
int main(int argc, char **argv)
{//创建事件底座指针struct event_base *base;struct evconnlistener *listener;
//创建事件指针struct event *signal_event;
//创建套接字的地址结构struct sockaddr_in sin;
#ifdef _WIN32WSADATA wsa_data;WSAStartup(0x0201, &wsa_data);
#endif
//真正创建事件底座base = event_base_new();if (!base) {fprintf(stderr, "Could not initialize libevent!\n");return 1;}memset(&sin, 0, sizeof(sin));
//初始化套接字的地址结构sin.sin_family = AF_INET;sin.sin_port = htons(PORT);
//一个函数干了套接字通信的 socket、listen、bind、accept几个函数的事情listener = evconnlistener_new_bind(base, listener_cb, (void *)base,LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE, -1,(struct sockaddr*)&sin,sizeof(sin));if (!listener) {fprintf(stderr, "Could not create a listener!\n");return 1;}
//注册一个信号事件signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);if (!signal_event || event_add(signal_event, NULL)<0) {fprintf(stderr, "Could not create/add a signal event!\n");return 1;}
//循环监听,相当于 while循环加里面的epoll_wait;下面的循环有很大概率没有机会调用event_base_dispatch(base);
//释放之前创建的事件evconnlistener_free(listener);event_free(signal_event);event_base_free(base);printf("done\n");return 0;
}//下面是千篇一律,定义了三个回调,分别独立对应三个事件。看一个就行
static void listener_cb(struct evconnlistener *listener, evutil_socket_t fd,struct sockaddr *sa, int socklen, void *user_data)
{struct event_base *base = user_data;struct bufferevent *bev;bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);if (!bev) {fprintf(stderr, "Error constructing bufferevent!");event_base_loopbreak(base);return;}bufferevent_setcb(bev, NULL, conn_writecb, conn_eventcb, NULL);bufferevent_enable(bev, EV_WRITE);bufferevent_disable(bev, EV_READ);bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
}static void conn_writecb(struct bufferevent *bev, void *user_data)
{struct evbuffer *output = bufferevent_get_output(bev);if (evbuffer_get_length(output) == 0) {printf("flushed answer\n");bufferevent_free(bev);}
}static void conn_eventcb(struct bufferevent *bev, short events, void *user_data)
{if (events & BEV_EVENT_EOF) {printf("Connection closed.\n");} else if (events & BEV_EVENT_ERROR) {printf("Got an error on the connection: %s\n",strerror(errno));/*XXX win32*/}/* None of the other events can happen here, since we haven't enabled* timeouts */bufferevent_free(bev);
}static void signal_cb(evutil_socket_t sig, short events, void *user_data)
{struct event_base *base = user_data;struct timeval delay = { 2, 0 };printf("Caught an interrupt signal; exiting cleanly in two seconds.\n");event_base_loopexit(base, &delay);
}
常规事件函数使用详解
*事件创建 event_new:
struct event* event_new(struct event_base* base,evutil_socket_t fd,short what,event_callback_fd cb,void* arg);
//
参数:
base:基事件, 也就是event_base_new()的返回值
fd:绑定到event上的文件描述符,监听的对象
what:文件描述符对应的事件(r/w/e),监听类型是什么what的取值:EV_READ:读一次后,退出循环EV_WRITE:写一次,退出循环EV_PERSIST:持续触发,可以理解为while(read())或while(write())
cb:一旦满足监听条件,回调的函数
arg:回调函数的参数//返回值:成功返回创建的事件对象event
事件回调函数:

参数跟上面一致(实际用时候不用传参)

typedef void (*event_callback_fn)(evutil_socket_t fd,short what,void* arg);
f
事件添加(到event_base上)
int event_add(struct event* ev,const strcut timeval* tv);
//参数:
ev是要添加的事件对象,就是event_new的返回值
tv一般传NULL,表示一直等到事件被触发,回调函数才会被调用。如果传非0,会等待事件被触发,如果事件一直不触发,时间到,回调函数依然会被调用//返回值:成功返回0;失败返回-1
事件删除(对应add,从event_base上摘下事件(不常用)):
int event_del(struct event* ev);
//ev是要摘下的事件对象,就是event_new的返回值
事件销毁:
int event_free(strcut event* ev);
//ev是要销毁的事件对象,就是event_new的返回值
基于libevent实现的FIFO的读写
读FIFO进程:
void perr_exit(const char* str) {perror(str);exit(1);
}
//读回调
void read_cb(evutil_socket_t fd, short what, void* arg) {char buf[BUFSIZ] = {0};read(fd, buf, sizeof(buf));printf("Read from writer:%s\n", buf);printf("what=%s\n", what & EV_READ ? "Yes" : "No");sleep(1);return;
}int main(int argc, char* argv[]) {int ret = 0;int fd = 0;unlink("myfifo");mkfifo("myfifo", 0644);fd = open("myfifo", O_RDONLY | O_NONBLOCK);//借助epoll反应堆,默认都是非阻塞if (fd == -1)perr_exit("open error");
//读管道时候,管道中不一定有数据,此时就需要有事件概念介入了
//创建事件基座struct event_base* base = event_base_new();struct event* ev = NULL;
//创建事件ev = event_new(base, fd, EV_READ | EV_PERSIST, read_cb, NULL);
//添加到事件基座event_add(ev, NULL);
//开启事件监听event_base_dispatch(base);
//释放事件event_base_free(base);close(fd);return 0;
}
写FIFO进程:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#include <event2/event.h>// 对操作处理函数
void write_cb(evutil_socket_t fd, short what, void *arg)
{// write管道char buf[1024] = {0};static int num = 0;sprintf(buf, "hello,world-%d\n", num++);write(fd, buf, strlen(buf)+1);sleep(1);
}// 写管道
int main(int argc, const char* argv[])
{// open file//int fd = open("myfifo", O_WRONLY | O_NONBLOCK);int fd = open("myfifo", O_WRONLY);if(fd == -1){perror("open error");exit(1);}// 写管道struct event_base* base = NULL;base = event_base_new();// 创建事件struct event* ev = NULL;// 检测的写缓冲区是否有空间写//ev = event_new(base, fd, EV_WRITE , write_cb, NULL);ev = event_new(base, fd, EV_WRITE | EV_PERSIST, write_cb, NULL);// 添加事件event_add(ev, NULL);// 事件循环event_base_dispatch(base);// 释放资源event_free(ev);event_base_free(base);close(fd);return 0;
}
 重点关注对写事件event_new函数入参,写监听时长的定义,写一次退出循环和一直触发的区别:

libevent模型,事件的运行状态:未决和非未决

未决:有资格被处理,但还没有被处理 (只剩数据没到达了)

非未决:没有资格被处理 (除了数据没到达,还有其他监听条件没满足,比如事件没add)

小注意:事件被处理完,如果设置了persist,add会被再调用一次

bufferevent

bufferevent概念

bufferevent的事件对象fd有两个缓冲区。

读:读缓冲区有数据,读回调函数被调用,使用bufferevent_read()读数据

写:使用bufferevent_write,向写缓冲中写数据,该缓冲区中有数据自动写出,写完后,回调函数被调用(鸡肋)

原理:bufferent利用队列实现两个缓冲区(数据只能读一次,读走就没, FIFO)

bufferevent事件对象创建和销毁
创建socket事件对象 bufferevent_socket_new:
struct bufferevent* bufferevent_socket_new(struct event_base* base,evutil_socket_t fd,enum bfferevent_options options)
//入参
base:基事件,event_base_new函数的返回值
fd:封装到bufferevent内的fd(绑定在一起),即lfd
enum表示枚举类型,一般取BEV_OPT_CLOSE_ON_FREE(关闭时释放底层套接字)//返回
成功返回bufferevent事件对象

对比event,有区别。这个不是在 new的时候设置的回调,而是借助后面的 bufferevent_setcb函数专门设置回调。

销毁事件对象 bufferevent_socket_free:
void bufferevent_socket_free(struct bufferevent* ev)
//入参
bufev:bufferevent_socket_new()函数的返回值readcb:读缓冲对应的回调,自己封装,在其内部读数据(注意是用bufferevent_read()读,而不是read())writecb:鸡肋,传NULL即可eventcb:可传NULLcbarg:回调函数的参数
给bufferevent事件设置回调 bufferevent_setcb
void bufferevent_setcb(struct bufferevent* bufev,bufferevent_data_cb readcb,bufferevent_data_cb writecb,bufferevent_event_cb eventcb,void* cbarg);
//入参:
bufev:bufferevent_socket_new()函数的返回值readcb:读缓冲对应的回调,自己封装,在其内部读数据(注意是用bufferevent_read()读,而不是read())writecb:鸡肋,传NULL即可eventcb:备用回调,比如异常回调,也可传NULLcbarg:三个回调函数的(相同)参数
 readcb对应的回调函数:

void read_cb(struct bufferevent* bev,void* arg){...bufferevent_read();
}

为什么 read_cb里面不封装read,因为没有fd给read传。fd通过new函数封装好的bufferevent传。

size_t bufferevent_read(struct bufferevent* bufev,void* data,size_t size);
writecb对应的回调函数:

int bufferevent_write(struct bufferevent* bufev,const void* data,size_t size)
eventcb对应的回调函数:
typedef void (*bufferevent_event_cb)(struct bufferevent* bev,short events,void* ctx)

buffer缓冲区开启和关闭

默认新建的bufferevent写缓冲是enable的,而读缓冲是disable的

通过下面函数操作缓冲区读写使能:

 

void bufferevent_enable(struct bufferevent* bufev,short events);		//启用缓冲区
void bufferevnet_disable(struct bufferevent* bufev,short events);		//禁用/*例如:开启读缓冲*/
void bufferevent_enable(bufev,EV_READ);//events的值可传入三个宏:
EV_READ
EV_WRITE
EV_READ|EV_WRITE

也可获取缓冲区的禁用状态:

short bufferevent_get_enable(struct bufferevent* bufev)
客户端和服务器连接和监听函数详解
客户端建立连接 bufferevent_socket_connect:
int bufferevent_socket_connect(struct bufferevent* bev,struct sockaddr* address,int addrlen);//入参
bev:bufferevent事件对象(封装了fd)
address,len:等同于connect()的参2和参3
服务器创建监听器对象 evconnlistener_new_bind:

这一个函数可以完成socket(),bind(),listen(),accept()四个函数的作用

struct evconnlistener* evconnlistener_new_bind(struct event_base* base,evconnlistener_cb cb,void* ptr,unsigned flags,int backlog,const struct sockaddr* sa,int socklen);
//入参
cb:监听回调函数(建立连接后用户要做的操作)
ptr:回调函数的参数
flags:可识别的标志,通常传:
​ LEV_OPT_CLOSE_ON_FREE(释放bufferevent时关闭底层传输端口, 这将关闭底层套接字, 释放底层bufferevent等)
​ LEV_OPT_REUSEABLE(可以设置端口复用)
backlog:相当于listen的参2,最大连接数, 传-1表示使用默认的最大值
sa:服务器自己的地址结构
socklen:sa的大小

回调函数 evconnlistener_cb cb:

监听成功后,说明客户端链接成功,原来的accept传出的客户端地址结构,现在在这里传出。

typedef void (*evconnlistener_cb)(struct evconnlistener* listener,evutil_socker_t sock,struct sockaddr* addr,int len,void* ptr);
//入参:
listener:evconnlistener_new_bind 的返回值
sock:用于通信的文件描述符,即cfd
addr:传出,客户端的地址结构
len:客户端地址结构的长度
ptr:外部ptr传进来的值
libevent实现TCP服务器流程

感觉这个思路和epoll反应堆还有些距离??? 

  • 创建基事件event_base
  • 使用evconlistener_new_bind()创建监听服务器,用来专门监听客户端链接事件,有新链接上来,就调用其回调函数(完成socket(),bind(),listen(),accept()四个函数的作用)
  • 设置evconlistener_new_bind()回调函数listner_cb(),该回调函数被调用,说明有一个新的客户端连接上来,会得到一个新的fd,用于跟客户端通信。
  • 创建bufferevent事件对象:bufferevent_socket_new(),将fd封装到这个事件对象中
  • 使用bufferevent_setcb()函数给bufferevent的read,write,event设置回调函数
  • 设置读写缓冲enable
  • 启动循环event_base_dispath(),监听通信事件()
  • 当监听的事件满足时,read_cb会被调用,在其内部bufferevent_read(),读
  • 释放连接
libevent实现TCP服务器源码分析
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>// 读缓冲区回调
void read_cb(struct bufferevent *bev, void *arg)
{char buf[1024] = {0};   bufferevent_read(bev, buf, sizeof(buf));printf("client say: %s\n", buf);char *p = "我是服务器, 已经成功收到你发送的数据!";// 发数据给客户端bufferevent_write(bev, p, strlen(p)+1);//执行完后回调write_cbsleep(1);
}// 写缓冲区回调
void write_cb(struct bufferevent *bev, void *arg)
{printf("I'm服务器, 成功写数据给客户端,写缓冲区回调函数被回调...\n"); 
}// 事件
void event_cb(struct bufferevent *bev, short events, void *arg)
{if (events & BEV_EVENT_EOF){printf("connection closed\n");  }else if(events & BEV_EVENT_ERROR)   {printf("some other error\n");}bufferevent_free(bev);    printf("buffevent 资源已经被释放...\n"); 
}void cb_listener(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *addr, int len, void *ptr)
{printf("connect new client\n");struct event_base* base = (struct event_base*)ptr;//创建出用于通信的socket事件对象bufferevent,并给bufferevent缓冲区设置回调和读使能struct bufferevent *bev;//2参fd被封装在新的事件对象 bufferevent中bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);bufferevent_enable(bev, EV_READ);
}int main(int argc, const char* argv[])
{// init server struct sockaddr_in serv;memset(&serv, 0, sizeof(serv));serv.sin_family = AF_INET;serv.sin_port = htons(9876);serv.sin_addr.s_addr = htonl(INADDR_ANY);struct event_base* base;base = event_base_new();// 创建套接字// 绑定// 接收连接请求struct evconnlistener* listener;listener = evconnlistener_new_bind(base, cb_listener, base, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 36, (struct sockaddr*)&serv, sizeof(serv));event_base_dispatch(base);evconnlistener_free(listener);event_base_free(base);return 0;
}
客户端流程简析和回顾
  1. 创建event_base
  2. 使用bufferevent_socket_new()创建一个用跟服务器通信的bufferevent事件对象
  3. 使用bufferevent_socket_connect连接服务器
  4. 使用bufferevent_setcb()给bufferevent对象的read,write,event设置回调
  5. 设置bufferevent对象的读写缓冲区使能 (4、5放connect前应该也没问题)
  6. 接受,发送数据bufferevent_read()/bufferevent_write()
  7. 启动循环监听event_base_dispath()
  8. 释放资源
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <arpa/inet.h>void read_cb(struct bufferevent *bev, void *arg)
{char buf[1024] = {0}; bufferevent_read(bev, buf, sizeof(buf));printf("fwq say:%s\n", buf);bufferevent_write(bev, buf, strlen(buf)+1);sleep(1);
}void write_cb(struct bufferevent *bev, void *arg)
{printf("----------我是客户端的写回调函数,没卵用\n"); 
}void event_cb(struct bufferevent *bev, short events, void *arg)
{if (events & BEV_EVENT_EOF){printf("connection closed\n");  }else if(events & BEV_EVENT_ERROR)   {printf("some other error\n");}else if(events & BEV_EVENT_CONNECTED){printf("已经连接服务器...\\(^o^)/...\n");return;}// 释放资源bufferevent_free(bev);
}// 客户端与用户交互,从终端读取数据写给服务器
void read_terminal(evutil_socket_t fd, short what, void *arg)
{// 读数据char buf[1024] = {0};int len = read(fd, buf, sizeof(buf));struct bufferevent* bev = (struct bufferevent*)arg;// 发送数据bufferevent_write(bev, buf, len+1);
}int main(int argc, const char* argv[])
{struct event_base* base = NULL;base = event_base_new();int fd = socket(AF_INET, SOCK_STREAM, 0);// 通信的fd放到bufferevent中struct bufferevent* bev = NULL;bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);// init server infostruct sockaddr_in serv;memset(&serv, 0, sizeof(serv));serv.sin_family = AF_INET;serv.sin_port = htons(9876);inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);// 连接服务器bufferevent_socket_connect(bev, (struct sockaddr*)&serv, sizeof(serv));// 设置回调bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);// 设置读回调生效// bufferevent_enable(bev, EV_READ);// 创建事件struct event* ev = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST,read_terminal, bev);// 添加事件                     event_add(ev, NULL);event_base_dispatch(base);event_free(ev);event_base_free(base);return 0;
}

 

更多推荐

libevent

本文发布于:2023-11-16 05:26:56,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1612930.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:libevent

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!