libevent中提供了一个Hello-world.c 的例子,从这个例子可以学习libevent是如何使用bufferevent的。

这个例子在Sample中

这个例子之前讲解过,这次主要看下bufferevent的使用。

第一步找到main函数

main函数

int main(){
//...
listener = evconnlistener_new_bind(base, listener_cb, (void *)base,
LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE, -,
(struct sockaddr*)&sin,
sizeof(sin)); //...

    event_base_dispatch(base);

    evconnlistener_free(listener);
    event_free(signal_event);
    event_base_free(base);

    printf("done\n");
    return 0;

}

main函数中调用evconnlistener_new_bind()创建了一个evconnlistener 类型的listener,

然后拍发消息,之后释放各种资源。

第二步在evconnlistener_new_bind()中调用evconnlistener_new()完成listener属性设置。

这个函数里对evconnlistener_event中base进行回调函数的绑定和参数设置,通过event_assign将evconnlistener_event的

listener设置读事件的回调函数,并且通过evconnlistener_enable让读回调函数触发,也就是触发listener_read_cb。

这里evconnlister_enable调用的也是结构体注册的enable具体看代码吧,调用的是r = lev->ops->enable(lev);

等同于调用event_listener_enable,该函数内部完成event_add。

struct evconnlistener_event {
    struct evconnlistener base;
    struct event listener;
};

struct evconnlistener *
evconnlistener_new(struct event_base *base,
evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,
evutil_socket_t fd)
{
struct evconnlistener_event *lev;//开辟evconnlistener_event大小区域
lev = mm_calloc(, sizeof(struct evconnlistener_event));
if (!lev)
return NULL;
//lev -> base 表示 evconnlistener
//evconnlistener evconnlistener_ops 基本回调参数和回调函数结构体赋值
lev->base.ops = &evconnlistener_event_ops;
//evconnlistener_cb 设置为listener_cb
lev->base.cb = cb;
//ptr表示event_base 指针
lev->base.user_data = ptr;
lev->base.flags = flags;
lev->base.refcnt = ;// lev is evconnlistener_event
//lev->listener is event
//为lev->listener设置读回调函数和读关注事件,仅进行设置并没加入event队列
event_assign(&lev->listener, base, fd, EV_READ|EV_PERSIST,
listener_read_cb, lev);
//实际调用了event_add将事件加入event队列
evconnlistener_enable(&lev->base); return &lev->base;
}

 第三步listener_read_cb内部调用accept生成新的socket处理连接,调用listener_cb

新的socket作为参数传递给evconnlistener_event中base的回调函数listener_cb

static void
listener_read_cb(evutil_socket_t fd, short what, void *p)
{
struct evconnlistener *lev = p;
int err;
evconnlistener_cb cb;
evconnlistener_errorcb errorcb;
void *user_data;
LOCK(lev);
while () {
  //...//cb 就 是 listener_cb
cb = lev->cb;
user_data = lev->user_data;
UNLOCK(lev);
//触发了listener_cb //完成了eventbuffer注册写和事件函数
cb(lev, new_fd, (struct sockaddr*)&ss, (int)socklen,
user_data); LOCK(lev);
if (lev->refcnt == ) {
int freed = listener_decref_and_unlock(lev);
EVUTIL_ASSERT(freed);
return;
}
--lev->refcnt;
}
//...
}

第四步listener_cb 调用bufferevent_socket_new 生成bufferevent, 然后bufferevent_setcb设置读写水位触发的回调函数,

bufferevent_enable将bufferevent的写事件加入监听,即开始检测写事件。关闭读事件,并且向outbuf中写入MSG

bufferevent_socket_new内部绑定bufferevent的读写事件回调函数,读事件为bufev->ev_read,绑定了bufferevent_readcb回调函数,

写事件为bufev->ev_write,绑定了bufferevent_writecb回调函数。这两个回调函数和bufferevent的readcb和writecb是不一样的,

这两个函数在对应的读写事件激活时才触发。而readcb和writecb是基于水位线达到阈值才会触发。做好区分。bufferevent_socket_new

内部还对bufev->output添加了对调函数bufferevent_socket_outbuf_cb,bufferevent_socket_outbuf_cb内部检测是否开启写事件,

