libevent & bufferevent
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了libevent & bufferevent相关的知识,希望对你有一定的参考价值。
参考技术A 对于一个网络模块来说,一个缓冲模块是必不可少的,缓冲模块主要用于缓冲网络接收的模块,和用户发送的模块。libevent在常规事件回调的基础上提供了一个缓冲的IO抽象概念,这个抽象概念被称为bufferevent、bufferevent提供了自动填充和释放的输入输出缓冲区,缓冲事件的用户不再直接处理IO,而是从输入缓冲区读取数据,写入到输出缓冲区。
libevent使用evbuffer作为网络缓冲模块,缓冲区由 evbuffer 和 evbuffer_chain 组成。evbuffer_chain是存储数据的一块内存,通过指针连接在一起,组成内存池,而evbuffer则是管理这个内存池的链表。
evbuffer_chain:属于evbuffer中单个项
evbuffer:
二者的关系
服务器用到libevent中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);
申请一个evconnlistener 对象,在给定的ip地址端口上监听TCP连接。新的连接到来时会触发回调函数 cb;
内部实现是 完成socket(),bind,listen()这些函数之后,将此fd通过event_assign函数添加到event_base中,event_assign函数前篇有所介绍,这里不再详述。
之前说event与event_base进行关联使用的是event_add函数,这个evconnlistener_enable就是做这个这个事情,
libevent中实现了大量的函数指针,初始化时将evconnlistener_event_ops 地址绑定到ops,最后调用evconnlistener_enable 函数里面操作lev->ops->enable(lev);,就执行到了event_listener_enable函数中,次函数中再进行event_add操作。
可以理解为 evconnlistener_new_bind 函数封装了socket的API操作,然后对event与event_base进行初始化关联操作,用户直接直接在回调中等待新的连接即可。
一个bufferevent包含了一个底层传输的fd,一个输入buffer一个输出buffer,并且bufferevent帮我们完成了从socket 上接收数据写入输入buffer,同时从输出buffer中取出数据通过sicket发送,当输入输出缓冲中的数据达到一定量的时候调用我们设置的回调函数。
bufferevent结构体中主要有
1 两个事件(读,写):基本的event,等待被触发后加入到base的活动队列,然后调用相应的回调函数,
2.两个缓冲区(input output):存储读取和待发送的数据
3三个回调函数(read write error):用户自定义
4两个超时时间:(读、写超时)
一: struct bufferevent * bufferevent_socket_new (struct event_base *base, evutil_socket_t fd, int options);
在给定的socket 上创建一个新的 socket bufferevent。
对bufferevent 结构体进行初始化操作,包含以下几项:
1 读写缓冲区(input,output)的初始化
2.读写事件初始化操作
二 :void bufferevent_setcb (struct bufferevent *bufev, bufferevent_data_cb readcb, bufferevent_data_cb writecb, bufferevent_event_cb eventcb, void *cbarg);
读写以及错误的回调函数的设置
三 :int bufferevent_enable (struct bufferevent *bufev, short event)
将事件添加到event_base上,
在 bufferevent_socket_new 中 对be_ops 进行了赋值操作 ,& bufferevent_ops_socket,
bufferevent_init_common中 bufev->be_ops = ops; 即 bufev->be_ops =& bufferevent_ops_socket;
bufferevent_enable 中调用了be_ops
enable函数就是指be_socket_enable函数,
函数调用层层下来之后还是event_add函数,将event添加到event_base上,底层epoll的epoll_ctl操作,添加节点到红黑树上。
以上就是使用bufferevent对 socket 读写监听的流程。
使用 libev 的套接字
【中文标题】使用 libev 的套接字【英文标题】:sockets using libev 【发布时间】:2013-02-25 00:56:55 【问题描述】:我正在寻找基于 libev 编写一个套接字程序。我注意到https://github.com/coolaj86/libev-examples/blob/master/src/unix-echo-server.c 中所述的几个示例使用基于 init 的回调。例如,
main()
......
ev_io_init(&client.io, client_cb, client.fd, EV_READ|EV_WRITE);
ev_io_start(EV_A_ &server.io);
static void client_cb (EV_P_ ev_io *w, int revents)
if (revents & EV_READ)
....
else if (revents & EV_WRITE)
......
我的问题来自预期的行为,例如,我在 EV_READ 中读取的所有内容都存储在链表中。假设我不断获得免费的数据包流来读取,我有机会进入 EV_WRITE 吗?我必须将我通过读取接收到的所有内容发送到另一个套接字。那么它会是一次 EV_READ 和第二次 EV_WRITE 吗?换句话说,EV_WRITE 什么时候会被解除阻塞?或者我是否需要阻止 EV_READ 才能调用 EV_WRITE。有人可以帮我理解这一点吗?
【问题讨论】:
codefundas.blogspot.com/2010/09/… 【参考方案1】:简短地回答:如果您总是先检查一种类型的事件,然后为另一种类型设置else
if
,您将面临饥饿的风险。一般来说,我会检查两者,除非指定的协议无法同时激活两者。
这是一个更不确定的答案: 您问题中的链接不包含代码结构,例如您的问题。客户端https://github.com/coolaj86/libev-examples/blob/master/src/unix-echo-client.c 确实有类似的回调。您会注意到它会在写入一次后禁用写入事件。
// once the data is sent, stop notifications that
// data can be sent until there is actually more
// data to send
ev_io_stop(EV_A_ &send_w);
ev_io_set(&send_w, remote_fd, EV_READ);
ev_io_start(EV_A_ &send_w);
这看起来像是试图避免管道 READ 事件分支的饥饿。即使我对 libev 不是很熟悉,您链接到的 github 示例似乎也不是很健壮。例如static void stdin_cb (EV_P_ ev_io *w, int revents)
不使用getline()
的返回值来检测EOF。此外,send()
和 recv()
套接字操作返回值不会检查读取或写入的数量(尽管在本地命名管道流上,数量很可能与请求的数量匹配)。如果稍后将其更改为基于 TCP 的连接,则检查数量至关重要。
【讨论】:
是的,我知道我用来理解的代码不是很好,但是如果您能指出我的任何其他代码,我将非常高兴。因此,从您所说的来看,如果我同时检查两者,我应该可以避免饥饿。谢谢。 是的,两者都检查。使用if()
语句而不是if-else
。【参考方案2】:
我认为您应该将写回调与读回调分开:
main()
ev_io_init(&read.io, read_cb, client.fd, EV_READ);
ev_io_init(&write.io, writead_cb, client.fd, EV_WRITE);
ev_io_start(EV_A_ &read.io);
ev_io_start(EV_A_ &write.io);
这是我的解决方案。
【讨论】:
以上是关于libevent & bufferevent的主要内容,如果未能解决你的问题,请参考以下文章