文章目录
libevent支持
select
(支持跨平台、性能开销大、可监听fd数量不能超过FD_SETSIZE)、poll
(类似select,但无FD_SETSIZE的限制)、epoll
(windows不支持、支持LT水平触发、ET边沿触发模式、性能快)、iocp
(linux不支持、内部维护有线程池处理)等多种网络模型,还支持有其他网络模型,本文仅涉及到这四种网络模型
libevent在未指定网络模型时按优先级别进行默认处理:
epoll > poll > select > iocp
环境配置及初始化
初始化
event_base_new(void)
创建上下文,可以进行一系列参数的设置,包括网络模型的选择
event_base_free(void)
销毁上下文
#include <event2/event.h>
int main()
{
#ifdef _WIN32
//windows下初始化网络环境
WSADATA wsa;
WSAStartup(MAKEWORD(2,2), &wsa);
#else
//linux下需要忽略SIGPIPE信号,否则向已关闭socket发送数据会崩溃
if(SIG_ERR == signal(SIGPIPE, SIG_IGN))
return -1;
#endif
//创建libevent上下文
event_base *base = event_base_new();
if(!base)
{
cout << "event_base_new failed" << endl;
}
//销毁上下文
if(base)
event_base_free(base);
#ifdef _WIN32
WSACleanup();
#endif
}
上下文属性配置
//event_base_new中会默认调用 event_base_new_with_config 接口,生成上下文
event_base * event_base_new_with_config(const struct event_config *);
//生成配置项
event_config * event_config_new(void);
//清理配置项
void event_config_free(struct event_config *cfg);
//设置配置项,涉及两块内容的配置event_config_set_flag; event_config_require_features
event_config_set_flag();
/*
EVENT_BASE_FLAG_NOLOCK
不为event_base分配锁,减少了加解锁开销,但多线程不安全
EVENT_BASE_FLAG_IGNORE_ENV
忽略环境变量的影响,选择使用的后端时,不检测EVENT_*环境变量,保证在不同的环境中运行的一致
EVENT_BASE_FLAG_STARTUP_IOCP
仅用于windows,启用任何必须的iocp分发逻辑
需要调用 event_config_set_num_cpus_hint,配置cpu的线程数量,iocp是多线程的,内部有维护线程池
EVENT_BASE_FLAG_NO_CACHE_TIME
不是在事件循环每次准备执行超时回调时检测当前时间,而在每次超时回调后进行检测,这会消耗更多的cpu时间,但时间会更加的精确
EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST
epoll下有效,防止同一个fd多次激发事件,fd如果做复制会有bug
EVENT_BASE_FLAG_PRECISE_TIMER
默认使用系统最快的记时机制,如果系统有较慢且更精准的则采用
*/
event_config_require_features();
/*
EV_FEATURE_ET = 0x01 边沿触发的后端
EV_FEATURE_01 = 0x02 要求添加、删除单个事件或确定哪个事件激活的操作是O(1)复杂度的后端(读取的时候开销可能会增大)
EV_FEATURE_FDS = 0x04 要求支持任意文件描述符,而不仅仅是套接字的后端(使用后就无法选择epoll了,epoll不支持文件描述符)
EV_FEATURE_EARLY_CLOSE = 0x08 检测连接关闭事件,允许使用EV_CLOSED检测连接关闭,无法在所有内核版本上都支持
*/
//避免使用指定网络模型
event_config_avoid_method(struct event_config *cfg, const char *method);
//重新初始化,调用fork之后可以正常工作,fork后子进程会复制父进程的内容,但有些内容复制是无效的,包括event,该接口可以保证复制后仍旧生效
int event_reinit(struct event_base *base);
上下文属性配置示例
//创建上下文配置项
event_config *conf = event_config_new();
//显示支持的网络模式
const char **methods = event_get_supported_methods();
//一组字符串,最后一个为NULL
for(int i = 0; methods[i] != NULL; i++)
{
cout << methods[i] << endl;
}
#ifdef _WIN32 //在windows中设置iocp
event_config_set_flag(conf, EVENT_BASE_FLAG_STARTUP_IOCP);
//使用iocp时,需要初始化在windows中的线程
evthread_use_windows_threads();
//设置cpu数量
SYSTEM_INFO si;
GetSystemInfo(&si);//获取cpu数量
event_config_set_num_cpus_hint(conf, si.dwNumberOfProcessors);//si.dwNumberOfProcessors:进程数量
#else
//设置特征
event_config_require_features(conf, EV_FEATURE_01);
//设置网络模型select
event_config_avoid_method(conf, "epoll");//不使用epoll网络模型
event_config_avoid_method(conf, "poll");//不使用poll网络模型
#endif
//根据配置项生成libevent上下文
event_base *base = event_base_new_with_config(conf);
//释放配置项,初始化完就可以释放了
event_config_free(conf);
if(!base)
{
cerr << "event_base_new_with_config failed" << endl;
}
else
{
cout << "event_base_new_with_config success" << endl;
//获取当前网络模型
cout << event_base_get_method(base) << endl;
//确认特征是否生效
int flag = event_base_get_features(base);
if(flag & EV_FEATURE_ET)
cout << "EV_FEATURE_ET is ok" << endl;
//... ...具体操作
event_base_free(base);
}
libevent封装的监听连接
#include <event2/event.h>
#include <event2/listener.h>
//回调函数,自定义
void evconnlistener_cb(struct evconnlistener *ev,
evutil_socket_t socket,
struct sockaddr *addr,
int socklen,
void *arg);
int main()
{
event_base *base = event_base_new();
if(base)
{
cout << "event_base_new success" << endl;
}
sockaddr_in seraddr;
memset(&seraddr, 0, sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(6000);
//地址不设置的话这里是绑定0,默认所有的地址连接都可以进来,但如果有多个网卡只指定其中一个网卡需要设置具体的服务器地址
//包含socket的create、bind、listen、绑定事件(连接事件)
evconnlistener *ev = evconnlistener_new_bind(base,
evconnlistener_cb,//接收到连接的回调接口
base,//传递给回调接口的参数,arg
LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE,
//对应的配置项,地址重用,evconnlistener关闭同时关闭socket
5,//连接队列大小
(sockaddr *)&seraddr//绑定的地址及端口
sizeof(seraddr)
);
//事件分发处理
if(base)
event_base_dispatch(base);
//清理
if(ev)
evconnlistener_free(base);
if(base)
event_base_free(base);
}
循环(loop)
主循环,处理所有的事件
int event_base_dispatch(struct event_base *base);//阻塞,等待事件发生
//该方式使用默认的值,判断是否有注册的事件event_add,没有会立即返回
int event_base_loop(struct event_base *base, int flags);
/*
该方式可以设置一些属性
EVLOOP_ONCE 0x01 等待一个事件运行,至少处理一次激活事件(处理完全部激活事件)才会返回
EVLOOP_NONBLOCK 0x02 非阻塞,没有事件发生立刻返回,有事件发生全部处理完后也立刻返回,相当于仅调用一次,需外部控制循环,事件触发调用时会根据优先级进行调用
EVLOOP_NO_EXIT_ON_EMPTY 0x04 没有注册事件循环也不会退出,用于事件后期多线程添加
*/
//停止循环
int event_base_loopexit(struct event_base *base, const struct timeval *tv);
//运行完所有的激活事件回调之后才退出,事件循环还没运行时会在下一轮调度完成后再立即停止
//在未到超时时间时会等待,若其中仍有事件激活会继续处理
int event_base_loopbreak(struct event_base *base);
//执行完当前正在处理的事件后立即退出,无操作立即退出
接收及处理数据
libevent对于数据的收发及处理有两种方式:事件回调、缓冲io
事件io处理
event_new
需要传递socket或其他文件描述符
evutil socket函数封装:
对于socket文件描述符的创建及处理,libevent也封装了一些接口,但也可自己处理
evutil_make_socket_nonblocking
evutil_make_listen_socket_reuseable
evutil_closesocket
事件状态
已初始化(initialized)、待决(pending)、激活(active)(persistent,持久的)
创建事件
//生成一个事件
event *event_new(struct event_base *base, evutil_socket_t fd, short what, event_callback_fn cb, void*arg);
//base:上下文
//fd:监听的文件描述符或socket
//what:事件标志
/*
EV_TIMEOUT 超时,默认忽略,只有在添加事件时设置超时时间才有效
EV_READ 读
EV_WRITE 写
EV_SIGNAL 信号
EV_PERSIST 持久的,未加的话事件触发就只有一次
EV_ET 边缘触发,需底层支持,2.0以上支持,影响EV_READ和EV_WRITE
*/
//cb:事件触发后的回调函数
//arg:传递给回调函数的参数
//回调函数
typedef void (*event_callback_fn)(evutil_socket_t, short, void*);
/*
evutil_socket_t:发生事件的文件描述符
short:what, 事件标志
void*:arg, 传递进来的参数
*/
分配设置事件
int event_assign(struct event *event,
struct event_base *base
evutil_socket_t fd,
short what,
void (*callback)(evutil_socket_t, short, void*),
void *arg);
//event:未初始化的事件
//该接口在测试时(debug)可能会有一些内存的问题,尽量使用event_new()
添加事件
int event_add(struct event *ev, const struct timeval *tv);
//ev:创建的事件
//tv:超时时间, 0没有超时时间
删除事件
int event_del(struct event *ev);
//事件状态变为non-pending, event空间并未释放
释放事件
void event_free(struct event *event);
事件示例:
//回调
static void signal_cb(int sock, short what, void *arg)
{
event *ev = (event *)arg;
//evsignal_pending()判断是否处于待决状态,即如果是非待决状态,第二个参数为获取待决状态超时时间,event_pending()多一个参数,要传递判断的事件类型
if(!evsignal_pending(ev, NULL))
{
//即便未加EV_PERSIST,每次也能重新添加事件
event_del(ev);
event_add(ev, NULL);
}
}
int main()
{
int ret;
event_base *base = event_base_new();
//除了event_new, libevent也做了一系列的宏隐藏了一些接口,分别用于定时器,信号等,比如evsignal_new
event *ev = evsignal_new(base, SIGINT, signal_cb, event_self_cbarg());
//evsignal_new隐藏的状态:EV_SIGNAL|EV_PERSIST
//event_self_cbarg()传递当前的event *ev给回调函数
//event *ev = event_new(base, SIGINT, EV_SIGNAL|EV_PERSIST, signal_cb, event_self_cbarg());
if(!ev)
{
cerr << "evsignal_new error" << endl;
return -1;
}
ret = event_add(ev, 0)
if(0 != ret)
{
cerr << "event_add error" << endl;
return -1;
}
event_base_dispatch(base);
event_free(ev);
event_base_free(base);
return 0;
}
定时器
//优点:如果设置了5秒定时,处理操作耗费了2秒,下一次经过3秒就会再次进入接口,但若是处理操作耗费了6秒,仍旧会在6秒后才进入
event_new(base, -1, EV_PERSIST, callback_fun, 0);
evtimer_new(base, callback_fun, event_self_cbarg());//非持久,内部其实还是调用了event_new
//base上下文,callback_fun回调函数,event_self_cbarg()传递给回调函数的参数
evtimer_del(ev);//宏替换为event_del
evtimer_add(ev, &tv);//tv超时时间,宏替换为event_add
//evtimer_pending(ev, &tv) //判断是否为待决事件
struct timeval
{
long tv_sec;//秒
long tv_usec;//微妙
};
//微秒的精度不一定能达到
//跟踪未决事件的超时值,默认的存储事件的方法:binary heap二叉堆,添加删除( O(logN) )
//优化方法:event_base_init_common_timeout(),在大量的且具有相同超时值时,该接口可将存储方法变为双链队列( O(1) )
static timeval tv = {3,0};
const timeval *t;
t = event_base_init_common_timeout(base, &tv);
//event_add时使用t即可
服务器交互示例
//正常的断开连接也会进入,超时会进入
void read_cb(evutil_socket_t sock,
short what,
void *arg)
{
event *ev = (event *)arg;
if(what & EV_TIMEOUT)
{
cout << "recv timeout" << endl;
event_free(ev);
evutil_closesocket(sock);
return;
}
char buf[1024] = {0};
int len = recv(sock, buf, sizeof(buf)-1,0);
if(len > 0)
{
cout << buf << endl;
send(sock, "ok", 2, 0);
}
else
{
event_free(ev);
evutil_closesocket(sock);
}
}
void connect_cb(evutil_socket_t sock,
short what,
void *arg)
{
sockaddr_in cliaddr;
socklen_t size = sizeof(cliaddr);
evutil_socket_t c = accept(sock, (sockaddr *)&cliaddr, &size);
char ip[16] = {0};
//libevent提供的转换网络传输ip地址为字符串的接口
evutil_inet_ntop(AF_INET, &cliaddr.sin_addr, ip, sizeof(ip)-1);
cout << ip << endl;
event_base *base = (event_base *)arg;
event *ev = event_new(base, c, EV_READ|EV_PERSIST, read_cb, event_self_cbarg());
//event *ev = event_new(base, c, EV_READ|EV_PERSIST|EV_ET, read_cb, event_self_cbarg());边缘触发
timeval tv = {5,0};
event_add(ev, &tv);
}
int main()
{
int ret;
event_base *base = event_base_new();
evutil_socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock <= 0)
{
cerr << "socket error: " << strerror(errno) << endl;
}
//设置地址复用及非阻塞模式
evutil_make_listen_socket_reuseable(sock);//复用地址,端口号被占用就无法复用
evutil_make_socket_nonblocking(sock);
sockaddr_in seraddr;
memset(&seraddr, 0, sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(6000);
//std中也有bind,避免冲突加::表示全局的bind
ret = ::bind(sock, (sockaddr *)&seraddr, sizeof(seraddr));
if(ret)
{
cerr << "bind error: " << strerror(errno) << endl;
return -1;
}
listen(sock, 5);
//接收连接事件,默认LT水平触发
event *ev = event_new(base, sock, EV_READ|EV_PERSIST, connect_cb, base);
event_add(ev, NULL);
event_base_dispatch(base);
evutil_closesocket(sock);
event_base_free(base);
return 0;
}
缓冲io处理
bufferevent
写入和读取都是在缓冲中进行,内部的网络接口已经做好
缓冲水位
缓冲区:输入缓冲区、输出缓冲区、evbuffer(结构体,存储缓冲区的数据)
回调:读取回调、写入回调(水位能控制回调什么时候调用)
读取低水位:低于该水位缓冲区不会去调用回调,读取操作使得输入缓冲区的数据量在此级别或更高时,读取回调才会被调用
读取高水位:当输入缓冲区数据量达到该级别时,bufferevent将停止读取,直到输入缓冲区中足够量的数据被抽取,使得数据低于此级别
默认都是0
写入低水位:写入操作使得输出缓冲区的数据量在此级别或更高时,写入回调才会被调用(比如多线程每次写入很小很少量数据到磁盘,性能会降低,因为磁盘的反复调度)
写入高水位:并没有可以设定,没什么意义
事件回调:
BEV_EVENT_READING 0x01 读
BEV_EVENT_WRITING 0x02 写
BEV_EVENT_EOF 0x10 读到文件结尾
BEV_EVENT_ERROR 0x20 发生错误
BEV_EVENT_TIMEOUT 0x40 超时
BEV_EVENT_CONNECTED 0x80 连接
bufferevent还支持客户端,BEV_EVENT_CONNECTED,客户端与服务端建立连接后会有一个连接事件
接口说明
//创建bufferevent上下文
struct bufferevent *bufferevent_socket_new(struct event_base *base,
evutil_socket_t fd,
enum bufferevent_options options);
/*
base:上下文
fd:套接字
options:参数设置
BEV_OPT_CLOSE_ON_FREE:释放时关闭socket
BEV_OPT_THREADSAFE:线程安全,自动分配锁,bufferevent接口都会加锁,即单个函数多线程调用是安全的,多个接口组合调用需要注意处理
BEV_OPT_DEFER_CALLBACKS:延迟回调,防有栈溢出,延迟回调不会立即调用,而是在event_loop()调用中被排队,将bufferevent事件放在最后处理,在通常的事件回调之后执行
BEV_OPT_UNLOCK_CALLBACKS:开启线程安全后,默认调用传递回调函数也会加锁,设置该参数就不锁(不设置时,若是开启了线程安全且在回调函数中又调用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_READ, EV_WRITE, EV_READ|EV_WRITE
*/
void bufferevent_setcb(struct bufferevent *bufev,
bufferevent_data_cb readcb,
bufferevent_data_cb writecb,
bufferevent_event_cb eventcb,
void *cbarg);
//设置回调函数,会传3种回调,不传不会调用,cbarg传递的参数,一样
size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);
int bufferevent_read_buffer(struct bufferevent *bufev, struct evbuffer *buf);
int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);
//设置超时时间
void bufferevent_set_timeouts(struct bufferevent *bufev, const struct timeval *timeout_read, const struct timeval *timeout_write);
//设置水位
void bufferevent_setwatermark(struct bufferevent *bufev, short events, size_t lowmark, size_t heigmark);
//释放一个缓冲事件,内部是引用计数的,不会立刻释放,会尝试尽快释放,过早的关闭可能会造成数据发送的不完整,缓冲区中尚未发送的数据不会继续发送
void bufferevent_free(struct bufferevent *bufev);
服务端示例
#include <bufferevent.h>
void read_cb(bufferevent *bev, void *arg)
{
char buf[1024] = {0};
//读取输入缓冲数据
int len = bufferevent_read(bev, buf, sizeof(buf)-1);
cout << buf << endl;
if(len<=0)
{
return;
}
if(strstr(buf, "quit") != NULL)
{
bufferevent_free(bev);
return;
}
//发送数据,写入到输出缓冲
bufferevent_write(bev, "ok", 3);
}
//连接后先是触发一次写事件
void write_cb(bufferevent *bev, void *arg);
//错误,超时,连接断开会进入,events:事件的类型
void event_cb(bufferevent *bev, short events, void *arg)
{
//读取超时事件发生后,数据读取停止
if(events & BEV_EVENT_TIMEOUT && events & BEV_EVENT_READING)
{
/*
读取缓冲数据,当设置读取水位后,缓冲中可能还存有低于低水位的字节数据
char buf[1024] = {0};
int len = bufferevent_read(bev, buf, sizeof(buf)-1);
*/
cout << "read timeout" << endl;
//使得数据可以重新读取:bufferevent_enable(bev, EV_READ);
bufferevent_free(bev);
}
else if(events & BEV_EVENT_ERROR)
{
bufferevent_free(bev);
}
}
void evconnlistener_cb(struct evconnlistener *ev,
evutil_socket_t sock,
struct sockaddr *cliaddr,
int cliaddrlen,
void *arg)
{
event_base *base = (event_base *)arg;
//创建bufferevent上下文
bufferevent *bev = bufferevent_socket_new(base, sock, BEV_OPT_CLOSE_ON_FREE);
//添加监控事件,设置读和写的权限
bufferevent_enable(bev, EV_READ|EV_WRITE);
//设置读取水位,10:低水位,20:高水位,(0无限制)(此时若发送15字节数据,会分两次调用可读,一次10字节数据,另一次5字节数据)
bufferevent_setwatermark(bev, EV_READ, 10, 20);
//设置写入水位
bufferevent_setwatermark(bev, EV_WRITE, 5, 0);
//设置超时时间
timeval tv = {5,0};
bufferevent_set_timeouts(bev, &tv, 0);
//设置回调函数
bufferevent_setcb(bev, read_cb, write_cb, event_cb, base);
}
int main()
{
event_base *base = event_base_new();
sockaddr_in seraddr;
memset(&seraddr, 0, sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(6000);
evconnlistener *ev = evconnlistener_new_bind(base,
evconnlistener_cb,
base,
LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE,
5,
(sockaddr *)&seraddr
sizeof(seraddr)
);
event_base_dispatch(base);
evconnlistener_free(base);
event_base_free(base);
return 0;
}
客户端示例
// int bufferevent_socket_connect(struct bufferevent *bufev, struct sockaddr *address, int addrlen);
// 建立连接
void read_cb(bufferevent *bev, void *arg);
void write_cb(bufferevent *bev, void *arg)
{
FILE *p = (FILE *)arg;
char data[1024] = {0};
int len = fread(data, 1, sizeof(data)-1, p);
if(len <= 0)
{
//存在问题:还在read时,服务端关闭了,没有释放
//解决:及时的监控,统一释放,设为全局(释放后置空)或某个类成员
cout << "file end or error" << endl;
fclose(p);
//bufferevent_free();立刻清理可能会造成缓冲数据没有发送结束
//禁掉写事件,当缓冲区发送完可写入时,会再进入该回调一次,需要再次写入时再打开
bufferevent_disable(bev, EV_WRITE);
return;
}
bufferevent_write(bev, data, len);
}
void event_cb(bufferevent *bev, short events, void *arg)
{
if(events & BEV_EVENT_TIMEOUT && events & BEV_EVENT_READING)
{
cout << "timeout" << endl;
bufferevent_free(bev);
return;
}
else if(events & BEV_EVENT_ERROR)
{
bufferevent_free(bev);
return;
}
//服务端的正常关闭事件,异常关闭(断电,网卡被拔等)通过超时判断
if(events & BEV_EVENT_EOF)
{
cout << "ser close" << endl;
bufferevent_free(bev);
return;
}
if(events & EV_EVENT_CONNECTED)
{
cout << "connect success" << endl;
//主动触发write事件
bufferevent_trigger(bev, EV_WRITE, 0);//0:选择项,不传
}
}
int main()
{
event_base *base = event_base_new();
//-1:内部创建socket
bufferevent *bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
sockaddr_in seraddr;
memset(&seraddr, 0, sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(6000);
evutil_inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr);
FILE *p = fopen("test.txt", "rb");
timeval tv = {5,0};
bufferevent_set_timeouts(bev, &tv, 0);
bufferevent_setcb(bev, read_cb, write_cb, event_cb, p);
bufferevent_enable(bev, EV_READ|EV_WRITE);
int ret = bufferevent_socket_connect(bev, (sockaddr *)&seraddr, sizeof(seraddr));
if(ret == 0) //未必是连接成功,可能是接口调用成功
{
cout << "connect " << endl;
}
event_base_dispatch(base);
event_base_free(base);
return 0;
}
过滤器
发送接收数据时先经过一次过滤,可以让数据先经过一层处理
//使用过滤器时会对具体的evbuffer操作
//将缓冲的数据移除掉,但会返回一份数据的拷贝
int evbuffer_remove(struct evbuffer *buf, void *data, size_t datalen);
//将数据加回缓冲区
int evbuffer_add(struct evbuffer *buf, const void *data, size_t datalen);
//创建一个过滤器
struct bufferevent *bufferevent_filter_new(
struct bufferevent *underlying, //针对该bufferevent做过滤器
bufferevent_filter_cb input_filter,//输入回调函数
bufferevent_filter_cb output_filter,//输出回调函数
int options,//选项,BEV_OPT_CLOSE_ON_FREE(关闭该过滤器时会同时关闭bufferevent), 与bufferevent_socket_new的选项一样
void (*free_context)(void *),//清理时的回调,用于清理内部资源
void *ctx);//传递给回调的参数
//输入输出回调函数
typedef enum bufferevent_filter_result(*bufferevent_filter_cb)(
struct evbuffer *source,
struct evbuffer *destination,
ev_ssize_t dst_limit,
enum bufferevent_flush_mode mode,
void *ctx);
/*
bufferevent_filter_result:返回值,枚举,BEV_OK = 0写入了任何数据, BEV_NEED_MODE = 1,没有写入(数据是要达到一定层次才会处理),BEV_ERROR = 2发生错误
source:处理前的数据的存放位置
destination:处理后的数据的存放位置,传递给读写事件的回调
dst_limit:高水位或速率限制,可以不处理,-1没有限制
mode:模式,传输的设定
BEV_NORMAL:在方便转换的基础上写入尽可能多的数据
BEV_FLUSH:表示写入尽可能多的数据
BEV_FINISHED:在流的末尾执行额外的清理操作
ctx:外部传递的参数
*/
过滤器代码示例
#include <event2/buffer.h>
bufferevent_filter_result filter_in(
struct evbuffer *source,
struct evbuffer *destination,
ev_ssize_t dst_limit,
enum bufferevent_flush_mode mode,
void *ctx)
{
//读取并清理源数据
char buf[1024] = {0};
int len = evbuffer_remove(source, buf, sizeof(buf)-1);
//处理数据
for(int i = 0; i < len; i++)
{
buf[i] = toupper(buf[i]);
}
evbuffer_add(destination, data, len);
return BEV_OK;
}
bufferevent_filter_result filter_out(
struct evbuffer *source,
struct evbuffer *destination,
ev_ssize_t dst_limit,
enum bufferevent_flush_mode mode,
void *ctx)
{
char buf[1024] = {0};
int len = evbuffer_remove(source, buf, sizeof(buf)-1);
//处理数据
for(int i = 0; i < len; i++)
{
buf[i] = toupper(buf[i]);
}
evbuffer_add(destination, data, len);
return BEV_OK;
}
//只要缓冲区中有内容未处理,就会一直调用过滤器回调函数
void read_cb(bufferevent *bev, void *arg)
{
char buf[1024] = {0};
int len = bufferevent_read(bev, buf, sizeof(buf)-1);
cout << buf << endl;
bufferevent_write(bev, "ok", 3);
}
void write_cb(bufferevent *bev, void *arg);
void event_cb(bufferevent *bev, short events, void *arg);
void evconnlistener_cb(struct evconnlistener *ev,
evutil_socket_t socket,
struct sockaddr *addr,
int socklen,
void *arg)
{
event_base *base = (event_base *)arg;
bufferevent *bev = bufferevent_socket_new(base, sock, BEV_OPT_CLOSE_ON_FREE);
//绑定bufferevent filter
bufferevent *bev_filter = bufferevent_filter_new(bev,
filter_in,//输入过滤函数
filter_out,//输出过滤函数
BEV_OPT_CLOSE_ON_FREE,//关闭filter同时关闭bev
0,//清理的回调函数
0,//传递给回调的参数
);
//设置bufferevent回调函数(直接设置bev_filter就可以),针对于具体的读取写入事件的回调
bufferevent_setcb(bev_filter, read_cb, write_cb, event_cb, NULL);
bufferevent_enable(bev_filter, EV_READ|EV_WRITE);
}
filter zlib压缩通信
接口说明:
//会把内部空间的引用直接取出,直接读取缓冲数据不复制,不将其缓冲区清除,使用evbuffer_remove会有效率问题(比如获取一部分数据还不足以解压,等待再解压,但缓冲数据已经删除)
int evbuffer_peek(struct evbuffer *buffer, ev_ssize_t len, struct evbuffer_ptr *start_at, struct evbuffer_iovec *vec_out, int n_vec);
struct evbuffer_iovec{
void *iov_base;
size_t iov_len;
};
/*
len:取出的数据大小,-1会自动内部识别
start_at:开始位置,不确定传NULL即可
*/
//数据取出是放在新的缓冲空间,evbuffer_add将该空间再添加进缓冲区,实际是做了一个复制的过程,增大了开销,跟内存的读取速度有关(比如处理音视频时一秒几十M一两百M,该开销相对而言还是很大的),此时对于处理高帧率数据可能会丢帧
//申请新的空间,先申请出来,向其中写数据
int evbuffer_reserve_space(struct evbuffer *buf, ev_ssize_t size, struct evbuffer_iovec *vec, int n_vecs);
/*
size:拓展缓冲区以至少提供size字节的空间
n_vecs,写入几个vec, 至少是1,2个的效率更高
*/
//空间提交,直接提交空间引用,减少了一次复制, 要与evbuffer_reserve_space对应一致
evbuffer_commit_space(buf, evbuffer_iovec *vec, 1);
//删除缓冲区中指定长度的数据
int evbuffer_drain(struct evbuffer *buf, size_t len);
zlib库接口:
//压缩
deflateInit(z_stream *, int level);
/*压缩初始化
level:级别
Z_DEFAULT_COMPRESSION 默认压缩
Z_BEST_COMPRESSION 最好的压缩方式(速度慢些)
Z_BEST_SPEED 最快速度(压缩率低些)
return Z_OK
*/
int deflate(z_stream *strm, int flush);
//压缩,flush:刷新方式,Z_SYVC_FLUSH,所有挂起的输出都是刷新到输出缓冲区,并且输出在字节边界上对齐
//解压缩
int inflate(z_stream *strm, int flush);
//flush:Z_SYVC_FLUSH,将输出尽可能的输出到输出缓冲区
int inflateInit(z_stream *strm);
/*
z_stream 压缩解压缩的上下文,与zlib库交互的结构体
成员:
uInt avail_in输入空间大小,处理后就是剩余未处理的大小
Bytef *next_in输入空间地址
uInt avail_out输出空间大小,处理后就是剩余输出空间大小
Bytef *next_out输出空间地址
*/
部分代码演示:
//server
struct Status
{
bool start = false;
FILE *p = NULL;
z_stream *z_output = NULL;
~Status()
{
if(fp)
fcose(p);
if(z_output)
inflateEnd(z_output);//清理动态分配的空间
z_output = 0;
delete z_output;
}
}
void read_cb(bufferevent *bev, void *arg)
{
Status *ps = (Status *)arg;
if(!ps->start)
{
char buf[1024] = {0};
bufferevent_read(bev, buf, sizeof(buf)-1);
string out = "out/";
out+=buf;
ps->p = fopen(out.c_str(), "wb");
bufferevent_write(bev, "ok", 2);
ps->start = True;
return;
}
do{
char data[1024] = {0};
int len = bufferevent_read(bev, data, sizeof(data));
if(len >= 0)
{
fwrite(data, 1, len, ps->p);
fflush(ps->p);
}
}while(evbuffer_get_length( evbuffer_get_input(bev) ) > 0);
//获取缓冲大小、获取输入缓冲
}
void write_cb(bufferevent *bev, void *arg);
void event_cb(bufferevent *bev, short events, void *arg)
{
Status *ps = (Status *)arg;
if(events & BEV_EVENT_EOF)
{
delete ps;
bufferevent_free(bev);
}
}
bufferevent_filter_result filter_in(
struct evbuffer *source,
struct evbuffer *destination,
ev_ssize_t dst_limit,
enum bufferevent_flush_mode mode,
void *ctx)
{
Status *s = (Status *)arg;
if(!s->start)
{
char buf[1024] = {0};
int len = evbuffer_remove(source, buf, sizeof(buf)-1);
evbuffer_add(destination, data, len);
return BEV_OK;
}
evbuffer_iovec v_in[1];
int n = evbuffer_peek(source, -1, NULL, v_in, 1);
if(n<=0)
{
return BEV_NEED_MORE;
}
z_stream *pz = s->z_output;
if(!pz)
return BEV_ERROR;
//zlib输入空间大小及地址
pz->avail_in = v_in[0].iov_len;
pz->next_in = (Byte *)v_in[0].iov_base;
evbuffer_iovec v_out[1];
evbuffer_reserve_space(destination, 4096, v_out, 1);
//zlib输出空间大小及地址
pz->avail_out = v_out[0].iov_len;
pz->next_out = (Byte *)v_out[0].iov_base;
int re = inflate(pz, Z_SYVC_FLUSH);
if(re != Z_OK)
{
cerr << "inflate error" << endl;
}
//解压前的数据大小
int nread = v_in[0].iov_len - pz->avail_in;
//解压后的数据大小
int nwrite = v_out[0].iov_len - pz->avail_out;
evbuffer_drain(source, nread);
v_out[0].iov_len = nwrite;
evbuffer_commit_space(destination, v_out, 1);
}
void evconnlistener_cb(struct evconnlistener *ev,
evutil_socket_t socket,
struct sockaddr *addr,
int socklen,
void *arg)
{
event_base *base = (event_base *)arg;
bufferevent *bev = bufferevent_socket_new(base, sock, BEV_OPT_CLOSE_ON_FREE);
Status *ps = new Status();
ps->pz = new z_stream();
inflateInit(ps->pz);
bufferevent *bev_filter = bufferevent_filter_new(bev,
filter_in,
0,
BEV_OPT_CLOSE_ON_FREE,
0,
ps
);
bufferevent_setcb(bev_filter, read_cb, 0, event_cb, ps);
bufferevent_enable(bev_filter, EV_READ|EV_WRITE);
}
//client
#define FILEPATH "text.txt"
struct clientStatus
{
bool fileend = false;
FILE *p = NULL;
z_stream *z_output;//维系压缩流程的上下文
bool start_send = false;
~clientStatus()
{
if(z_output)
deflateEnd(z_output);
z_output = 0;
delete z_output;
fclose(p);
p = NULL;
}
}
bufferevent_filter_result filter_out(
struct evbuffer *source,
struct evbuffer *destination,
ev_ssize_t dst_limit,
enum bufferevent_flush_mode mode,
void *ctx)
{
clientStatus *s = (clientStatus *)arg;
if(s->start_send)
{
char buf[1024] = {0};
int len = evbuffer_remove(source, buf, sizeof(buf));
evbuffer_add(destination, data, len);
return BEV_OK;
}
else
{
evbuffer_iovec v_in[1];//输出空间
int n = evbuffer_peek(source, -1, 0, v_in, 1);
if(n<=0)
{
//调用write回调,清理空间(不合理)
if(s->end)
return BEV_OK;
return BEV_NEED_MORE;//BEV_NEED_MORE不会进入写入回调
}
z_stream *pz = s->z_output;
if(!pz)
return BEV_ERROR;
//zlib输入空间大小及地址
pz->avail_in = v_in[0].iov_len;
pz->next_in = (Byte *)v_in[0].iov_base;
evbuffer_iovec v_out[1];
evbuffer_reserve_space(destination, 4096, v_out, 1);
//zlib输出空间大小及地址
pz->avail_out = v_out[0].iov_len;
pz->next_out = (Byte *)v_out[0].iov_base;
int re = deflate(pz, Z_SYVC_FLUSH);
if(re != Z_OK)
{
cerr << "deflate error" << endl;
}
//压缩了的数据大小
int nread = v_in[0].iov_len - pz->avail_in;
//压缩后的数据大小
int nwrite = v_out[0].iov_len - pz->avail_out;
evbuffer_drain(source, nread);
v_out[0].iov_len = nwrite;
evbuffer_commit_space(destination, v_out, 1);
}
}
void client_read_cb(bufferevent *bev, void *arg)
{
clientStatus *s = (clientStatus *)arg;
char buf[1024] = {0};
int len = bufferevent_read(bev, buf, sizeof(buf)-1);
if(strcmp(buf, "ok") == 0)
{
cout << buf << endl;
s->start_send = true;
bufferevent_trigger(bev, BEV_WRITE, 0);
}
else
{
bufferevent_free(bev);
}
}
void client_write_cb(bufferevent *bev, void *arg)
{
clientStatus *s = (clientStatus *)arg;
FILE *p = s->p;
if(s->fileend)
{
//获取过滤器绑定的buffer
bufferevent *b = bufferevent_get_underlying(bev);
//获取输出缓冲
evbuffer *evb = bufferevent_get_output(b);
//获取输出缓冲大小
int len = evbuffer_get_length(evb);
if(len <= 0)
{
//立刻清理,缓冲区如果有数据也会清理,不会发送,可能有问题,要做判断(如上)
bufferevent_free(bev);
delete s;
return;
}
else
{
//有数据, 刷新缓冲,不刷新缓冲的话,client_write_cb不会再进来
bufferevent_flush(bev, BEV_WRITE, BEV_FINISHED);
return;
}
}
char buf[1024] = {0};
int len = fread(data, 1, sizeof(data), p);
if(len <=0)
{
s->fileend = True;
bufferevent_flush(bev, BEV_WRITE, BEV_FINISHED);
return;
}
bufferent_write(bev, data, len);
}
void client_event_cb(bufferevent *bev, short events, void *arg)
{
if(events & BEV_EVENT_CONNECTED)
{
bufferevent_write(bev, FILEPATH, strlen(FILEPATH));
FILE *p = fopen(FILEPATH, "rb");
clientStatus *s = new clientStatus();
s->p = p;
s->z_output = new z_stream();
//初始化上下文,默认压缩
deflateInit(s->z_output, Z_DEFAULT_COMPRESSION);
bufferevent *bev_filter = bufferevent_filter_new(bev, 0, filter_out, BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS, 0, s);
bufferevent_setcb(bev_filter, client_read_cb, client_write_cb, client_event_cb, s);
bufferevent_enable(bev_filter, EV_READ|EV_WRITE);
}
}