(4)libevent 数据缓冲Bufferevent

libevent 为此提供了一种通用机制,即bufferevent

bufferevent 由一个底层的传输端口(如套接字 ),一个读取缓冲区和一个写入缓冲区组成。与通常的事件在底层传输端口已经就绪,可以读取或者写入的时候执行回调不同的是,bufferevent 在读取或者写入了足够量的数据之后调用用户提供的回调。

回调和水位

bufferevent中提供了对读写回调的触发条件及最大缓存长度的设置,即低高水位:

  • 读低水位:就是当可读的数据量到达这个低水位后,才会调用用户设置的回调函数。比如用户想每次读取100字节,那么就可以把低水位设置为100。当可读数据的字节数小于100时,即不会调用用户设置的回调函数。可读数据大于等于100字节后,才会调用用户的回调函数。默认值为0,所以每个读取操作都会导致读取回调被调用。

  • 读高水位:把读事件的evbuffer的数据量限制在高水位之下。比如读缓冲区不能太大(太大的话,链表会很长)。那么用户就会设置读事件的高水位。当读缓冲区的数据量达到这个高水位后,即使socket fd还有数据没有读,也不会读进这个读缓冲区里面。一句话说,就是控制evbuffer的大小。

  • 写低水位:写入操作使得输出缓冲区的数据量达到或者低于此级别时,写入回调函数将被调用。默认值是0,所以只有输出缓冲区空的时候才会调用此回调。

  • 写高水位:bufferevent没有直接使用这个水位。它在bufferevent用作另外一个bufferevent的底层传输端口时有特殊意义。

1.设置读写水位

/*设置水位*/
void bufferevent_setwatermark (struct bufferevent *bufev, short events, size_t lowmark, size_t highmark)

/*设置读写超时*/
int bufferevent_set_timeouts(struct bufferevent *bufev, const struct timeval *timeout_read, const struct timeval *timeout_write);

2.启用/禁用事件

void bufferevent_enable(struct bufferevent *bufev, short events);
void bufferevent_disable(struct bufferevent *bufev, short events);
short bufferevent_get_enabled(struct bufferevent *bufev);

3.获取bufferevent中的数据

bufferevent 提供了下列函数用于观察要写入或者读取的数据。

struct evbuffer *bufferevent_get_input(struct bufferevent *bufev);
struct evbuffer *bufferevent_get_output(struct bufferevent *bufev);

4.向bufferevent的输出缓冲区添加数据

/* 将内存中从 data 处开 始的 size 字节数据添加到输出缓冲区的末尾 */
int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);
/* 移除 buf 的所有内 容,将其放置到输出缓冲区的末尾。 */
int bufferevent_write_buffer(struct bufferevent *bufev, struct evbuffer *buf);

5.从bufferevent的输入缓冲区移除数据

/* 从输入缓冲区移除 size 字节的数据,将其存储到内存中 data 处, 函数返回实际移除的字节数 */
size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);

/* 抽空输入缓冲区的所有内容,将其放置到 buf 中 */
int bufferevent_read_buffer(struct bufferevent *bufev, struct evbuffer *buf);

6.bufferevent的清空操作

bufferevent 强制从底层传输端口读取或者写入尽可能多的数据 ,而忽略其他可能保持数据不被写入的限制条件 。函数的细节功能依赖于 bufferevent 的具体类型。

  • otype 参数应该是 EV_READ、EV_WRITE 或者 EV_READ | EV_WRITE,
  • state 参数可以是 BEV_NORMAL、BEV_FLUSH 或者 BEV_FINISHED
/* 失败时 bufferevent_flush()返回-1,如果没有数据被清空则返回 0,有数据被清空则返回 1 */
int bufferevent_flush(struct bufferevent *bufev, short iotype, enum bufferevent_flush_mode state);

7.释放bufferevent操作

bufferevent 内部具有引用计数,所以,如果释放 时还有未决的延迟回调,则在回调完成之前 bufferevent 不会被删除。

void bufferevent_free(struct bufferevent *bev);
/* 创建基于套接字的bufferevent */

struct bufferevent *bufferevent_socket_new(
    struct event_base *base,
    evutil_socket_t fd,
    enum bufferevent_options options);
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <sys/socket.h>
#include <string.h>

