7.1 libevent基本概念
libevent是干什么的
- 开源的库,提高开发效率
- 封装了socket通信
- 封装了IO多路转接
- 精简,专注于网络,高性能
- 事件驱动
libevent库的安装
- 官方网站:http://libevent.org
- 源码下载:
- 1.4.x – 适合源码学习
- 2.x
- 源码包的安装:
./configure
- 检测安装环境
- 生成makefile
make
- 编译源代码
- 生成库、可执行文件
sudo make install
- 将数据拷贝到系统中
- 默认目录
/usr/local
–/usr/local/include
–/usr/local/bin
–/usr/local/lib
libevent库的使用
- 编译程序的时候指定
-levent
即可- 常用头文件:
#include <event2/event.h>
#include <event2/listener.h>
动态库和找不到:
1.找到xxx.so放到/usr/lib
/lib
– 不推荐
sudo find /usr/local -name "libevent.so"
2.将xxx.so放到环境变量中
LD_LIBRARY_PATH
- export LD_LIBARY_PATH=xxxx
- 重新加载资源
–.~/.bashrc
–./etc/profile
7.2 使用流程
- 创建一个事件处理框架
- 创建一个事件
- 事件添加到事件处理框架上
- 开始事件循环
- 释放资源给
7.3 事件处理框架event_base
使用 libevent 函数之前需要分配一个或者多个
event_base
结构体。每个event_base
结构体持有一个事件集合,可以检测以确定哪个事件是激活的。
struct eventop {
const char *name;
void *(*init)(struct event_base *); // 初始化
int (*add)(void *, struct event *); // 注册事件
int (*del)(void *, struct event *); // 删除事件
......
};
- 创建event_base
struct event_base* event_base_new(void);
- 失败返回NULL
- 释放event_base
event_base_free(struct event_base* base);
- 循环监听base对应的事件, 等待条件满足
event_base_dispatch();
- 查看event_base封装的后端
const char **event_get_supported_methods(void);
const char *event_base_get_method(const structevent_base *base);
- event_base和fork
- 子进程创建成功之后, 父进程可以继续使用event_base
- 子进程中需要继续使用event_base需要重新进程初始化
int event_reinit(struct event_base* base);
7.4 事件创建 - event
7.4.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_t fd, // 文件描述符 - int
short what,
event_callback_fn cb, // 事件的处理动作
void *arg // 回调函数的参数
);
- 调用
event_new()
函数之后, 新事件处于已初始化和非未决状态
7.4.2 释放事件
void event_free(struct event *event);
7.4.3 设置未决事件
构造事件之后,在将其添加到 event_base 之前实际上是不能对其做任何操作的。使用
event_add()
将事件添加到event_base
, 非未决事件 -> 未决事件.
/*
* 函数功能:
* 将事件添加到event_base中
* 参数:
* tv:
* NULL: 事件被触发, 对应的回调被调用
* tv = {0, 100}, 如果设置时间,在改时间段内检测的事件没被触发, 时间到达之后, 回调函数还是会被调用
* 返回值:
* 成功:0
* 失败:-1
*/
int event_add(
struct event *ev,
const struct timeval *tv
);
7.4.4 设置非未决
/*
* 对已经初始化的事件调用 event_del()
* 将使其成为非未决和非激活的。
* 如果事件不是未决的或者激活的,调用将没有效果。
* 成功时函数返回 0,失败时返回-1。
*/
int event_del(struct event *ev);
7.5 事件循环
一旦有了一个已经注册了某些事件的 event_base, 就需要让libevent 等待事件并且通知事件的发生。
/*
* 事件只会被触发一次
* 事件没有被触发, 阻塞等待
*/
#define EVLOOP_ONCE 0x01
/*
* 非阻塞 等方式去做事件检测
* 不关心事件是否被触发了
*/
#define EVLOOP_NONBLOCK 0x02
/*
* 没有事件的时候, 也不退出轮询检测
*/
#define EVLOOP_NO_EXIT_ON_EMPTY 0x04
/*
* 正常退出返回0, 失败返回-1
*/
int event_base_loop(struct event_base *base, int flags);
/*
* 等同于没有设置标志的 event_base_loop ( )
* 将一直运行,直到没有已经注册的事件了,或者调用了
* event_base_loopbreak()或者 event_base_loopexit()为止。
*/
int event_base_dispatch(struct event_base*
base);
/*
* 函数功能:
* 循环停止,如果 event_base 当前正在执行激活事件的回调 ,它将在执行完当前正在处理的事件后立即退出
* 参数:
* base:
* 返回值:
* 成功:0
* 失败:-1
*/
int event_base_loopexit(struct event_base *base, const struct timeval *tv);
/*
* 让event_base 立即退出循环
*/
int event_base_loopbreak(struct event_base *base);
struct timeval {
long tv_sec;
long tv_usec;
};
示例:使用event读管道
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.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:%s\n", buf);
}
int main(int argc, const char *argv[])
{
// 创建有名管道
unlink("myfifo");
mkfifo("myfifo", 0664);
int fd = open("myfifo", O_RDONLY | O_NONBLOCK);
// 创建事件处理框架
struct event_base *base = event_base_new();
// 创建事件
struct event *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;
}
示例:使用event写管道
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <event2/event.h>
void write_cb(evutil_socket_t fd, short what,void *arg)
{
char buf[1024] = { 0 };
static int num = 0;
sprintf(buf, "num:%d", num++);
int len = write(fd, buf, strlen(buf)+1);
printf("data:%s\n", buf);
}
int main(int argc, const char *argv[])
{
int fd = open("myfifo", O_WRONLY | O_NONBLOCK);
// 创建事件处理框架
struct event_base *base = event_base_new();
// 创建事件 检测的是写缓冲区是否有空间写
struct event *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;
}
7.6 使用bufferevent
7.6.1 创建使用基于套接字的bufferevent
使用
bufferevent_socket_new()
创建基于套接字的bufferevent
/*
* 函数功能:
* 创建基于套接字的bufferevent
* 参数:
* base:event_base
* fd:文件描述符
* options:常用BEV_OPT_CLOSE_ON_FREE
* 返回值:
* 成功:返回一个bufferevent
* 失败:NULL
*/
struct bufferevent *bufferevent_socket_new(
struct event_base *base,
evutil_socket_t fd,
enum bufferevent_options options
);
7.6.2 在bufferevent上启动链接
/*
* 函数功能:
* 在bufferevent上启动链接
* 参数:
* bev:
* address:server端的IP和端口
* addrlen:address大小
* 返回值:
* 成功:
* 失败:
*/
int bufferevent_socket_connect(
struct bufferevent *bev,
struct sockaddr *address,
int addrlen
);
7.6.3 释放bufferevent操作
/*
* 函数功能:
* 释放 bufferevent
* 参数:
* bev:
*/
void bufferevent_free(struct bufferevent *bev);
7.6.4 bufferevent读写缓冲区回调操作
typedef void (*bufferevent_data_cb)(
struct bufferevent *bev,
void *ctx
);
typedef void (*bufferevent_event_cb)(
struct bufferevent *bev,
short events,
void *ctx
);
/*
* 函数功能:
* 给读写缓冲区设置回调函数
* 参数:
* bufev:创建的bufferevent
* readcb:读缓冲区的回调函数
* writecb:写缓冲区的回调函数
* eventcb:事件(异常、断开链接等)回调函数
* cbarg:回调函数参数
* 返回值:
* 成功:
* 失败:
*/
void bufferevent_setcb(
struct bufferevent *bufev,
bufferevent_data_cb readcb,
bufferevent_data_cb writecb,
bufferevent_event_cb eventcb,
void *cbarg
);
events参数 | 说明 |
---|---|
EV_EVENT_READING | 读取操作时发生某事件,具体是哪种事件请看其他标志 |
BEV_EVENT_WRITING | 写入操作时发生某事件,具体是哪种事件请看其他标志 |
BEV_EVENT_ERROR | 操作时发生错误。关于错误的更多信息,请调用EVUTIL_SOCKET_ERROR() |
BEV_EVENT_TIMEOUT | 发生超时 |
BEV_EVENT_EOF | 遇到文件结束指示 |
BEV_EVENT_CONNECTED | 请求的连接过程已经完成 |
7.6.5 启用、禁用缓冲区
/*
* 可以启用或者禁用 bufferevent 上的 EV_READ、EV_WRITE
* 或者 EV_READ | EV_WRITE 事件。
* 没有启用读取或者写入事件时,
* bufferevent 将不会试图进行数据读取或者写入。
* 禁用之后对应的回调就不会被调用。
*/
void bufferevent_enable(
struct bufferevent *bufev,
short events
);
void bufferevent_disable(
struct bufferevent *bufev,
short events
);
short bufferevent_get_enabled(
struct bufferevent *bufev
);
/*
* 默认EV_WRITE是enable
* EV_READ是关闭的
*/
7.6.7 操作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
);
7.7 链接监听
7.7.1 创建和释放evconnlistener
typedef void (*evconnlistener_cb)(
struct evconnlistener *listener, //evconnlistener_new_bind的返回值
evutil_socket_t sock, // 用于通信的文件描述符
struct sockaddr *addr, // client地址结构
int len, // addr大小
void *ptr // 外部参数
);
struct evconnlistener * evconnlistener_new(
struct event_base *base,
evconnlistener_cb cb,
void *ptr,
unsigned flags,
int backlog,
evutil_socket_t fd
);
/*
* 函数功能:
* 创建监听socket,绑定,监听,等待并接受连接
* 参数:
* base:事件处理框架
* cb:回调函数,接受连接之后,用户要做什么
* ptr:回调函数的参数
* flags:常用
* LEV_OPT_CLOSE_ON_FREE:
* 如果设置了这个选项,释放连接监听器会关闭底层套接字
* LEV_OPT_REUSEABLE:
* 某些平台在默认情况下,关闭某监听套接字后,
* 要过一会儿其他套接字才可以绑定到同一个端
* 口。设置这个标志会让 libevent 标记套接
* 字是可重用的,这样一旦关闭,可以立即打开其
* 他套接字,在相同端口进行监听。
* backlog:-1:使用默认的最大值
* sa:服务器的IPheport信息
* socklen:sa的大小
* 返回值:
* 成功:
* 失败:
*/
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 连接。
* 新连接到达时,监听 器调用你给出的回调函数
* 参数 flags :参考手册-page99-100(可识别的标志)
*/
void evconnlistener_free(struct evconnlistener *lev);
7.7.2 启用和禁用evconnlistener
int evconnlistener_disable(struct evconnlistener *lev);
int evconnlistener_enable(struct evconnlistener *lev);
/* 这两个函数暂时禁止或者重新允许监听新连接。 */
7.7.3 调整evconnlistener的回调函数
void evconnlistener_set_cb(
struct evconnlistener *lev,
evconnlistener_cb cb,
void *arg
);
/* 函数调整 evconnlistener 的回调函数和其参数 */
示例:
// server.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/listener.h>
void read_cb(struct bufferevent* bev, void *arg)
{
// read data from buffer
char buf[1024] = { 0 };
int len = bufferevent_read(bev, buf, sizeof(buf));
printf("recv buf:%s\n", buf);
char *pt = "recived";
bufferevent_write(bev, pt, strlen(pt)+1);
}
void write_cb(struct bufferevent* bev, void *arg)
{
printf("recived successed\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);
}
// 连接完成之后对应的通信操作
void listen_cb(
struct evconnlistener *listener,
evutil_socket_t fd,
struct sockaddr *addr,
int len,
void *ptr)
{
// 将fd封装为带缓冲区的事件
struct event_base* base = (struct event_base*)ptr;
struct bufferevent *bev = NULL;
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[])
{
// 创建事件处理框架
struct event_base *base = event_base_new();
// init server info
struct sockaddr_in serv;
memset(&serv, 0x00, sizeof(serv));
serv.sin_family = AF_INET;
serv.sin_port = htons(9876);
serv.sin_addr.s_addr = htonl(INADDR_ANY);
// 创建监听套接字、绑定、监听、等待并接受连接请求
struct evconnlistener* listen = NULL;
listen = evconnlistener_new_bind(
base,
listen_cb, // 有新连接是该函数被调用
base,
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
-1,
(struct sockaddr*)&serv,
sizeof(serv)
);
// 开始事件循环
event_base_dispatch(base);
// 释放资源
evconnlistener_free(listen);
event_base_free(base);
return 0;
}
// client.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <arpa/inet.h>
void read_cb(struct bufferevent* bev, void *arg)
{
// read data from buffer
char buf[1024] = { 0 };
int len = bufferevent_read(bev, buf, sizeof(buf));
printf("serv send:%s\n", buf);
char *pt = "recived";
}
void write_cb(struct bufferevent* bev, void *arg)
{
printf("send successed\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("connected succussed\n");
return;
}
// 释放资源
bufferevent_free(bev);
}
// 终端接收数据并将数据发送给server
void read_terminal_cb(int 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 = event_base_new();
// 创建事件
int fd = socket(AF_INET, SOCK_STREAM, 0);
struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
// 连接服务器
struct sockaddr_in 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_cb, bev);
// 添加事件
event_add(ev, NULL);
// 开始事件循环
event_base_dispatch(base);
// 释放资源
bufferevent_free(bev);
event_base_free(base);
return 0;
}