libevent事件循环库

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);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值