void eventcb(struct bufferevent *bev, short events, void *ptr)
{
    if (events & BEV_EVENT_CONNECTED) {
         /* like start reading or writing */
    } else if (events & BEV_EVENT_ERROR) {
         /* An error occured while connecting. */
    }
}

int main_loop(void)
{
    struct event_base *base;
    struct bufferevent *bev;
    struct sockaddr_in sin;

    base = event_base_new();

    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */
    sin.sin_port = htons(8080); /* Port 8080 */

    bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);

    bufferevent_setcb(bev, NULL, NULL, eventcb, NULL);

    if (bufferevent_socket_connect(bev,
        (struct sockaddr *)&sin, sizeof(sin)) < 0) {
        /* Error starting connection */
        bufferevent_free(bev);
        return -1;
    }

    event_base_dispatch(base);
    return 0;
}

一、水平触发和边缘触发

#include <iostream>
#include <event2/event.h>
#ifndef _WIN32
#include <signal.h>
#else
#endif
#include <thread>
#include <errno.h>
#include <string.h>
using namespace std;
#define SPORT 5001 

//正常断开连接也会进入,超时会进入
void client_cb(evutil_socket_t s, short w, void *arg)
{
	//水平触发LT 只有有数据没有处理,会一直进入
	//边缘触发ET 有数据时只进入一次, 发送长度大于接收长度,只进入一次,会读取不完整。

	event *ev = (event *)arg;
	//判断超时
	if (w&EV_TIMEOUT)
	{
		cout << "timeout" << endl;
		event_free(ev);
		evutil_closesocket(s);
		return;
	}

	char buf[1024] = { 0 };
	int len = recv(s, buf, sizeof(buf) - 1, 0);
	if (len > 0)
	{
		cout << buf << endl;
		send(s, "ok", 2, 0);
	}
	else
	{
		//需要清理event
		cout << "event_free" << flush;
		event_free(ev);
		evutil_closesocket(s);
	}
}

void listen_cb(evutil_socket_t s, short w, void *arg)
{
	cout << "listen_cb" << endl;
	sockaddr_in sin;
	socklen_t size = sizeof(sin);
	//读取连接信息
	evutil_socket_t client = accept(s, (sockaddr*)&sin, &size);
	char ip[16] = { 0 };
	evutil_inet_ntop(AF_INET, &sin.sin_addr, ip, sizeof(ip) - 1);
	cout << "client ip is " << ip << endl;

	//客户端数据读取事件
	event_base *base = (event_base *)arg;
	
	// 边缘触发( EV_ET)
	//event *ev = event_new(base,client,EV_READ|EV_PERSIST|EV_ET,client_cb,event_self_cbarg());
	event *ev = event_new(base, client, EV_READ | EV_PERSIST, client_cb, event_self_cbarg());
	timeval t = { 10,0 };	//设置超时
	event_add(ev, &t);
}

int main(int argc, char *argv[])
{
#ifdef _WIN32 
	//初始化socket库
	WSADATA wsa;
	WSAStartup(MAKEWORD(2, 2), &wsa);
#else
	//忽略管道信号,发送数据给已关闭的socket
	if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
		return 1;
#endif

	event_base *base = event_base_new();

	//创建socket
	evutil_socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock <= 0)
	{
		cerr << "socket error:" << endl;
		return -1;
	}
	int val = 1;

	//设置地址复用和非阻塞
	evutil_make_socket_nonblocking(sock);
	evutil_make_listen_socket_reuseable(sock);
	//绑定端口和地址
	sockaddr_in sin;
	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(SPORT);
	int re = ::bind(sock, (sockaddr*)&sin, sizeof(sin));
	if (re != 0)
	{
		cerr << "bind error:" << endl;
		return -1;
	}
	//开始监听
	listen(sock, 10);

	//开始接受连接事件 默认水平触发
	event *ev = event_new(base, sock, EV_READ | EV_PERSIST, listen_cb, base);
	event_add(ev, 0);

	//进入事件主循环
	event_base_dispatch(base);
	evutil_closesocket(sock);
	event_base_free(base);

	return 0;
}
posted @ 2023-11-09 01:26  osbreak  阅读(86)  评论(0编辑  收藏  举报