接上文libevent(九)bufferevent

上文主要讲了bufferevent如何监听读事件,那么bufferevent如何监听写事件呢?

对于一个fd,只要它的写缓冲区没有满,就会触发写事件。

一般情况下,如果不向这个fd发送大量的数据,它的写缓冲区是不会满的。

所以,如果一开始就监听写事件,写事件会一直被触发。

libevent的做法是:

当我们确实要向fd写入数据时,才监听该fd的写事件。

监听写事件

在用户回调函数中,可以通过 bufferevent_write 向输出缓冲output中写数据。

int
bufferevent_write(struct bufferevent *bufev, const void *data, size_t size)
{
if (evbuffer_add(bufev->output, data, size) == -)
return (-); return ;
}
/* Adds data to an event buffer */

int
evbuffer_add(struct evbuffer *buf, const void *data_in, size_t datlen)
{
struct evbuffer_chain *chain, *tmp;
const unsigned char *data = data_in;
size_t remain, to_alloc;
int result = -; EVBUFFER_LOCK(buf); if (buf->freeze_end) {
goto done;
}
/* Prevent buf->total_len overflow */
if (datlen > EV_SIZE_MAX - buf->total_len) {
goto done;
} chain = buf->last; /* If there are no chains allocated for this buffer, allocate one
* big enough to hold all the data. */
if (chain == NULL) {
chain = evbuffer_chain_new(datlen);
if (!chain)
goto done;
evbuffer_chain_insert(buf, chain);
} if ((chain->flags & EVBUFFER_IMMUTABLE) == ) {
/* Always true for mutable buffers */
EVUTIL_ASSERT(chain->misalign >= &&
(ev_uint64_t)chain->misalign <= EVBUFFER_CHAIN_MAX);
remain = chain->buffer_len - (size_t)chain->misalign - chain->off;
if (remain >= datlen) {
/* there's enough space to hold all the data in the
* current last chain */
memcpy(chain->buffer + chain->misalign + chain->off,
data, datlen);
chain->off += datlen;
buf->total_len += datlen;
buf->n_add_for_cb += datlen;
goto out;
} else if (!CHAIN_PINNED(chain) &&
evbuffer_chain_should_realign(chain, datlen)) {
/* we can fit the data into the misalignment */
evbuffer_chain_align(chain); memcpy(chain->buffer + chain->off, data, datlen);
chain->off += datlen;
buf->total_len += datlen;
buf->n_add_for_cb += datlen;
goto out;
}
} else {
/* we cannot write any data to the last chain */
remain = ;
} /* we need to add another chain */
to_alloc = chain->buffer_len;
if (to_alloc <= EVBUFFER_CHAIN_MAX_AUTO_SIZE/)
to_alloc <<= ;
if (datlen > to_alloc)
to_alloc = datlen;
tmp = evbuffer_chain_new(to_alloc);
if (tmp == NULL)
goto done; if (remain) {
memcpy(chain->buffer + chain->misalign + chain->off,
data, remain);
chain->off += remain;
buf->total_len += remain;
buf->n_add_for_cb += remain;
} data += remain;
datlen -= remain; memcpy(tmp->buffer, data, datlen);
tmp->off = datlen;
evbuffer_chain_insert(buf, tmp);
buf->n_add_for_cb += datlen; out:
evbuffer_invoke_callbacks(buf);
result = ;
done:
EVBUFFER_UNLOCK(buf);
return result;
}

现在回顾一下bufferevent_socket_new,我们在这个函数中,设置了输出缓冲区的回调函数

evbuffer_add_cb(bufev->output, bufferevent_socket_outbuf_cb, bufev);
static void
bufferevent_socket_outbuf_cb(struct evbuffer *buf,
const struct evbuffer_cb_info *cbinfo,
void *arg)
{
struct bufferevent *bufev = arg;
struct bufferevent_private *bufev_p =
EVUTIL_UPCAST(bufev, struct bufferevent_private, bev); if (cbinfo->n_added &&
(bufev->enabled & EV_WRITE) &&
!event_pending(&bufev->ev_write, EV_WRITE, NULL) &&
!bufev_p->write_suspended) {
/* Somebody added data to the buffer, and we would like to
* write, and we were not writing. So, start writing. */
if (be_socket_add(&bufev->ev_write, &bufev->timeout_write) == -) {
/* Should we log this? */
}
}
}

可以看出,我们在输出缓冲区的回调函数中,将该fd的写事件添加到了epoll中。

事件流程

上面我们监听了fd的写事件,而此时该fd的写缓冲区没有满,所以写事件被触发,继而调用我们在上文设置的写事件回调函数 bufferevent_writecb。

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_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 = ;
#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
_bufferevent_run_eventcb(bufev,
BEV_EVENT_CONNECTED);
if (!(bufev->enabled & EV_WRITE) ||
bufev_p->write_suspended) {
event_del(&bufev->ev_write);
goto done;
}
}
} atmost = _bufferevent_get_write_max(bufev_p); if (bufev_p->write_suspended)
goto done; if (evbuffer_get_length(bufev->output)) {
evbuffer_unfreeze(bufev->output, );
res = evbuffer_write_atmost(bufev->output, fd, atmost);
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_decrement_write_buckets(bufev_p, res);
} 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.
*/
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);
}

