libevent是一个事件通知库;封装了reactor。Libevent支持Linux、Unix和Windows。对I/O事件、信号和定时事件提供统一的处理。使用libevent_pthread库来提供线程安全支持。基于Reactor模式实现。事件驱动、高性能、轻量级、专注于网络、跨平台、支持多种I/O多路复用技术、支持I/O,定时器和信号事件、注册事件优先级。
tar zxvf libevent-2.1.8-stable.tar.gz # 解压
cd libevent-2.1.8-stable
./configure --prefix=/xxx/install --host=arm-linux-gnueabihf
make
make install
cd /xxx/install
tar -zcvf libevent-2.1.12-install.tar.gz lib/*.so*
# 将 libevent-2.1.12-install.tar.gz 复制到开发板上
# 新建文件夹:/usr/local/lib/libevent , 然后解压到该文件夹中
sudo mkdir /usr/local/lib/libevent
sudo tar -zxf ./libevent-2.1.12-install.tar.gz --strip-components 1 -C /usr/local/lib/libevent
# 开发板上添加库文件搜索路径
sudo vi /etc/ld.so.conf.d/libc.conf
# 在 /etc/ld.so.conf 文件中添加库的搜索路径
/usr/local/lib/libevent //根据自己的库路径添加
# 然后 ldconfig 生成/etc/ld.so.cache,可用ldconfig -v 查看
ldconfig
#include
#include
int main()
{
char ** methods = event_get_supported_methods();//获取libevent后端支持的方法
int i =0;
for(i = 0; methods[i] != NULL; i++)
{
printf("%s\n",methods[i]);
}
struct event_base *base = event_base_new();
printf("----%s\n",event_base_get_methods(base));
return 0;
}
/**
dongfang@dongfang-virtual-machine:~/NetWorkProgramming$ ./getmethods
epoll
poll
select
----epoll
*/
编译:gcc getmethods.c -o getmethods -levent
解决报错:error while loading shared libraries: libevent-2.1.so.6: cannot open shared object file: No such file or directory
ldd getmethods
linux-vdso.so.1 (0x00007ffdcc7dd000)
libevent-2.1.so.6 => not find
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f1f4e200000)
/lib64/ld-linux-x86-64.so.2 (0x00007f1f4e4ea000)
find / -name libevent-2.1.so.6 --> 找到该文件在 /usr/local/lib下面
LD_DEBUG=libs ./getmethods -v # 查看程序搜索库时的路径
可发现他寻找的地址为 /usr/lib/libevent-2.1.so.6 , 所以我们需要添加软链接过去
sudo ln -s /usr/local/lib/libevent-2.1.so.6 /usr/lib/libevent-2.1.so.6
在 .libs 隐藏文件中包含全部libevent已经编译好的so文件。其中core为libevent的核心文件,libevent.so为主链接文件,会关联到其他全部so文件。在sample目录下会有已经编译好的服务器应用程序。在libevent的源码中的sample目录下面提供了很多例子。
使用 libevent 函数之前需要分配一个或者多个 event_base 结构体。每个event_base 结构体持有一个事件集合**,**可以检测以确定哪个事件是激活的。**event_base_new()**函数分配并且返回一个新的具有默认设置的 event_base。函数会检测环境变量,返回一个到 event_base 的指针。如果发生错误,则返回 NULL。选择各种方法时,函数会选择 OS 支持的最快方法。
struct event_base *event_base_new(void);
申请到的 event_base 指针通过 event_base_free 函数进行释放。
void event_base_free(struct event_base *base)
如果fork出子进程,想在子进程继续使用event_base,那么子进程需要对event_base重新初始化,函数如下:
int event_reinit(struct event_base *base)
创建好libevent_base根节点后,需要等待事件的产生,也就是等待想要等待的事件的激活,在libevent中提供了对应的接口,类似 while(1){ epoll_wait() }
的功能函数。
int event_base_loop(struct event_base* base, int flags); flags的取值: #define EVLOOP_ONCE 0x01 只触发一次,如果事件没有被触发,那么就阻塞等待 #define EVLOOP_NONBLOCK 0x02 非阻塞方式检测事件是否触发
大多时候我们会调用另一个api,它相当于flags=0时候的event_base_loop函数
int event_base_dispatch(struct event_base *base); 该函数相当于没有设置标志位的event_base_loop。程序会一直运行下去,直到没有需要检测的事件了,或者被结束循环的api终止。
等待一定时间后退出循环
int event_base_loopexit(struct event_base *event_base, const struct timeval *tv) struct timeval{ long tv_sec; long tv_usec; };
直接退出循环
int event_base_loopbreak(struct event_base *event_base)
生成新事件,使用 event_new()接口创建事件。
struct event *event_new(struct event_base *base, evutil_socket_t fd, short events, event_callback_fn cb, void *arg); base: event_base根节点 fd: 上树的文件描述符 events: 监听的事件 #define EV_TIMEOUT 0x01 // 超时事件 #define EV_READ 0x02 // 读事件 #define EV_WRITE 0x04 // 写事件 #define EV_SIGNAL 0x08 // 信号事件 #define EV_PERSIST 0x10 // 周期性触发 #define EV_ET 0x20 // 设置边沿触发 cb: void (*event_callback_fn)(evutil_socket_t fd, short events, void *arg); 回调函数 arg: 传入回调函数的参数
构造事件之后,在将其添加到 event_base 之前实际上是不能对其做任何操作的。使用event_add()将事件添加到 event_base。
int event_add(struct event *ev, const struct timeval *tv); ev:上树节点的地址 tv: NULL 永久监听,固定时间 限时等待
int event_del(struct event *ev);
void event_free(struct event *ev);
#include
#include
#include "wrap.h"
#define MAX 1000
// 用于保存accept到的各个客户端的cfd以及上树节点
struct eventfd {
evutil_socket_t fd;
struct event *ev;
}event_fd[MAX];
void init_event_fd(void)
{
int i = 0;
for(i = 0;i < MAX; i++)
{
event_fd[i].fd = -1;
event_fd[i].ev = NULL;
}
}
void set_event_fd(evutil_socket_t fd, struct event* ev)
{
int i = 0;
for(i = 0; i < MAX; i++)
{
if(event_fd[i].fd == -1)
break;
}
if(i == MAX)
exit(1);
event_fd[i].fd = fd;
event_fd[i].ev = ev;
}
int find_event_fd(int fd)
{
int i = 0;
for(i = 0; i < MAX; i++)
{
if(event_fd[i].fd == fd)
break;
}
if(i == MAX)
{
printf("not find fd = %d\n",fd);
exit(1);
}
return i;
}
void cfd_cb(evutil_socket_t cfd, short events, void* arg)
{
char buf[1500] = "";
memset(buf, 0, 1500);
int n = Read(cfd, buf, sizeof(buf));
if(n <= 0)
{
perror("err or close!\n");
int i = find_event_fd(cfd);
event_del(event_fd[i].ev);
event_free(event_fd[i].ev);
Close(cfd);
event_fd[i].fd = -1;
event_fd[i].ev = NULL;
}else
{
printf("%s\n",buf);
Write(cfd, buf, n);
}
}
void lfd_cb(evutil_socket_t lfd, short events, void* arg)
{
struct event_base *base = (struct event_base*)arg;
int cfd = Accept(lfd, NULL, NULL);
if(cfd < 0)
{
perror("Accept error");
return;
}
struct event *ev = event_new(base, cfd, EV_READ | EV_PERSIST, cfd_cb, NULL);
event_add(ev, NULL);
set_event_fd(cfd, ev);
}
int main(int argc, char* argv[])
{
init_event_fd();
// 创建套接字并绑定,返回服务器套接字
int lfd = tcp4bind(8000, NULL);
// 监听
Listen(lfd, 128);
// 创建event_base
struct event_base* base = event_base_new();
// 初始化lfd上树节点
struct event* ev = event_new(base, lfd, EV_READ | EV_PERSIST, lfd_cb, base);
// 上树
event_add(ev, NULL);
// 循环监听
event_base_dispatch(base);
Close(lfd);
event_base_free(base);
return 0;
}
bufferevent 是 libevent 中的一个事件缓冲 IO,内部实现了基本 socket recv/send 操作 ,用户只需要调用 bufferevent 的 API 即可实现数据的读写。libevent的bufferevent事件对应一个文件描述符、两个缓冲区、三个回调函数。在libevent的应用层有读写缓冲区,对应底层的读写缓冲区。底层的读缓冲区数据拷贝到应用层缓冲区会触发读回调,从应用层缓冲区将数据写入底层缓冲区会触发写回调。还有一个事件回调,用于出错、断开连接等触发的事件回调。
创建新的节点
struct bufferevent *bufferevent_socket_new(struct event_base *base, evutil_socket_t fd, int options); base : 根节点 fd : 文件描述符 options : enum bufferevent_options { // 释放bufferevent自动关闭底层接口fd BEV_OPT_CLOSE_ON_FREE = (1<<0), //使bufferevent能够在多线程下是安全的 BEV_OPT_THREADSAFE = (1<<1), // 使事件循环中延迟运行回调 BEV_OPT_DEFER_CALLBACKS = (1<<2), // 执行回调时不会在缓冲区事件上保留锁, 要求BEV_OPT_DEFER_CALLBACKS同时也被设置 // 将来BEV_OPT_UNLOCK_CALLBACKS可能会被libevent作者考虑删除 BEV_OPT_UNLOCK_CALLBACKS = (1<<3) }; 返回值:初始化新建节点的地址
设置节点回调
void bufferevent_setcb(struct bufferevent *bufev, bufferevent_data_cb readcb, bufferevent_data_cb writecb, bufferevent_event_cb eventcb, void *cbarg); bufev: 新建节点的地址 readcb: 读回调 writecb:写回调 eventcb:事件异常回调 cbarg: 传给回调函数的参数 typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx); typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short what, void *ctx); what: #define BEV_EVENT_READING 0x01 /**< error encountered while reading */ #define BEV_EVENT_WRITING 0x02 /**< error encountered while writing */ #define BEV_EVENT_EOF 0x10 /**< eof file reached /对方关闭连接 */ #define BEV_EVENT_ERROR 0x20 /**< unrecoverable error encountered */ #define BEV_EVENT_TIMEOUT 0x40 /**< user-specified timeout reached */ #define BEV_EVENT_CONNECTED 0x80 /**< connect operation finished. */
事件回调的使能和不使能函数
int bufferevent_enable(struct bufferevent *bufev, short event); int bufferevent_disable(struct bufferevent *bufev, short event); event: EV_READ | EV_WRITE
发送数据
int bufferevent_write(struct bufferevent *bufev,const void *data, size_t size); 将data的数据写到buffer_event的写缓冲区中
接收数据
size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size); 将bufferevent的读缓冲区数据读取到data中, 同时将读取到的数据从bufferevent的读缓冲区删除。
连接侦听器,创建套接字、监听并提取
struct evconnlistener * evconnlistener_new_bind(struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog, const struct sockaddr *sa, int socklen) base: base根节点 cb: 提取套接字cfd后调用的回调 typedef void (*evconnlistener_cb)(struct evconnlistener *evl, evutil_socket_t fd, struct sockaddr *cliaddr, int socklen, void *ptr); evl: 链接侦听器的地址 fd: cfd cliaddr: 客户端的地址信息 socklen: 地址信息长度 ptr: 传递给回调函数参数 ptr: 传给回调的参数 flags: LEV_OPT_LEAVE_SOCKETS_BLOCKING 文件描述符为阻塞 LEV_OPT_CLOSE_ON_FREE *关闭时自动释放 LEV_OPT_REUSEABLE *端口复用 LEV_OPT_THREADSAFE 分配锁,线程安全 backlog: -1 自动填充 sa: 绑定的地址信息 socklen: sa的大小 返回值:链接侦听器的地址
封装了底层的socket和connect接口,通过调用此函数将bufferevent事件和通信的socket进行绑定
int bufferevent_socket_connect(struct bufferevent *evl, const struct sockaddr *serv, int scoklen); evl: 新建的bufferevent节点 serv: 服务器地址 socklen: 服务器长度 1、创建节点 base = event_base_new(); int fd = socket(AF_INET, SOCK_STREAM, 0); struct bufferevent *evl = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE); struct bufferevent *evl = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); 2、封装 bufferevent_socket_connect(evl, serv, socklen);
释放链接监听器
void evconnlistener_free(struct evconnlistener *lev);
参考hello-world.c更改为TCP收发数据
#include
#include
#include
#include
#ifndef _WIN32
#include
# ifdef _XOPEN_SOURCE_EXTENDED
# include
# endif
#include
#endif
#include
#include
#include
#include
#include
static const char MESSAGE[] = "Hello, World!\n";
static const int PORT = 9995;
static void listener_cb(struct evconnlistener *, evutil_socket_t,
struct sockaddr *, int socklen, void *);
static void conn_writecb(struct bufferevent *, void *);
static void conn_readcb(struct bufferevent *, void *);
static void conn_eventcb(struct bufferevent *, short, void *);
static void signal_cb(evutil_socket_t, short, void *);
int main(int argc, char **argv)
{
struct event_base *base;
struct evconnlistener *listener;
struct event *signal_event;
struct sockaddr_in sin;
#ifdef _WIN32
WSADATA wsa_data;
WSAStartup(0x0201, &wsa_data);
#endif
base = event_base_new(); // 创建event_base根节点
if (!base) {
fprintf(stderr, "Could not initialize libevent!\n");
return 1;
}
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
// 创建链接监听器 listener_cb监听回调
listener = evconnlistener_new_bind(base, listener_cb, (void *)base,
LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE, -1,
(struct sockaddr*)&sin,
sizeof(sin));
if (!listener) {
fprintf(stderr, "Could not create a listener!\n");
return 1;
}
// 创建信号触发节点,收到信号SIGINT,调用信号signal_cb
signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);
// 上树监听
if (!signal_event || event_add(signal_event, NULL)<0) {
fprintf(stderr, "Could not create/add a signal event!\n");
return 1;
}
// 循环监听
event_base_dispatch(base);
// 释放链接监听器、信号节点、base根节点
evconnlistener_free(listener);
event_free(signal_event);
event_base_free(base);
printf("done\n");
return 0;
}
static void
listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
struct sockaddr *sa, int socklen, void *user_data)
{
struct event_base *base = user_data;
struct bufferevent *bev;
// 创建一个bufferevent节点,释放自动关闭
bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
if (!bev) {
fprintf(stderr, "Error constructing bufferevent!");
event_base_loopbreak(base);
return;
}
// 设置回调函数并上树,写回调conn_writecb, 异常事件回调conn_eventcb
bufferevent_setcb(bev, conn_readcb, conn_writecb, conn_eventcb, NULL);
bufferevent_enable(bev, EV_WRITE | EV_READ); // 设置读写事件使能
// bufferevent_disable(bev, EV_READ); // 设置读时间非使能
// 给 cfd 发送消息 MESSAGE
bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
}
static void
conn_writecb(struct bufferevent *bev, void *user_data)
{
// 获取缓冲区的类型
struct evbuffer *output = bufferevent_get_output(bev);
if (evbuffer_get_length(output) == 0) { // 缓冲区中没有数据了
// printf("flushed answer\n");
// bufferevent_free(bev); // 释放节点,自动关闭
}
}
static void
conn_readcb(struct bufferevent *bev, void *user_data)
{
char buff[1500] = "";
int n = bufferevent_read(bev, buff, sizeof(buff)); // 读取数据
printf("%s\n", buff);
bufferevent_write(bev, buff, n); // 发送数据
}
static void
conn_eventcb(struct bufferevent *bev, short events, void *user_data)
{
if (events & BEV_EVENT_EOF) { // 如果连接已经关闭
printf("Connection closed.\n");
} else if (events & BEV_EVENT_ERROR) { // 如果连接发生错误
printf("Got an error on the connection: %s\n",
strerror(errno));/*XXX win32*/
}
/* None of the other events can happen here, since we haven't enabled
* timeouts */
bufferevent_free(bev); // 释放bev
}
static void
signal_cb(evutil_socket_t sig, short events, void *user_data)
{
struct event_base *base = user_data; // 接收传入的base根节点
struct timeval delay = { 2, 0 };
printf("Caught an interrupt signal; exiting cleanly in two seconds.\n");
event_base_loopexit(base, &delay); // 2s后退出循环接听
}
可以监听 STDIN 、cfd 、服务器的数据等等
#include
#include
#include
#include
#include
#include
#include
#include
#include
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;
}