01 libevent的安装和测试
libevent-基本概念
1.libevent是干什么的
- 开源的库,提高开发效率
封装了socket通信
封装了IO多路转接 - 精简,专注于网络, 性能高
- 事件驱动
2.libevent库的安装
- 官方网站:http://libevent.org
- 源码包下载:
1.4.x–适合源码学习
2.x - 源码包的安装
(1) ./configure
–prefix== /usr/xxxxx
检测安装环境
生成makefile
(2) make
编译源代码
生成一些库(◊动态,静态 ◊可执行程序)
(3) sudo make install
将数据拷贝到对应的目录
如果目录不存在,创建该目录
默认目录(◊ /usr/local)
/usr/local/include
/usr/local/bin
/usr/local/lib
3.libevent库的使用
- 编译程序的时候指定-levent 即可
- 常用头文件:
#include <event2/event.h>
#include<event2/listener.h>
4.示例程序演示
- hello-world.c
- 浏览源代码
02 解决动态库找不到的问题
1.找到xxx.so放到/usr/lib /lib–不推荐
○ sudofind /usr/local-name “libevent.so”
2.将xxx.so放到环境变量中
LD_LIBRARY_PATH
- export LD_LIBRARY_PATH=xxxx
□ ~/.bashrc --用户级别
□ /etc/profile -系统级别 - 使用命令重新加载
□ .~/.bashrc
□ ./etc/profile
□ .等价于source
3.修改/etc/ls.so.conf
a. 动态库路径添加到该文件中-绝对路径
b. sudoldconfig -v
03 event_base讲解
=====使用套路
1.创建一个事件处理框架
2.创建一事件
3.事件添加到事件处理框架上
4.开始事件循环
5.释放资源
- 释放event_base
○ event_base_free(struct event_base* base);
04 event_base相关的小函数
事件处理框架-event_base
1.使用libevent 函数之前需要分配一个或者多个 event_base 结构体。每个event_base 结构体持有一个事件集合,可以检测以确定哪个事件是激活的。
○ 相当于epoll红黑树的树根
○ 底座
○ 抽象层,完成对event_base的封装
○ 每个 event_base 都有一种用于检测哪种事件已经就绪的 “方法”,或者说后端.
2.
- 创建event_base
○ structevent_base*event_base_new(void);
○ 失败返回NULL - 释放event_base
○event_base_free(structevent_base* base); - 循环监听base对应的事件,等待条件满足
○event_base_dispatch();
- 查看event_base封装的后端
○ const char **event_get_supported_methods(void);
char*str[];
○ const char *event_base_get_method(const struct event_base *base );
- event_base和fork
○ 子进程创建成功之后,父进程可以继续使用event_base
○ 子进程中需要继续使用event_base需要重新进程初始化
intevent_reinit(struct event_base* base);
05 event事件的创建
事件创建-event
1.创建新事件
#define EV_TIMEOUT 0x01 //废弃
#define EV_READ 0x02
#define EV_WRITE 0x04
#define EV_SIGNAL 0x08
#define EV_PERSIST 0x10//持续触发
#define EV_ET 0x20//边沿模式
typedef void (*event_callback_fn)(evutil_socket_t, short, void *);
struct event *event_new(
struct event_base *base,
evutil_socket_tfd,-// 文件描述符-int
short what,
event_callback_fncb,//事件的处理动作
void *arg
);
○ 调用event_new()函数之后, 新事件处于已初始化和非未决状态
2.释放事件
○ void event_free(struct event *event);
3.设置未决事件
构造事件之后,在将其添加到 event_base 之前实际上是不能对其做任何操作的。使用event_add()将事件添加到event_base,非未决事件-> 未决事件.
○int event_add(
struct event *ev,
const struct timeval *tv
);
-tv:
NULL: 事件被触发, 对应的回调被调用
tv= {0, 100}, 如果设置的时间, 在改时间段内检测的事件没被触发,时间到达之后, 回调函数还是会被调用
函数调用成功返回0,失败返回-1
4.设置非未决
○ int event_del(struct event *ev);
对已经初始化的事件调用 event_del()将使其成为非未决和非激活的。如果事件不是未决的或者激活的,调用将没有效果。成功时函数返回 0,失败时返回-1。
06 消息循环
事件循环-event_loop
一旦有了一个已经注册了某些事件的event_base,就需要让 libevent 等待事件并且通知事件的发生。
#define EVLOOP_ONCE 0x01
事件只会被触发一次
事件没有被触发,阻塞等
#define EVLOOP_NONBLOCK 0x02
非阻塞等方式去做事件检测
不关心事件是否被触发了
#define EVLOOP_NO_EXIT_ON_EMPTY 0x04
没有事件的时候,也不退出轮询检测
1.intevent_base_loop(struct event_base *base, int flags);
a. 正常退出返回0,失败返回-1。
2.intevent_base_dispatch(structevent_base* base);
○ 等同于没有设置标志的 event_base_loop ( )
○ 将一直运行,直到没有已经注册的事件了,或者调用 了event_base_loopbreak()或者 event_base_loopexit()为止。
3.循环停止
- 返回值:成功0, 失败-1
○ 如果 event_base 当前正在执行激活事件的回调 ,它将在执行完当前正在处理的事件后立即退出。
int event_base_loopexit(struct event_base base,const struct timevaltv);
○ 让event_base 立即退出循环
int event_base_loopbreak(struct event_base *base);
07 libevent内部事件的状态转换
1.非未决
○ 没有资格被处理
2.未决
○ 有资格被处理但是还没有处理
时间的状态转换:
08 使用event读管道
#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 read_cb(evutil_socket_t fd, short what, void *arg)
{
// 读管道
char buf[1024] = {0};
int len = read(fd, buf, sizeof(buf));
printf("data len = %d, buf = %s\n", len, buf);
printf("read event: %s", what & EV_READ ? "Yes" : "No");
}
// 读管道
int main(int argc, const char* argv[])
{
unlink("myfifo");
//创建有名管道
mkfifo("myfifo", 0664);
// open file
int fd = open("myfifo", O_RDONLY | O_NONBLOCK);
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_READ | EV_PERSIST, read_cb, NULL);
// 添加事件
event_add(ev, NULL);
// 事件循环
event_base_dispatch(base);
// 释放资源
event_free(ev);
event_base_free(base);
close(fd);
return 0;
}
09 使用event写管道
#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);
}
// 写管道
int main(int argc, const char* argv[])
{
// open file
int fd = open("myfifo", O_WRONLY | O_NONBLOCK);
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);
// 添加事件
event_add(ev, NULL);
// 事件循环
event_base_dispatch(base);
// 释放资源
event_free(ev);
event_base_free(base);
close(fd);
return 0;
}
10 bufferevent介绍
数据缓冲区-Bufferevent
1.bufferevent理解:
- 是libevent为IO缓冲区操作提供的一种通用机制
- bufferevent 由一个底层的传输端口(如套接字 ),一个读取缓冲区和一个写入缓冲区组成。
- 与通常的事件在底层传输端口已经就绪,可以读取或者写入的时候执行回调不同的是,bufferevent 在读取或者写入了足够量的数据之后调用用户提供的回调。
2.回调-缓冲区对应的操作
- 每个 bufferevent 有两个数据相关的回调。
○ 一个读取回调
□ 从底层传输端口读取了任意量的数据之后会调用读取回调(默认)。
○ 一个写入回调
□ 输出缓冲区中足够量的数据被清空到底层传输端口后写入回调会被调用(默认)。
11 使用bufferevent
1.创建基于套接字的bufferevent
○ 可以使用 bufferevent_socket_new()创建基于套接字的 bufferevent
struct bufferevent *bufferevent_socket_new(
struct event_base *base,
evutil_socket_t fd,
enum bufferevent_options options
);
- options:BEV_OPT_CLOSE_ON_FREE
□ 释放 bufferevent 时关闭底层传输端口。这将关闭底层套接字,释放底层 bufferevent 等
□ 参考手册 page53,bufferevent的选项标志。 - structbufferevent也是一个event
- 成功时函数返回一个 bufferevent,失败则返回NULL。
2.在bufferevent上启动链接
int bufferevent_socket_connect(
struct bufferevent *bev,
struct sockaddr *address, int addrlen
);
address 和 addrlen 参数跟标准调用 connect()的参数相同。如果还没有为bufferevent 设置套接字,调用函数将为其分配一个新的流套接字,并且设置为非阻塞的。
如果已经为 bufferevent 设置套接字,调用bufferevent_socket_connect() 将告知libevent 套接字还未连接,直到连接成功之前不应该对其进行读取或者写入操作。
连接完成之前可以向输出缓冲区添加数据。
3.释放bufferevent操作
void bufferevent_free(struct bufferevent *bev);
这个函数释放 bufferevent
4.bufferevent读写缓冲区回调操作
typedef void (*bufferevent_data_cb)(
struct bufferevent *bev,
void*ctx
);
typedef void (*bufferevent_event_cb)(
struct bufferevent *bev,
short events,
void *ctx
);
events参数:
EV_EVENT_READING:读取操作时发生某事件,具体是哪种事件请看其他标志。
BEV_EVENT_WRITING:写入操作时发生某事件,具体是哪种事件请看其他标志。
BEV_EVENT_ERROR:操作时发生错误。关于错误的更多信息,请调用EVUTIL_SOCKET_ERROR()。
BEV_EVENT_TIMEOUT:发生超时。
BEV_EVENT_EOF:遇到文件结束指示。
BEV_EVENT_CONNECTED:请求的连接过程已经完成.
void bufferevent_setcb(
struct bufferevent *bufev,
bufferevent_data_cb readcb,
bufferevent_data_cb writecb,
bufferevent_event_cb eventcb, void *cbarg
);
5.禁用、启用缓冲区
void bufferevent_enable(
struct bufferevent *bufev,
short events
);
void bufferevent_disable(
struct bufferevent *bufev,
short events
);
short bufferevent_get_enabled(
struct bufferevent *bufev
);
可以启用或者禁用 bufferevent 上的 EV_READ、EV_WRITE 或者EV_READ |EV_WRITE 事件。没有启用读取或者写入事件时,bufferevent 将不会试图进行数据读取或者写入。
6.操作bufferevent中的数据
○ 向bufferevent的输出缓冲区添加数据
int bufferevent_write(
struct bufferevent *bufev,
const void *data,
size_t size
);
○ 从bufferevent的输入缓冲区移除数据
size_t bufferevent_read(
struct bufferevent *bufev,
void *data,
size_t size
);
12 链接监听器-evconnlistener
1.创建和释放evconnlistener
typedef void (*evconnlistener_cb)(
struct evconnlistener *listener,
evutil_socket_t sock,
struct sockaddr *addr,
int len,
void *ptr
);
struct evconnlistener *evconnlistener_new(
struct event_base *base,
evconnlistener_cb cb,
void *ptr,
unsigned flags,
int backlog,
evutil_socket_t fd
);
参数flags:
参考手册-可识别的标志
struct evconnlistener *evconnlistener_new_bind(
struct event_base *base,
evconnlistener_cb cb,
void *ptr,
unsigned flags,
int backlog,
const struct sockaddr *sa, int socklen);
两个 evconnlistener_new*()函数都分配和返回一个新的连接监听器对象。连接监听器使用 event_base 来得知什么时候在给定的监听套接字上有新的 TCP 连接。新连接到达时,监听器调用你给出的回调函数。
void evconnlistener_free(struct evconnlistener *lev);
2.启用和禁用 evconnlistener
int evconnlistener_disable(struct evconnlistener *lev);
int evconnlistener_enable(struct evconnlistener *lev);
这两个函数暂时禁止或者重新允许监听新连接。
3.调整 evconnlistener 的回调函数
void evconnlistener_set_cb(
struct evconnlistener *lev,
evconnlistener_cb cb,
void *arg
);
函数调整 evconnlistener 的回调函数和其参数。
13 bufferevent实现服务器端代码
server.c:
#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));
char* p = "我已经收到了你发送的数据!";
printf("client say: %s\n", p);
// 发数据给客户端
bufferevent_write(bev, p, strlen(p)+1);
printf("====== send buf: %s\n", p);
}
// 写缓冲区回调
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");
}
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;
}
client.c:
#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/bufferevent.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);
}
void write_cb(struct bufferevent *bev, void *arg)
{
printf("I am Write_cb function....\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(∩_∩)O哈哈~\n");
return;
}
bufferevent_free(bev);
printf("free bufferevent...\n");
}
void send_cb(evutil_socket_t fd, short what, void *arg)
{
char buf[1024] = {0};
struct bufferevent* bev = (struct bufferevent*)arg;
printf("请输入要发送的数据: \n");
read(fd, buf, sizeof(buf));
bufferevent_write(bev, buf, strlen(buf)+1);
}
int main(int argc, const char* argv[])
{
struct event_base* base;
base = event_base_new();
struct bufferevent* bev;
bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
// 连接服务器
struct sockaddr_in serv;
memset(&serv, 0, sizeof(serv));
serv.sin_family = AF_INET;
serv.sin_port = htons(9876);
evutil_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 | EV_PERSIST);
// 创建一个事件
struct event* ev = event_new(base, STDIN_FILENO,
EV_READ | EV_PERSIST,
send_cb, bev);
event_add(ev, NULL);
event_base_dispatch(base);
event_base_free(base);
return 0;
}
makefile:
target:server.c client.c
gcc server.c -o server -levent
gcc client.c -o client -levent
.PHONY:clean
clean:
-rm *.o server client -f