主要做了三件事:

  1. 通过evbuffer_write_atmost将输出缓冲output中的数据写入fd中。
  2. 如果output中的数据全部处理完毕,删除写事件
  3. 调用用户定义的写事件回调函数

参考资料:

Libevent源码分析-----bufferevent工作流程探究


libevent(十)bufferevent 2的更多相关文章

  1. 处理大并发之五 使用libevent利器bufferevent

    转自:http://blog.csdn.net/feitianxuxue/article/details/9386843 处理大并发之五 使用libevent利器bufferevent 首先来翻译一段 ...

  2. libevent(九)bufferevent

    bufferevent,带buffer的event struct bufferevent { struct event_base *ev_base; const struct bufferevent_ ...

  3. libevent+bufferevent总结

    libevent+bufferevent总结 1 学习参考网址 libevent学习网址:http://blog.csdn.net/feitianxuxue/article/details/93725 ...

  4. libevent中的bufferevent原理

    以前的文章看过缓冲区buffer了,libevent用bufferevent来负责管理缓冲区与buffer读写事件.       今天就带大家看下evbuffer.c,使用bufferevent处理事 ...

  5. libevent网络编程汇总

    libevent源码剖析: ========================================================== 1.libevent源码剖析一(序) 2.libeve ...

  6. Libevent:7Bufferevents基本概念

    很多时候,应用程序除了能响应事件之外,还希望能够处理一定量的数据缓存.比如,当写数据的时候,一般会经历下列步骤: l  决定向一个链接中写入一些数据:将数据放入缓冲区中: l  等待该链接变得可写: ...

  7. 项目中的Libevent(多线程)

    多线程版Libevent //保存线程的结构体 struct LibeventThread { LibEvtServer* that; //用作传参 std::shared_ptr<std::t ...

  8. reactor模型框架图和流程图 libevent

    学习libevent有助于提升程序设计功力,除了网络程序设计方面外,libevent的代码里有很多有用的设计技巧和基础数据结构,比如信息隐藏.函数指针.c语言的多态支持.链表和堆等等,都有助于提升自身 ...

  9. libevent(1)

    很多时候,除了响应事件之外,应用还希望做一定的数据缓冲.比如说,写入数据的时候,通常的运行模式是: l 决定要向连接写入一些数据,把数据放入到缓冲区中 l 等待连接可以写入 l 写入尽量多的数据 l  ...

随机推荐

  1. mount --bind绑定命令

    将目录或文件DirFile-1绑定到目录或文件DirFile-2上,所有对DirFile-2的访问就是对DirFile-1的访问 mount --bind [DirFile-1] [DirFile-2 ...

  2. windows VMware 安装mac 系统

    0x00 下载链接 首先肯定要有镜像: 链接:https://pan.baidu.com/s/190NBRBwNXVOYRxb6nodHeA 提取码:ahq5 然后还得有这个插件: 链接:https: ...

  3. AJ学IOS 之ipad开发Popover的调色板应用_popover显示后其他控件仍然能进行交互

    AJ分享,必须精品 一:效果 后面的是xcode的控制台 二:代码 ViewController #import "ViewController.h" #import " ...

  4. python基础入门:matplotlib绘制多Y轴画图(附源码)

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:屁屁酱 PS:如有需要Python学习资料的小伙伴可以加点击下方链接 ...

  5. G - Can you find it? 二分

    Give you three sequences of numbers A, B, C, then we give you a number X. Now you need to calculate ...

  6. Matlab学习-(2)

    1. 文件读取 在编写一个matlab项目时候,通常要导入很多不同格式的数据,下面我们来学习不同的导入函数.(1) 保存工作区MATLAB支持工作区的保存.用户可以将工作区或工作区中的变量以文件的形式 ...

  7. CoreDNS解析异常记录

    CoreDNS解析异常记录 异常情况:集群是用kubespray部署的4个worknode,coredns默认部署2个deployment.今天发现部署了coredns的node上的pod正常解析内部 ...

  8. 基于spring的安全管理框架-Spring Security

    什么是spring security? spring security是基于spring的安全框架.它提供全面的安全性解决方案,同时在Web请求级别和调用级别确认和授权.在Spring Framewo ...

  9. 5、flink常见函数使用及自定义转换函数

    代码地址:https://gitee.com/nltxwz_xxd/abc_bigdata 一.flink编程方法 获取执行环境(execution environment) 加载/创建初始数据集 对 ...

  10. 微信自动关闭内置浏览器页面,返回公众号窗口 WeixinJSBridge.call('closeWindow')

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...