1.简介
特点
源码包安装
2.libevent框架
创建event_base
创建添加事件
循环监听事件满足
释放event_base
相关函数了解
3.常规事件event
未决与非未决
使用fifo的读写
4.带缓冲区事件bufferevent
bufferevent
A.服务器创建监听器
C.给读写缓冲区设置回调
D.禁用、启用缓冲区
E.客户端连接服务器
server
client
1.简介
libevent
libevent版本一共有1.4系列和2.0系列两个稳定版本。
1.4系列比较古老,但是源码简单,适合源码的学习
2.0系列比较新,建议直接使用2.0
需要注意的是,1.4系列和2.0系列两个版本的接口并不兼容,就是2.0将一些接口的原型发>生了改变,所以将1.4升级到2.0需要重新编码。
特点
开源、精简、跨平台、专注于网络通信
基于“事件”异步通信模型 -- 回调
事件:event库中将所有的事情封装成了事件,就像操作系统将所有的东西成文件
异步:对应同步(所有的事情都要讲究先后顺序),注册函数的时间和函数被调用的时间不同,只有当内核监听到事件的条件满足时,才会去调用函数(回调函数)。和信号一样,先注册信号捕捉函数,有信号来时才会去调用回调函数
核心其实就是epoll
源码包安装
参考README、readme
1.下载压缩包 --> libevent-2.0.20-stable.tar.gz
2.解压 --> tar -xvf libevent-2.0.20-stable.tar.gz
3.进入解压后的目录 -->cd /....
4.检查安装环境,生产makefile -->./configure参数可加可不加
5.生成 .o 和可执行文件 -->make
6.将必要的资源cp到系统指定目录(软、硬连接,必要的.so文件等)sudo make install
7.进入simple目录,运行demo验证库按安装使用情况,要添加上-levent,这里其实是采用动态库链接的方式,去掉lib前缀和.so后缀cd ./simplegcc hello-world.c -o hell -levent//-l库名:指定动态库库名,-L和-I不添加采用默认./a.out
由于编译运行时指定的是动态库,运行可执行文件时动态链接器会去系统指定位置找该动态库,6 中其实就是将软、硬连接,必要的.so文件放到系统固定位置,这样链接时动态链接器才找得到 库.so(是个软链接,然后根据软链接去找到相应的可执行文件的目录)
链接时必要的.so文件存储在/user/lib或是/user/loal/lib
动态链接器查找.so文件的目录一般是/user/lib
2.libevent框架
创建event_base
【头文件】:#include<event2/event.h>
struct event_base *event_base_new(void);
struct event_base *base = NULL;
base = event_base_new();
创建添加事件
1.创建事件event -- 常规事件、带缓冲区事件:strcut event *event_new(strcut event_base *base, evutil_socket_t fd,short what, event_callback_fn cd,void *arg);what: EV_READ、EV_WRITE、EV_PERSIST(连续触发 -- whike(read() / write() ))cd: 回调函数 -- typedef void(*event_callback_fn)(evutil_socket_t fd, short,void *);struct bufferevent *bufferevent_socket_new(struct event_base *base,evutil socket_t fd, enum bufferevent_options options);.base: 函数返回值options:BEV_OPT_CLOSE_FREE2.将事件event添加到base上int event_add(struct event *ev, const struct timeval *tv);ev: event_new()函数返回的事件。tv: 为 NULL,不会超时。意为:一直等到事件被触发,回调函数会被调用。为非 0,等待期间,检查事件没有被触发,时间到,回调函数依旧会被调用。bufferevent暂时不看
循环监听事件满足
启动循环:相当于epoll中while{ epoll_wait(); ... } -- 阻塞监听到客户端的连接请求、读请求等然后调用回调函数进行处理它内部是等待事件满足然后根据事件去调用自己写的对应的回调函数int event_base_dispatch(struct event_base *base);base: event_base_new 函数的返回值返回值:1表示成功,0表示失败只有 event_new 中指定了EV_PERSIST 才持续触发,否则值触发一次,就跳出循环通常这样:EV_WRITEIEV_PERSIST、EV_READIEV_PERSIST在指定事件后停止循环:int event_base_loopexit(struct event_base *base, const struct timeval *tv)
立即停止循环:int evnet_base_loopbread(struct event_base *base);
释放event_base
void event_base_new(struct event_base *base);
相关函数了解
查看 支持哪些 多路 IOconst char **event_get_supported_methods(void);查看 当前用的 多路 IOconst char * event_base_get_method(const struct event base *base);查看 fork后子进程使用的 event_baseint event _reinit(struct event_base *base);成功:0,失败:-1使用该函数后,父进程的base才能在子进程中生效
3.常规事件event
可以用在本地中,如管道
步骤:
1.创建一个事件对象,监听的事件发生等于该ev发生
strcut event *event_new(strcut event_base *base, //指明事件要插入的底座 -- 要调用event_add才真的插入evutil_socket_t fd, //事件对象中的fd,将其捆绑事件对象ev上,-- 事件/请求来源,回调函数处理的就是fd的请求short what, //要监听的事件 -- r、w、e -- r发生,监听条件满足,调用回调读取数据然后处理event_callback_fn cd,//事件发生,监听条件满足,调用该回调函数void *arg); //回调函数的参数,传base自己或NULLwhat: EV_READ 一次读,event_base_dispatch(base)客户端有数据过来,服务器要读,监听到条件满足,读取然后调用回调函数处理数据,退出结束EV_WRITE 一次写EV_PERSIST 结合event_base_dispatch(base)处理完客户端的请求进入下一轮循环,继续等待客户端的数据然后读取,该函数不退出(连续触发 -- whike(read() / write() )) cb: 回调函数 -- typedef void(*event_callback_fn)(evutil_socket_t fd, short,void *);返回值:成功则返回创建的事件event2.将事件添到base上,该base在ev当中已经被指明int event_add(struct event *ev, const struct timeval *tv);ev: event_new()函数返回的事件。tv: 为 NULL,不会超时。意为:一直等到事件被触发,回调函数会被调用。为非 0,等待期间,检查事件没有被触发,时间到,回调函数依旧会被调用。一旦事件ev中的对端fd有事件(r、w、e)发生满足监听条件,base监听到将调用ev中的回调函数去处理事件3.将事件从base上拿下
int event_del(struct event *ev);ev: event_new()函数返回的事件4.释放事件
int event_free(struct event *ev);成功:0;失败:-1
未决与非未决
未决:有资格被处理,但还没有被处理(事件还没发生,监听调价按不满足,无法调用回调处理事件 -- 处理数据)
非未决:没有资格被处理(还没准备好,即使事件到来也不会调用回调函数处理事件)
使用fifo的读写
服务器这边:处理读事件 -- 读取出来然后处理数据 -- base在这边
客户端:写事件,写给服务器
4.带缓冲区事件bufferevent
bufferevent
主要应用于套接字通信
#include <event2/bufferevent.h>
原理:bufferevent有两个缓冲区:也是队列实现,读走没,先进先出。读:有数据-->读回调函数被调用-->使用 bufferevent_read() --> 读数据。写:使用 bufferevent_write()-->向写缓冲中写数据 -->该缓冲区有数据自动写出-->写完,回调函数被调用(鸡肋)
A.服务器创建监听器
【头文件】: #include <event2/listener.h>
struct evconnlistener* evconnlistener_new( struct event base *base,evconnlistener_cb cb,void *ptr,unsigned flags,int backlog,*evutil_socket_t fd
); 【了解】1.【掌握】
struct evconnlistener *evconnlistener_new_bind (struct event_base *base, //event_base_new(), 底座的创建evconnlistener_cb cb, //监听器的回调,在里面封装事件创建B、设置缓冲区回调C、启动缓冲区函数Dvoid *ptr, //cb的参数,cb中添加事件、bufferevent回调函数、设置缓冲区回调等函数都需要base,靠ptr传进回调cb中的回调使用unsigned flags,int backlog,const struct sockaddr *sa,int sockln);cb:监听回调函数。接受连接之后,用户要做的操作ptr:回调函数的参数flags:“可识别的标志”LEV_OPT_CLOSE_ON_FREE:释放 bufferevent 时关闭底层传输端口。这将关闭底层套接字.释放底层 bufferevent等LEV_OPT_REUSEABLE 端口复用。可以“ | ”backlog:listen的2参。传-1:表使用默认最大值sa:服务器IP+Portsocklen:sa的大小
这一个函数,可完成系统调用socket(), bind(), listen(), accept()的作用2. evconnlistener_cb cb回调函数 -- 在该回调函数体内创建事件处理服务器读请求,将B、C、D都封装进该回调
【注:该回调函数不由我们调用,是框架自动调用,因此只需知晓参数含义即可】
typedef void (*evconnlistener_cb) (struct evconnlistener *listener,evutil_socket_t sock,struct sockaddr *addr,int len,void *ptr);listener: evconnlistener_new_bind 函数的返回值sock: 用于通信的文件描述符addr: 客户端的 IP+端口len: addr 的len.ptr:evconnlistener_new_bind()中ptr传递进来的值,一般传base,因为事件创建B、设置缓冲回调C、启动缓冲区函数D参数都需要base
B.带缓冲区的事件对象创建、释放
创建: -- 有客户端连接了才能创建,所以得先创建监听器,在监听器的回调函数中去等待客户端的请求连接然后创建事件绑定,处理请求
struct bufferevent *bufferevent_socket_new(struct event_base *base, evutil_socket_t fd, enum bufferevent_options options);base:event_base_new 函数的返回值。fd:跟 bufferevent绑定的套接字的文件描述符。类比 event_new()options:BEV_OPT_CLOSE_ON_FREE 只用这一个即可。[可参考《libevenet 参考手册(中文版)》p53,“bufferevent 的选项标志”】释放:
void bufferevent_free(struct bufferevent *bev);bev:上面函数的返回值。
C.给读写缓冲区设置回调
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:可传 NULL,用来处理异常或额外事件的回调cbarg :传给回调函数用的参数。1.bufferevent_data_cb回调函数:
typedef vdid (*bufferevent_data_cb)(struct bufferevent *bev, void*ctx);
如:void read_cb(struct bufferevent *bev, void *arg){ ......//size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);// 通常用在 readcb 中,替代read()! //库提供的bufferevent_read()//读数据,类似 read()的作用//buffervent_write();//int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);// 常用在 bufferevent_read 之后,替代 write()! //库提供的......} //自己设计的回调函数读数据:从 bufferevnet 输入缓冲区 中 移除数据2.bufferevent_event_cb eventcb回调函数:
void event_cb(struct bufferevent *bev, short events, void *ctx){......buffervent_event_cb(bev,events,ctx);/*typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short events, void *ctx);events:不同标志位,代表不同的事件。【可参考《libevenet 参考手册(中文版)》p52,“回调和水位”】EV_EVENT_READING:读取操作时发生某事件,具体是哪种事件,看其他标志。BEV_EVENT_WRITING:写入操作时发生某事件,具体是哪种事件请看其他标志。BEV_EVENT_ERROR:操作时发生错误。关于错误的更多信息,请调用EVUTILSOCKET_ERRORO()。BEV_EVENT_TIMEOUT:发生超时。BEV_EVENT_EOF:遇到文件结束指示。BEV_EVENT_CONNECTED:请求的连接过程已经完成,实现客户端时可用。/*......
}
D.禁用、启用缓冲区
默认:新建的 bufferevent 写缓冲是enable的。而,读缓冲是 disable 的。
void bufferevent_enable(struct bufferevent *bufev,short events);启用缓冲区events :EV_READ、EV_WRITE、EV_READIEV_WRITE通常用来启用 bufferevent的read 缓冲。void bufferevent_disable(struct bufferevent *bufev,short events); 禁用events :EV_READ、EV_WRITE、EV_READIEV_WRITEshort bufferevent_get_enabled(struct bufferevent *bufev);、获取缓冲区的禁用状态,需要借助 & 来得到。
E.客户端连接服务器
int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *address,int addrlen);address、len:类似 connect()中的用法 -- 服务器的地址结构和地址结构的长度bev: 封装了客户端cfd的事件对象
server
#include <stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<arpa/inet.h>
#include<event2/bufferevent.h>
#include<event2/event.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);sleep(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;// 添加新事件struct bufferevent *bev;bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);// 给 bufferevent 缓冲区设置回调bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);// 启用 bufferevent 的读缓冲。默认是 disable 的bufferevent_enable(bev, EV_READ);
}int main(int argc, const char* argv[]) {// init serverstruct 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(); // 创建事件基础结构//创建套接字//绑定//接收连接请求//事件的创建都应该在cb_listener中创建,先有监听器libevent内部才能去处理请求事件和将新来的事件对象添加给base,不能在main中创建,无法向监听器中传事件对象//不像event可以用add将事件添加给base,启动循环处理请求 -- 还是有差别的,fifo是PIC机制,不是网络通信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));//这一个函数,可完成系统调用socket(), listen(), bind(), accept()的作用,accept返回客户端的套接字,就相当于事件对象的创建//这和在listen创建监听器后才能accept获得客户端套接字一个道理,得先有监听器才能处理链接请求,创建客户端对应的事件对象,监听器内部再将新来的事件对象添加到base上//还有监听器listener监听已经在base上的事件的请求事件//cb_listener函数内核自己调用,监听到事件就会调用,不像epoll自己设定的读请求等回调函数还是需要自己调用的// 启动循环监听event_base_dispatch(base);evconnlistener_free(listener);event_base_free(base);return 0;
}
client
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <arpa/inet.h>
#include <event2/bufferevent.h>
#include <event2/event.h>// 读缓冲区回调
void read_cb(struct bufferevent *bev, void *arg) {char buf[1024] = {0};bufferevent_read(bev, buf, sizeof(buf));printf("server say: %s\n", buf);char *p = "客户端收到服务器数据!";bufferevent_write(bev, p, strlen(p) + 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("已经连接服务器.....\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);// 初始化 server 信息struct sockaddr_in serv; // server 的地址结构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 的读回调bufferevent_enable(bev, EV_READ);// 创建事件struct event *ev = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST, read_terminal, bev);// 将事件 ev 添加到指定的 base 中event_add(ev, NULL);event_base_dispatch(base); // 循环监听调用回调事件对象 ev 中的 read_terminal 处理请求event_free(ev);event_base_free(base);return 0;
}