Libevent
Libevent
libevent是跨平台开源库,专注网络处理高并发
,封装了socket通信和I/O多路转接,是一个开源框架,代码精简性能高(使用大量回调函数
),事件驱动
处理高并发时,epoll只能在Linux下 ,不想封装epoll可以用Libevent实现,windows下可以用select,也可以用Libevent实现
Libevent安装
-
在官网下载Libevent安装包
-
在DownLoads下解压
tar -zxvf libevent-2.1.8-stable.tzr.gz cd libevent-2.1.8-stable
-
源码安装
./configure //检测安装环境,生成makefie
-
make编译安装Libevent库
make
-
拷贝数据到对应目录
sudo make install //目录不存在则创建对应目录,一般安装在默认目录/usr/lcoal // /usr/local/include 头文件 // /usr/local/bin 可执行二进制文件 // /usr/local/lib 库文件
-
检测安装成功
cd sample ./ hello-world //sample中的实例程序 nc 127.1 9995 //成功通信
-
编译程序
gcc hello-world,c -o -levent
Libevent使用流程
- 创建一个事件处理框架
- 创建一事件
- 事件添加到事件处理框架
- 开始事件循环
- 释放资源
创建事件处理框架
事件处理框架,其中有消息循环,需要自己启动
-
创建event_base
struct event_base* event_base_new(void); //失败返回NULL
创建事件(不带缓冲区)
一般可以用于管道,普通文件描述符
struct event *event_new(struct event_base *base, evutil_socket_t fd, short what, event_callback_fn cb,void *arg
)
—struct event_base *base 创建的事件框架
—evutil_socket_t fd,文件描述符
— short what 事件
- #define EV_TIMEOUT 0x01 // 废弃
- #define EV_READ 0x02 epollin
- #define EV_WRITE 0x04 epollout
- #define EV_SIGNAL 0x08 libevent封装了信号操作
- #define EV_PERSIST 0x10 // 持续触发
- #define EV_ET 0x20 // 边沿模式 ,默认为水平模式
what的设置决定了事件循环的工作模式,如果是指定了 EV_READ 并指定EV_PERSIST,则消息循环会一直检测直至通信一方关闭连接,如果不设置消息循环只检测一次
,消息循环停止
—event_callback_fn cb what事件对应的回调函数,事件的处理动作
—void *arg 给cb传参
—没有缓冲区
释放事件
void event_free(struct event *event);
回调函数
typedef void (*event_callback_fn)(evutil_socket_t, short, void *);
设置未决事件
构造事件之后,在将其添加到 event_base 之前实际上 ,是不能对其做任何操作的。使用event_add()将事件 添加到event_base, 非未决事件 -> 未决事件.
int event_add(struct event *ev, const struct timeval *tv)
—const struct timeval *tv
- 在tv时间内(如{0,100}),没有事件发生回调函数也会调用
- tv设置为NULL,则事件被触发对应回调才会调用
设置非未决事件
对已经初始化的事件调用 event_del()将使其成为非未决和非激活的,如果事件不是未决的或者激活的,调用将没有效果
int event_del(struct event *ev);
事件循环
循环开始
int event_base_dispatch(struct event_base* base)
- 等同于没有设置标志的 event_base_loop ( )
- 将一直运行,直到没有已经注册的事件了,或者调用 event_base_loopbreak()或者 event_base_loopexit()为止
循环终止
事件循环事件很长,可能为死循环
int event_base_loopexit(struct event_base *base,const struct timeval *tv )
//tv时间后如果event_base在执行激活事件的回调,则在执行完当前处理事件后退出
int event_base_loopbreak(struct event_base *base);
//强制终止循环
释放资源
要先释放事件
event_base_free(struct event_base* base)
Libevent内部事件状态转换
非未决事件:没有资格被处理的事件
未决事件:有资格但还没被处理的事件
数据缓冲区Bufferevent
套接字通信时,需要带缓冲区的事件Bufferevent,头文件include<event/bufferevent.h>
套接字的文件描述符对应内核中一块缓冲区(又分为读写两块缓冲区),Libevent提供的Bufferevent也提供了这样的机制(自带缓冲区),事件读操作时,内核传输数据会放到Bufferevent缓冲区,因此读数据直接从Libevent提供的缓冲区读就行
- bufferevent 由一个
底层的传输端口(如套接字 )
, 一个读取缓冲区
和一个写入缓冲区
组成 bufferevent 在读取或者写入了足够量的数据之后调用用户提供的回调
- 每个 bufferevent 有两个数据相关的回调
– 读缓冲区对应一个读取回调
– 写缓冲区对应一个写入回调
(写缓冲区只要有数据就会自动发送,写入回调通知数据已发送,用处不大)
bufferevent和event区别
bufferevent是event升级版,它为事件维护了一块内存缓冲区供数据收发时使用(不用内核),读写缓冲区被操作时都有对应的回调函数可以进行相应的操作
创建基于套接字的bufferevent
struct bufferevent *bufferevent_socket_new(struct event_base *base,struct sockaddr *address, int addrlen
)
—evutil_socket_t fd
—enum bufferevent_options options
- options: BEV_OPT_CLOSE_ON_FREE (一般只用这个)//释放 bufferevent 时关闭底层传输端口。这将关闭底层套接字,释放底层 bufferevent 等
—成功时函数返回一个 bufferevent,失败则返回 NULL
释放bufferevent
void bufferevent_free(struct bufferevent *bev);
读写缓冲区设置回调
给读写缓冲区设置回调
void bufferevent_setcb(struct bufferevent *bufev, bufferevent_data_cb readcb,bufferevent_data_cb writecb,bufferevent_event_cb eventcb,void *cbarg)
— bufferevent_data_cb readcb读
缓冲区有对应事件发生,读调用,在读回调中读数据,调用 bufferevent_read()
—bufferevent_data_cb writecb
写缓冲区有对应事件发生,写回调,可以直接传NULL
—bufferevent_event_cb eventcb
断开连接,连接成功等事件发生可以通过eventcb处理,可以传入NULL,
- EV_EVENT_READING:读取操作时发生某事件,具体是哪种事件请看其他标志
- BEV_EVENT_WRITING:写入操作时发生某事件,具体是哪种事件请看其他标志
- BEV_EVENT_ERROR:操作时发生错误。关于错误的更多信息
- BEV_EVENT_EOF:遇到文件结束指示
BEV_EVENT_CONNECTED
:请求的连接过程已经完成
–实现客户端适合可以用来判断
bufferevent_data_cb是被typedef定义过的函数指针类型
- typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx)
- setcb()时的 void* cbarg指向的内存传给readcb,由void *ctx接收
bufferevent启动链接
套接字通信时客户端用Libevent封装时调用
int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *address, int addrlen)
启用/禁用缓冲区
禁用之后对应的回调不会被调用,即事件不会再处理
void bufferevent_enable( struct bufferevent *bufev, short events)
—events : EV_READ、EV_WRITE 或者 EV_READ | EV_WRITE
—默认ev_write是打开的
—默认ev_read是关闭的
(读缓冲的读回调默认关闭)
禁用
void bufferevent_disable(struct bufferevent *bufev, short events)
判断当前buffevent读写是否可用
short bufferevent_get_enabled(struct bufferevent *bufev)
链接监听器
套接字通信时服务器端调用,完成了创建监听套接字,绑定,监听,接收连接请求四步
struct evconnlistener *evconnlistener_new_bind(struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags,int backlog, const struct sockaddr *sa, int socklen)
—(struct event_base *base 事件处理框架
— evconnlistener_cb cb 接收连接后的用户操作
—unsigned flags
- LEV_OPT_CLOSE_ON_FREE 释放链接监听器会关闭底层套接字
- LEV_OPT_REUSEABLE libevent标记套接字可复用
—void *ptr 给回调函数cb传参,ptr指向的内存区域由cb->ptr取得
—int backlog:同时监听上限(128),可以传入-1即默认使用最大值
— const struct sockaddr *sa 服务器IP和端口信息
typedef void (*evconnlistener_cb)(
struct evconnlistener *listener,
- listener与evconnlistener_new_bind返回值指向同一片内存
evutil_socket_t sock,
- 用于通信的文件描述符
struct sockaddr *addr,
-客户端IP和端口信息
int len,
void *ptr
)
回调函数的填充由调用者实现,一般由操作系统调用,只注意实现即可
重新设置evconnlistener 的回调函数
void evconnlistener_set_cb(struct evconnlistener *lev, evconnlistener_cb cb, void *arg)
启用/禁用监听器
禁用
int evconnlistener_disable(struct evconnlistener *lev);
启用
int evconnlistener_enable(struct evconnlistener
很少用到这两个函数
操作bufferevent中的数据
向bufferevent的输出缓冲区添加数据,写数据后会有size_t size这个字符串会被自动发送出去,写回调就会被调用
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);
释放链接监听器
void evconnlistener_free(struct evconnlistener
Libevent实现客户端代码
#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;
}