以及是否可写,如果可写,同样将写事件加入监听队列,也就是调用了event_add。bufferevent_socket_new内部解释完毕了。

bufferevent_setcb设置的是读写水位达到阈值后的回调函数,bufferevent_enable内部也是调用了event_add,将读事件加入监听队列。

bufferevent_enable内部调用bufev->be_ops->enable(bufev, impl_events),等同于be_socket_enable,另外bufferevent_write

函数内部调用evbuffer_add,evbuffer_add内部调用了evbuffer_invoke_callbacks,

就会调用绑定在output buffer上的回调函数bufferevent_socket_outbuf_cb。

static void
listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
struct sockaddr *sa, int socklen, void *user_data)
{
struct event_base *base = user_data;
struct bufferevent *bev; bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
if (!bev) {
fprintf(stderr, "Error constructing bufferevent!");
event_base_loopbreak(base);
return;
}
//设置写回调和事件回调
bufferevent_setcb(bev, NULL, conn_writecb, conn_eventcb, NULL);
bufferevent_enable(bev, EV_WRITE);
bufferevent_disable(bev, EV_READ);
//将要发送的内容写入evbuffer结构
bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
}
struct bufferevent *
bufferevent_socket_new(struct event_base *base, evutil_socket_t fd,
int options)
{
struct bufferevent_private *bufev_p;
struct bufferevent *bufev;   //...//设置bufferevent中 ev_read(event类型)回调函数
event_assign(&bufev->ev_read, bufev->ev_base, fd,
EV_READ|EV_PERSIST, bufferevent_readcb, bufev);
//设置bufferevent中 ev_write(event类型)回调函数
event_assign(&bufev->ev_write, bufev->ev_base, fd,
EV_WRITE|EV_PERSIST, bufferevent_writecb, bufev); //为bufev->output(evbuffer类型)设置回调函数,插入bufferevent->output的callback队列
//bufferevent_socket_outbuf_cb回调函数内部将ev_write事件加入事件队列
evbuffer_add_cb(bufev->output, bufferevent_socket_outbuf_cb, bufev); evbuffer_freeze(bufev->input, );
evbuffer_freeze(bufev->output, );
  //... return bufev;
}
static int
be_socket_enable(struct bufferevent *bufev, short event)
{
if (event & EV_READ) {
if (be_socket_add(&bufev->ev_read,&bufev->timeout_read) == -)
return -;
}
if (event & EV_WRITE) {
if (be_socket_add(&bufev->ev_write,&bufev->timeout_write) == -)
return -;
}
return ;
}

第五步 bufferevent的output中写入MSG, 并且之前也已经将EV_WRITE事件加入监听,所以内核检测到socket可写,

会通知bufferevent的ev_write,调用绑定在ev_write上的函数bufferevent_writecb。

这是bufferevent内部的写操作,我们可以详细看一下。之前也有讲过bufferevent会将接收到的数据放到input buffer中,

将output buffer中的数据发送。所以之前讲过的接口bufferevent_write让我们将要发送的数据放到output中,

bufferevent_read可以从input中读出

bufferevent接收到的数据。

