概述
优点:
- 开源
- 精简
- 支持跨平台
- 专注于网络通信
源码包安装步骤:可参考具体README文件
./configure
检查安装环境,生成makefilemake
生成.o和可执行文件sudo make install
将必要的资源拷贝至系统指定目录- 进入sample目录,运行demo验证是否安装成功
- 编译使用库的.c时,需要加
-levent
选项 - 库名libevent.so的路径可以在/usr/local/lib查看
特性:基于“事件”异步通信模型 → 回调
libevent框架
框架
- 创建event_base(地基)
struct event_base *event_base_new(void);
struct event_base *base = event_base_new();
- 创建事件evnet
- event →
event_new();
- bufferevent →
bufferevent_socket_new();
- event →
- 将事件添加到event_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 event_base_free(base);
其他函数了解
- 查看支持哪些多路IO(比如select、poll、epoll)
const char **event_get_supported_methods(void);
- 查看当前用的多路IO(一般为epoll)
const char *event_base_get_method(const struct event_base *base);
- fork后使子进程重新初始化event_base,避免继承父进程的event_base
int event_reinit(struct event_base *base);
常规事件:event
头文件:#include<event2/event.h>
创建事件
struct event *event_new(struct event_base *base,evutil_socket_t fd,short what,event_callback_fn cb; void *arg);
创建一个event事件
参数:
- base:event_base_new()的返回值
- fd:绑定到bufferevent事件上的文件描述符
- what:对应的监听类型
- EV_READ:一次读事件
- EV_WRTIE:一次写事件
- EV_PERSIST:持续触发
- 结合event_base_dispatch()使用
- 通常搭配读或写
- EV_PERSIST | EV_READ
- EV_PERSIST | EV_WRTIE
- cb:一旦事件满足监听条件,回调的函数
typedef void(*event_callback_fn)(evutil_socket_t fd, short what, void *arg)
- 三个参数分别对应
event_new()
的第二、三、五个参数
- 三个参数分别对应
- arg:回调函数的参数
返回值:成功创建的struct event
事件对象
添加事件
int event_add(struct event *ev, const struct timeval *tv);
将事件添加到event_base上
参数:
- ev:event对象,即event_new()的返回值
- tv:超时时间
- NULL:不会超时,即一直等到事件被触发,回调函数被调用
- 非零值:若等待时间内事件没有被触发,则时间一到回调函数被调用
移除事件
int event_del(struct event *ev);
将事件从event_base上取下(了解)
参数:event对象,即event_new()的返回值
释放事件
int event_free(struct event *ev); 销毁一个事件
参数:event对象,即event_new()的返回值
未决和非未决
- 非未决:没有资格被处理(即使满足对应监听事件)
- 未决:有资格被处理,但尚未被处理(因为对应事件还未被触发)
编码练习:libevent + fifo实现读写
read
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<event2/event.h>
#include<errno.h>
#include<fcntl.h>
#include<sys/stat.h>
void sys_err(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 write : %s\n", buf);
sleep(1);
return ;
}
int main(int argc, char *argv[]) {
unlink("test_fifo");
mkfifo("test_fifo", 0644); //创建fifo
int fd = open("test_fifo", O_RDONLY | O_NONBLOCK); //打开fifo的读端
if(fd == -1) sys_err("open error");
struct event_base *base = event_base_new(); //创建event_base
struct event *ev = event_new(base, fd, EV_READ | EV_PERSIST, read_cb, NULL); //创建事件
event_add(ev, NULL); //添加事件
event_base_dispatch(base); //循环监听
//释放base和event
event_base_free(base);
event_free(ev);
return 0;
}
write
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<event2/event.h>
#include<errno.h>
#include<fcntl.h>
void sys_err(const char *str) {
perror(str);
exit(1);
}
void write_cb(evutil_socket_t fd, short what, void *arg) {
char buf[] = "hello libevent ~ ";
static int num = 0;
sprintf(buf, "%d", ++num);
write(fd, buf, strlen(buf) + 1);
sleep(1);
return ;
}
int main(int argc, char *argv[]) {
int fd = open("test_fifo", O_WRONLY | O_NONBLOCK); //打开fifo的写端
if(fd == -1) sys_err("open error");
struct event_base *base = event_base_new(); //创建event_base
struct event *ev = event_new(base, fd, EV_WRITE | EV_PERSIST, write_cb, NULL); //创建事件
event_add(ev, NULL); //添加事件
event_base_dispatch(base); //循环监听
//释放base和event
event_base_free(base);
event_free(ev);
return 0;
}
带缓冲区的事件:bufferevent
头文件:#include<event2/bufferevent.h>
相较常规event事件而言,bufferevent专注于socket通信
bufferevent原理
一个bufferevent对象中有read/write两个缓冲区,借助队列实现,先进先出,读走数据就没了
- 读:读缓冲中有数据 → 读回调函数被调用 → 使用
bufferevent_read()
→ 从读缓冲中读数据 - 写: 使用
bufferevent_write()
→ 向写缓冲中写数据 → 当写缓冲区有数据后会自动发送给对端 → 发送完成后,写回调函数被调用(只起到通知的作用)
相关函数
创建/销毁bufferevent
struct bufferevent *bufferevent_socket_new(struct event_base *base, evutil_socket_t fd, enum bufferevent_options options);
创建一个bufferevent事件对象,类比event_new()
参数:
- base:event_base_new()的返回值
- fd:绑定到bufferevent事件上的文件描述符
- options:BEV_OPT_CLOSE_ON_FREE
返回值:成功创建的bufferevent事件对象
void bufferevent_socket_free(struct bufferevent *ev);
释放一个bufferevent事件对象
给bufferevent设置回调
对比event:event_new(fd, callback函数, ~)
→ event_add()
→ 挂到event_base上
而bufferevent在new的时候并没有设置回调函数,需要另外使用函数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()
返回的bufferevent对象 - readcb:设置bufferevent读缓冲对应的回调函数(内部使用
bufferevent_read()
读数据) - writecb:设置bufferevent写缓冲对应的回调函数(只起到通知作用,因此可以传NULL)
- eventcb:设置事件回调,可以传NULL
- cbarg:上述回调函数使用的参数
readcb对应的回调函数
函数类型:typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void*ctx);
void read_cb(struct bufferevent *bev, void *cbarg) {
.....
bufferevent_read(); //读数据:相当于从bufferevent输入缓冲区中移除数据
}
bufferevent_read()函数原型:size_t bufferevent_read(struct bufferevent *bev, void *buf, size_t bufsize);
writecb对应的回调函数
函数类型:同上
bufferevent_write()函数原型: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);
void event_cb(struct bufferevent *bev, short events, void *ctx) {
.....
}
参数events:一般传BEV_EVENT_CONNECTED(请求的连接过程已完成,实现客户端时可用)
禁用/启用缓冲区
新建的bufferevent对象写缓冲是enable状态,读缓冲是disable状态
void bufferevent_enable(struct bufferevent *bufev, short events);
启用缓冲区
参数:
- bufev:
bufferevent_socket_new()
返回的bufferevent对象 - events:EV_READ、EV_WRITE、EV_READ|EV_WRITE
客户端连接服务器
int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *address, int addrlen);
等效于socket()
+ connect()
参数:
- bev: bufferevent 事件对象(封装了fd)
- address:connect()的参2
- len:connect()的参3
服务器创建/释放监听器
头文件:#include<event2/listener.h>
创建监听器
struct evconnlistener *evconnlistener_new_bind(struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog, const struct sockaddr *sa, int socklen);
等效于socket() + bind() + listen() + accept()
参数:
- base:event_base_new()的返回值
- cb:回调函数,即建立连接之后,用户要做的通信操作
- ptr:传递给回调函数,一般传event_base
- flags:可识别的标志,一般传LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE(释放bufferevent同时关闭底层套接字 | 端口复用)
- backlog:listen()的参2,指定连接上限,一般传-1,表示使用默认最大值
- sa:服务器地址结构
- socklen:服务器地址结构大小
返回值:成功创建的监听器
监听器回调函数
typedef void(evconnlistener_cb)(struct evconnlistener *listener, evutil _socket_t sock, struct sockaddr *addr, *int len, void *ptr);
参数:
- listener:
evconnlistener_new_bind
()返回值 - sock:用于通信的文件描述符
- addr:客户端地址结构
- len:客户端地址结构大小
- ptr:
evconnlistener_new_bind()
参3传递进来的ptr,一般是event_base
ps:该回调函数由框架自动调用,不用我们手动调用,因此只需要知道参数含义即可
释放监听器
void evconnlistener_free(struct evconnlistener *lev);
libevent实现TCP连接
服务器
思路:
- 使用
event_base_new()
创建event_base - 使用
evconnlistener_new_bind()
创建监听器,在其回调函数listner_cb()
中,处理接受连接后的操作 - 监听器的回调函数被调用,说明有一个客户端连接上来,会得到一个cfd,用与跟客户端进行通信
- 在监听器的回调函数中:
- 使用
bufferevent_socket_new()
创建一个bufferevent事件对象,将cfd封装到该事件对象中 - 使用
bufferevent_setcb()
函数给该bufferevent对象的read、write、event设置回调函数 - 设置该bufferevent对象的读缓冲、写缓冲的使能状态:enable/disable
- 当监听的事件满足时,对应回调函数会被调用,接受/发送数据:
bufferevent_read()
/bufferevent_write()
- 使用
- 使用
event_base_dispath()
启动循环监听 - 释放资源
ps:监听器只用于监听连接请求,bufferevent监听read、write、event请求
代码:
#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);
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_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;
}
客户端
思路:
- 使用
event_base_new()
创建event_base - 使用
bufferevent_socket_new()
创建一个用于跟服务器通信的bufferevent事件对象 - 使用
bufferevent_socket_connect()
连接服务器 - 使用
bufferevent_setcb()
函数给该bufferevent对象的read、write、event设置回调函数 - 设置该bufferevent对象的读缓冲、写缓冲的使能状态:enable/disable
- 接受/发送数据:
bufferevent_read()
/bufferevent_write()
- 使用
event_base_dispath()
启动循环监听 - 释放资源
代码:
#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 info
struct 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;
}