目录
编译安装libevent
libevent
事件对象
事件操作
事件循环
事件处理
libevent 客户端demo
libevent 服务端demo
libevent 服务端升级demo
libevent完整demo
总结
C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂
编译安装libevent
git上下载https://github.com/libevent/libevent/releases/tag/release-2.1.12-stable
# 解压
root@kaka:/# tar -zxvf libevent-2.1.12-stable
root@kaka:/# cd libevent-2.1.12-stable/
# 执行配置./configure, 检测安装环境, 生成makefile.
# 执行./configure的时候也可以指定路径, ./configure --\prefix=/usr/local
# 这样就可以安装到指定的目录下, 但是这样在进行源代码编译的时候
# 需要指定用-I头文件的路径和用-L库文件的路径. 若默认安装不指定--prefix,
# 则会安装到系统默认的路径下, 编译的时候可以不指定头文件和库文件所在的路径.
root@kaka:/libevent-2.1.12-stable# ./configure
# 执行make命令编译整个项目文件
root@kaka:/libevent-2.1.12-stable# make
# make install进行安装
# 头文件拷贝到了/usr/local/include目录下
# 库文件拷贝到了/usr/local/lib目录下
root@kaka:/libevent-2.1.12-stable# make install
gcc hello-world.c -levent 。由于安装的时候已经将头文件和库文件拷贝到了系统头文件所在路径/usr/local/include和系统库文件所在路径/usr/local/lib, 所以这里编译的时候可以不用指定-I和-L.
libevent
libevent是由 c 实现的异步事件库,封装了reactor;注册异步事件,检测异步事件,根据事件的触发先后顺序,调用相对应回调函数处理事件;
处理的事件包括:网络 io 事件、定时事件以及信号事件;
事件对象
reactor对象封装为struct event_base;通过:
(1)event_base_new()构造对象。
(2)event_base_free()销毁对象。
event对象可以自己处理IO。
(1)event_new():构建事件对象、绑定、事件回调。
(2)event_free():销毁事件对象。
bufferevent是在event对象上面封装的缓冲区。
(1)bufferevent_socket_new():构建bufferevent对象。
(2)bufferevent_free():销毁bufferevent对象。
evconnlistener是专门处理listenfd的对象,使我们不需要关注bind、listen、accept的具体操作。
(1)evconnlistener_new():构建evconnlistener对象、绑定、事件回调。
(2)evconnlistener_free():销毁evconnlistener对象。
(3)evconnlistener_new_bind():创建listenfd、bind、listen、注册读事件。
事件操作
event对象:
(1)event_add(),注册事件。
(2)event_del(),注销事件。
bufferevent对象:
(1)bufferevent_enable(),注册事件。
(2)bufferevent_disable(),注销事件。
事件循环
(1)事件循环:event_base_dispatch(),event_base_loop()。
(2)事件循环退出:event_base_loopexit(),event_base_break()。
事件处理
设置事件相对应的回调。
(1)如果是使用event对象,在event_new()会设置相对应的回调。
(2)如果IO由libevent处理,那么使用bufferevent_setcb()来设置回调。
void bufferevent_setcb(struct bufferevent *bufev,
bufferevent_data_cb readcb, bufferevent_data_cb writecb,
bufferevent_event_cb eventcb, void *cbarg)
{
BEV_LOCK(bufev);
bufev->readcb = readcb;
bufev->writecb = writecb;
bufev->errorcb = eventcb;
bufev->cbarg = cbarg;
BEV_UNLOCK(bufev);
}
libevent 客户端demo
主动连接
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <event.h>
#include <event2/listener.h>
// typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short what, void *ctx);
void connected_cb(struct bufferevent *bev, short what, void *ctx) {
if (what == BEV_EVENT_CONNECTED) { //连接建立成功
printf("connect server successed\n");
} else {
printf("connect server failed\n");
}
}
int main() {
// 创建事件对象
struct event_base * base = event_base_new();
struct sockaddr_in sin = {0};
sin.sin_addr.s_addr = inet_addr("192.168.181.1");
sin.sin_family = AF_INET;
sin.sin_port = htons(8080);
// 连接的建立---主动连接
//构建bufferevent对象
struct bufferevent *ev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);//-1表示自动创建fd
bufferevent_socket_connect(ev, (struct sockaddr *)&sin, sizeof(sin));
//设置回调
bufferevent_setcb(ev, NULL, NULL, connected_cb, NULL);
// 事件循环
event_base_dispatch(base);
// 销毁bufferevent对象
bufferevent_free(ev);
// 销毁事件对象
event_base_free(base);
return 0;
}
libevent 服务端demo
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <event.h>
#include <event2/listener.h>
// typedef void (*evconnlistener_cb)(struct evconnlistener *, evutil_socket_t, struct sockaddr *, int socklen, void *);
void accept_cb(struct evconnlistener *listen, evutil_socket_t fd, struct sockaddr *sock, int socklen, void *arg) {
char ip[32] = {0};
evutil_inet_ntop(AF_INET, sock, ip, sizeof(ip)-1);
printf("accept a client fd:%d ip:%s\n", fd, ip);
}
int main() {
// 创建事件对象
struct event_base * base = event_base_new();
// listener
struct sockaddr_in sin = {0};
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);
//创建listenfd、bind、listen、注册读事件。
struct evconnlistener *listen = evconnlistener_new_bind(base, accept_cb, base,
LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, 512, (struct sockaddr*)&sin, sizeof(sin));
// 事件循环
event_base_dispatch(base);
// 销毁evconnlistener对象
evconnlistener_free(listen);
// 销毁事件对象
event_base_free(base);
return 0;
}
libevent 服务端升级demo
#include <event.h>
#include <event2/listener.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
void socket_read_callback(struct bufferevent *bev, void *arg) {
// 操作读缓冲当中的数据
//struct evbuffer *evbuf = bufferevent_get_input(bev); // 封装了读缓冲区
//char *msg = evbuffer_readln(evbuf, NULL, EVBUFFER_EOL_LF);// \n作为数据包分隔符
//if (!msg) return;
//printf("server read the data: %s\n", msg);
//char reply[4096] = {0};
//sprintf(reply, "recvieced msg: %s\n", msg);//echo
//free(msg);
//bufferevent_write(bev, reply, strlen(reply));
// 也可以直接用 bufferevent_read 读数据
// bufferevent_read(struct bufferevent *bufev, void *data, size_t size)
char msg[256] = {0};
bufferevent_read(bev, msg, 256);
printf("server read the data: %s\n", msg);
char reply[4096] = {0};
sprintf(reply, "recvieced msg: %s\n", msg);//echo
bufferevent_write(bev, reply, strlen(reply));
}
// 处理连接断开
void socket_event_callback(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_TIMEOUT)
printf("timeout\n");
bufferevent_free(bev); // close
}
// accept的回调函数封装
void listener_callback(struct evconnlistener *listener, evutil_socket_t fd,
struct sockaddr *sock, int socklen, void *arg) {
char ip[32] = {0};
evutil_inet_ntop(AF_INET, sock, ip, sizeof(ip) - 1);
printf("accept a client fd:%d ip:%s\n", fd, ip);
struct event_base* base = (struct event_base*) arg;
//创建一个bufferevent
struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
// 设置读、写、以及异常时的回调函数
bufferevent_setcb(bev, socket_read_callback, NULL, socket_event_callback, NULL);
// 使能这个bufferevent开启读事件
bufferevent_enable(bev, EV_READ | EV_PERSIST);
}
int main()
{
// listener
struct sockaddr_in sin = { 0 };
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);
/* 底层IO多路复用抽象 */
struct event_base *base = event_base_new();
/* evconnlistener 监听 */
struct evconnlistener *listener =
evconnlistener_new_bind(base, listener_callback, base,
LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE,
10, (struct sockaddr *) &sin,
sizeof(struct sockaddr_in));
// 事件循环
event_base_dispatch(base);
// 销毁evconnlistener对象
evconnlistener_free(listener);
// 销毁事件对象
event_base_free(base);
return 0;
}
libevent完整demo
包含网络事件,io事件,定时事件,信号事件
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <event.h>
#include <time.h>
#include <signal.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
void socket_read_callback(struct bufferevent *bev, void *arg) {
// 操作读缓冲当中的数据
struct evbuffer *evbuf = bufferevent_get_input(bev); // 封装了读缓冲区
char *msg = evbuffer_readln(evbuf, NULL, EVBUFFER_EOL_LF);
// 也可以直接用 bufferevent_read 读数据
// bufferevent_read(struct bufferevent *bufev, void *data, size_t size)
if (!msg) return;
printf("server read the data: %s\n", msg);
char reply[4096] = {0};
sprintf(reply, "recvieced msg: %s\n", msg);//echo
// -WRN: 需要自己释放资源
free(msg);
bufferevent_write(bev, reply, strlen(reply));
}
// stdio标准输入触发读事件时的回调
void stdio_callback(struct bufferevent *bev, void *arg) {
// 获取读缓冲区并操作读缓冲中的数据
struct evbuffer *evbuf = bufferevent_get_input(bev); // 封装了读缓冲区
// 从中读一行,需要指定换行符
char *msg = evbuffer_readln(evbuf, NULL, EVBUFFER_EOL_LF);
if (!msg) return;
if (strcmp(msg, "quit") == 0) {
printf("safe exit!!!\n");
event_base_loopbreak(arg);
}
printf("stdio read the data: %s\n", msg);
}
// socket出现如错误、关闭等异常事件时的回调
void socket_event_callback(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_TIMEOUT)
printf("timeout\n");
bufferevent_free(bev); // close
}
// accept的回调函数封装
void listener_callback(struct evconnlistener *listener, evutil_socket_t fd,
struct sockaddr *sock, int socklen, void *arg) {
char ip[32] = {0};
evutil_inet_ntop(AF_INET, sock, ip, sizeof(ip) - 1);
printf("accept a client fd:%d ip:%s\n", fd, ip);
struct event_base *base = (struct event_base *) arg;
//创建一个bufferevent
struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
// 设置读、写、以及异常时的回调函数
bufferevent_setcb(bev, socket_read_callback, NULL, socket_event_callback, NULL);
// 使能这个bufferevent开启读事件
bufferevent_enable(bev, EV_READ | EV_PERSIST);
}
static void do_timer(int fd, short events, void *arg) {
struct event *timer = (struct event *) arg;
time_t now = time(NULL);
printf("do_timer %s", (char *) ctime(&now));
//event_del(timer);
// struct timeval tv = {1,0};
// event_add(timer, &tv);
}
static void do_sig_int(int fd, short event, void *arg) {
struct event *si = (struct event *) arg;
event_del(si);
printf("do_sig_int SIGINT\n");//CTRL + C
}
// 建立连接的事件封装
int main() {
struct sockaddr_in sin;
memset(&sin, 0, sizeof(struct sockaddr_in));
sin.sin_family = AF_INET;
sin.sin_port = htons(8080);
/* 底层IO多路复用抽象 */
struct event_base *base = event_base_new();
/* evconnlistener 监听 */
struct evconnlistener *listener =
evconnlistener_new_bind(base, listener_callback, base,
LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE,
10, (struct sockaddr *) &sin,
sizeof(struct sockaddr_in));
/* 普通 fd 的 IO 事件管理,此处以标准输入 stdin 为例 */
struct bufferevent *ioev = bufferevent_socket_new(base, 0, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(ioev, stdio_callback, NULL, NULL, base);
bufferevent_enable(ioev, EV_READ | EV_PERSIST);
/* 定时事件 */
struct event evtimer;
struct timeval tv = {1, 0}; // {秒, 微秒}
event_set(&evtimer, -1, EV_PERSIST, do_timer, &evtimer); // tv 为超时时间
event_base_set(base, &evtimer);
event_add(&evtimer, &tv);
/* 信号事件 */
struct event evint;
event_set(&evint, SIGINT, EV_SIGNAL, do_sig_int, &evint);
event_base_set(base, &evint);
event_add(&evint, NULL);
/* 开启主循环 */
event_base_dispatch(base);
/* 结束释放资源 */
evconnlistener_free(listener);
event_base_free(base);
return 0;
}
/*
gcc ev2.c -o ev2 -levent
client:
telnet 127.0.0.1 8080
*/
总结
libevent是一个事件通知库,目标是对事件编程,封装了reactor。
使用层次:在事件中自己处理IO和只需要处理业务逻辑(libevent内部处理IO)两种方式。
封装层次:把事件对象绑定到reactor对象上面;需要熟悉事件操作、事件处理、事件循环。
libevent 主要封装了异步事件库与操作系统的交互;让用户无需关注平台检测处理事件的机制的差异,只需关注事件的具体处理;