static void
bufferevent_writecb(evutil_socket_t fd, short event, void *arg)
{
struct bufferevent *bufev = arg;
struct bufferevent_private *bufev_p =
EVUTIL_UPCAST(bufev, struct bufferevent_private, bev);
int res = ;
short what = BEV_EVENT_WRITING;
int connected = ;
ev_ssize_t atmost = -;
//对 bufferevent加锁,支持多线程安全模式
_bufferevent_incref_and_lock(bufev);
//检测是否带有超时事件
if (event == EV_TIMEOUT) {
/* Note that we only check for event==EV_TIMEOUT. If
* event==EV_TIMEOUT|EV_WRITE, we can safely ignore the
* timeout, since a read has occurred */
what |= BEV_EVENT_TIMEOUT;
goto error;
}
//判断是否是连接事件
if (bufev_p->connecting) {
int c = evutil_socket_finished_connecting(fd);
/* we need to fake the error if the connection was refused
* immediately - usually connection to localhost on BSD */
if (bufev_p->connection_refused) {
bufev_p->connection_refused = ;
c = -;
} if (c == )
goto done; bufev_p->connecting = ;
//连接失败删除该事件
if (c < ) {
event_del(&bufev->ev_write);
event_del(&bufev->ev_read);
_bufferevent_run_eventcb(bufev, BEV_EVENT_ERROR);
goto done;
} else {
connected = ;
//windows情况下直接运行事件回调函数,然后go done
#ifdef WIN32
if (BEV_IS_ASYNC(bufev)) {
event_del(&bufev->ev_write);
bufferevent_async_set_connected(bufev);
_bufferevent_run_eventcb(bufev,
BEV_EVENT_CONNECTED);
goto done;
}
#endif
//linux 下 运行事件回调函数
_bufferevent_run_eventcb(bufev,
BEV_EVENT_CONNECTED);
//检测是否可写,不可写删除该事件
if (!(bufev->enabled & EV_WRITE) ||
bufev_p->write_suspended) {
event_del(&bufev->ev_write);
goto done;
}
}
}
//计算bufferevent能写的最大数量
atmost = _bufferevent_get_write_max(bufev_p);
//写事件挂起了,跳过。
if (bufev_p->write_suspended)
goto done; //output非空
if (evbuffer_get_length(bufev->output)) {
//将output的头打开,从头部发送
evbuffer_unfreeze(bufev->output, );
//bufferevent调用写操作,将outbuffer中的内容发送出去
res = evbuffer_write_atmost(bufev->output, fd, atmost);
//将output的头部关闭
evbuffer_freeze(bufev->output, );
if (res == -) {
int err = evutil_socket_geterror(fd);
if (EVUTIL_ERR_RW_RETRIABLE(err))
goto reschedule;
what |= BEV_EVENT_ERROR;
} else if (res == ) {
/* eof case
XXXX Actually, a 0 on write doesn't indicate
an EOF. An ECONNRESET might be more typical.
*/
//写完了
what |= BEV_EVENT_EOF;
}
if (res <= )
goto error;
//bufferevent减少发送的大小,留下未发送的,下次再发送,因为是PERSIST|WRITE
//所以会在下次检测到可写时候继续写
_bufferevent_decrement_write_buckets(bufev_p, res);
} //计算是否将outbuf中的内容发送完,发完了就删除写事件
if (evbuffer_get_length(bufev->output) == ) {
event_del(&bufev->ev_write);
} /*
* Invoke the user callback if our buffer is drained or below the
* low watermark.
*/
//将buffer中的内容发完,或者低于low 水位,那么调用用户注册的写回调函数
//之前注册在bufev->writecb中的回调函数
if ((res || !connected) &&
evbuffer_get_length(bufev->output) <= bufev->wm_write.low) {
_bufferevent_run_writecb(bufev);
} goto done; reschedule:
if (evbuffer_get_length(bufev->output) == ) {
event_del(&bufev->ev_write);
}
goto done; error:
bufferevent_disable(bufev, EV_WRITE);
_bufferevent_run_eventcb(bufev, what); done:
_bufferevent_decref_and_unlock(bufev);
}

第六步:这个函数内部每次尽可能多的发送数据,没有发送完就下次轮询继续发送,直到水位低于或等于

写数据的低水位,那么就会触发bufferevent 低水位写回调函数。也就是conn_writecb,

在conn_writecb内部检测output buffer中数据为空,就释放该bufferevent。

static void
conn_writecb(struct bufferevent *bev, void *user_data)
{
struct evbuffer *output = bufferevent_get_output(bev);
if (evbuffer_get_length(output) == ) {
printf("flushed answer\n");
bufferevent_free(bev);
}
}

这就是整体流程,bufferevent内部的流畅看懂即可,我们只需要使用libevent提供的接口即可。

我的公众号,谢谢关注

Libevent学习笔记(五) 根据例子学习bufferevent的更多相关文章

  1. (转)Qt Model/View 学习笔记 (五)——View 类

    Qt Model/View 学习笔记 (五) View 类 概念 在model/view架构中,view从model中获得数据项然后显示给用户.数据显示的方式不必与model提供的表示方式相同,可以与 ...

  2. Learning ROS for Robotics Programming Second Edition学习笔记(五) indigo computer vision

    中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS for Robotics Pr ...

  3. Go语言学习笔记五: 条件语句

    Go语言学习笔记五: 条件语句 if语句 if 布尔表达式 { /* 在布尔表达式为 true 时执行 */ } 竟然没有括号,和python很像.但是有大括号,与python又不一样. 例子: pa ...

  4. C++基础 学习笔记五:重载之运算符重载

    C++基础 学习笔记五:重载之运算符重载 什么是运算符重载 用同一个运算符完成不同的功能即同一个运算符可以有不同的功能的方法叫做运算符重载.运算符重载是静态多态性的体现. 运算符重载的规则 重载公式 ...

  5. C#可扩展编程之MEF学习笔记(五):MEF高级进阶

    好久没有写博客了,今天抽空继续写MEF系列的文章.有园友提出这种系列的文章要做个目录,看起来方便,所以就抽空做了一个,放到每篇文章的最后. 前面四篇讲了MEF的基础知识,学完了前四篇,MEF中比较常用 ...

  6. cmake学习笔记(五)

    在cmake 学习笔记(三) 中简单学习了 find_package 的 model 模式,在cmake 学习笔记(四)中了解一个CMakeCache相关的东西.但靠这些知识还是不能看懂PySide使 ...

  7. java之jvm学习笔记五(实践写自己的类装载器)

    java之jvm学习笔记五(实践写自己的类装载器) 课程源码:http://download.csdn.net/detail/yfqnihao/4866501 前面第三和第四节我们一直在强调一句话,类 ...

  8. Typescript 学习笔记五:类

    中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...

  9. ES6学习笔记<五> Module的操作——import、export、as

    import export 这两个家伙对应的就是es6自己的 module功能. 我们之前写的Javascript一直都没有模块化的体系,无法将一个庞大的js工程拆分成一个个功能相对独立但相互依赖的小 ...

随机推荐

  1. leetcode27_C++Remove Element

    给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度. 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成 ...

  2. 在intelij IDEA中添加对jetBrick文件的识别

    在intelij IDEA中添加对jetBrick文件的识别 打开setting, 搜索File Types, 在Recognized File Types窗口找到Java Server Page或者 ...

  3. Literature Books

    Lean In (Sheryl Sandberg) Option B (Sheryl Sandberg) Ready Player One

  4. Leetcode_2. Add_Two_Number

    2. Add_Two_Number 用两个非空链表分别表示两个非负整数,链表的节点表示数字的位,链表头表示数字的低位,链表尾表示数字高位.求两个链表所表示数字的和. 比如: Input: (2 -&g ...

  5. moment.js学习总结

    一. 介绍: moment.js不依赖任何第三方库,支持字符串.Date.时间戳以及数组等格式,可以像PHP的date()函数一样,格式化日期时间,计算相对时间,获取特定时间后的日期时间等等.下面是一 ...

  6. 福大软工1816:Beta(4/7)

    Beta 冲刺 (1/7) 队名:第三视角 组长博客链接 本次作业链接 团队部分 团队燃尽图 工作情况汇报 张扬(组长) 过去两天完成了哪些任务 文字/口头描述 准备四六级 展示GitHub当日代码/ ...

  7. a6

    组员:陈锦谋 今日内容: 界面按钮.icon制作,PS学习 明日计划: 继续 困难: 时间不多吧,今天主要电气实践

  8. package分析

    由于大家对package的使用存在太多困惑,我在这里将自己对于package的使用的领悟进行一点总结: package中所存放的文件 所有文件,不过一般分一下就分这三种 1,java程序源文件,扩展名 ...

  9. bash循环语句

    1  )单分支if语句 if 测试条件 :then 如果满足条件就执行这里的代码 f 2)双分支的if语句 if  测试条件:then 如果满足条件就执行这里的代码 else 如果不满足条件就执行这里 ...

  10. CodeForces Round #527 (Div3) D1. Great Vova Wall (Version 1)

    http://codeforces.com/contest/1092/problem/D1 Vova's family is building the Great Vova Wall (named b ...