(十八)bufferevent的读写回调函数及对外接口

来源:互联网 发布:java百度课程 编辑:程序博客网 时间:2024/06/07 12:56

前言

在上一节中,我们介绍了bufferevent实现自动管理的基本思想(水位线机制),在本小节中,我们将介绍bufferevent_readcbbufferevent_writecb等函数,了解它工作的全过程。

bufferevent_readcb

static voidbufferevent_readcb(int fd, short event, void *arg)  {    struct bufferevent *bufev = arg;    int res = 0;    short what = EVBUFFER_READ;    size_t len;    int howmuch = -1;    /* Note that we only check for event==EV_TIMEOUT. If    * event==EV_TIMEOUT|EV_READ, we can safely ignore the    * timeout, since a read has occurred */    /* 这个回调函数只有当缓冲区可读时才应被触发     * 如果是因为超时被触发,则直接跳转到error处     */    if (event == EV_TIMEOUT) {             what |= EVBUFFER_TIMEOUT;        goto error;    }    /*     * If we have a high watermark configured then we don't want to        * read more data than would make us reach the watermark.     */                              //先检测input缓冲区已有的数据    /* 不为0代表高水位被设置了(默认高水位是0(代表无限))     * 既然高水位被设置了,那么就需要检测是否超过水位了     */    if (bufev->wm_read.high != 0) {   //如果读取高水位不为0        howmuch = bufev->wm_read.high - EVBUFFER_LENGTH(bufev->input);   //到达高水位剩余的值        /* we might have lowered the watermark, stop reading */        //小于0,则代表越水位了,bufferevent停止读取        if (howmuch <= 0) {               struct evbuffer *buf = bufev->input;            //将可读事件删掉            event_del(&bufev->ev_read);               evbuffer_setcb(buf,                bufferevent_read_pressure_cb, bufev);               return;  //直接返回        }    }    //从fd读取数据到输入缓冲区    res = evbuffer_read(bufev->input, fd, howmuch);       if (res == -1) {        /* 这两种错误返回都可以进行再一次尝试,而不用退出         * EAGAIN是因为在非阻塞操作中,产生了阻塞(比如read函数,如果将fd设置为非阻塞,但是无数据可读,就会返回EAGAIN)         * EINTR是因为操作被信号中断了等原因而产生         */        if (errno == EAGAIN || errno == EINTR)               goto reschedule;    //重新调度        /* error case */        //因为其它原因出错了,则直接加上EVBUFFER_ERROR标记证明出错        what |= EVBUFFER_ERROR;        } else if (res == 0) {        /* eof case */        what |= EVBUFFER_EOF;  //缓冲区到尾了    }    if (res <= 0)        goto error;    //注册读事件    bufferevent_add(&bufev->ev_read, bufev->timeout_read);    /* See if this callbacks meets the water marks */    len = EVBUFFER_LENGTH(bufev->input);    if (bufev->wm_read.low != 0 && len < bufev->wm_read.low)          return;    /* 最高水位线不为默认值(无限),且长度大于最高水位线(从fd读取数据之后的情况)     * 这证明越位了,当重新恢复成没越过高水位的情况时才能重新将该事件注册     * 所以我们先将该事件从注册链表中删除     * 接着设置evbuffer的回调函数(这个是在分析evbuffer的时候讲解的)     * 将其设置成bufferevent_read_pressure_cb     * 别忘了该函数的作用是当没有越水位的时候,注册读事件     * 这样就完成了对越过高水位停止读取的控制     */    if (bufev->wm_read.high != 0 && len >= bufev->wm_read.high) {        struct evbuffer *buf = bufev->input;          event_del(&bufev->ev_read);      //将该读事件从注册链表中删除        /* Now schedule a callback for us when the buffer changes */        evbuffer_setcb(buf, bufferevent_read_pressure_cb, bufev);       }    /* Invoke the user callback - must always be called last */    //用户设置的回调函数,最后才调用    if (bufev->readcb != NULL)        (*bufev->readcb)(bufev, bufev->cbarg);      return; //重新调度 reschedule:    bufferevent_add(&bufev->ev_read, bufev->timeout_read);       return; //error情况 error:    (*bufev->errorcb)(bufev, what, bufev->cbarg);}

这个函数稍微有点长,跟着注释一行一行读下来应该还是比较容易理解。这里再整理一下可能有点难懂的地方:当越过读取高水位时,停止读取的操作。

  1. 首先,当高水位为0的时候,这是代表高水位是无穷大,并不是没有字节,千万不要误解
  2. 然后当检测到高水位不是无限大的时候,就需要检测当前是否越过了高水位
  3. 如果越过了,则将该事件从注册链表中删除,然后给缓冲区设置bufferevent_read_pressure_cb回调函数(该函数会检测当前缓冲区大小是否越位,如果没有越位,则重新注册读事件),然后不用进行下面的读取操作了,直接返回
  4. 如果没越过,则进行读取

还有一点想再提一下,当缓冲区的读事件触发时,先调用的是bufferevent_readcb而不是用户注册的读回调函数,在bufferevent_readcb函数中,末尾(此时数据已经读入了缓冲区)才会调用用户注册的函数(所以用户可以直接对缓冲区进行操作)。这一点务必理解,否则你便没有明白bufferevent是如何自动管理缓冲区的。

bufferevent_writecb

关于bufferevent_writecb函数,其实和bufferevent_readcb函数类似。这里我们就简单的看一下它的逻辑,就不详细分析了。

static voidbufferevent_writecb(int fd, short event, void *arg){    struct bufferevent *bufev = arg;    int res = 0;    short what = EVBUFFER_WRITE;    if (event == EV_TIMEOUT) {        what |= EVBUFFER_TIMEOUT;           goto error;    }    /* 有数据直接读取即可     * 写入低水位默认是0,意思是只有当输出缓冲区为空时才会回调     */    if (EVBUFFER_LENGTH(bufev->output)) {        //将缓冲区中的数据写到fd      res = evbuffer_write(bufev->output, fd);          if (res == -1) {#ifndef WIN32/*todo. evbuffer uses WriteFile when WIN32 is set. WIN32 system calls do not *set errno. thus this error checking is not portable*/            if (errno == EAGAIN ||            errno == EINTR ||            errno == EINPROGRESS)                goto reschedule;            /* error case */            what |= EVBUFFER_ERROR;#else                goto reschedule;#endif        } else if (res == 0) {            /* eof case */            what |= EVBUFFER_EOF;        }        if (res <= 0)            goto error;    }  /* 如果输出缓冲区还剩有数据(一次没读完)   * 则将该读事件重新注册到事件链表上   */    if (EVBUFFER_LENGTH(bufev->output) != 0)          bufferevent_add(&bufev->ev_write, bufev->timeout_write);    /*     * Invoke the user callback if our buffer is drained or below the     * low watermark.     */    //只有到达低水位及以下,才会回调    if (bufev->writecb != NULL &&        EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low)        (*bufev->writecb)(bufev, bufev->cbarg);    return; reschedule:    if (EVBUFFER_LENGTH(bufev->output) != 0)          bufferevent_add(&bufev->ev_write, bufev->timeout_write);    return; error:    (*bufev->errorcb)(bufev, what, bufev->cbarg);}

最后,我们再介绍一下留给用户的读取/写入缓冲区的外部接口bufferevent_write以及bufferevent_read这些函数。

写入缓冲区

bufferevent_write

intbufferevent_write(struct bufferevent *bufev, const void *data, size_t size){    int res;    //将data开始size大小的字节接到输出缓冲区的尾部    res = evbuffer_add(bufev->output, data, size);       //调用失败    if (res == -1)        return (res);    /* If everything is okay, we need to schedule a write */    //注册写事件    if (size > 0 && (bufev->enabled & EV_WRITE))             bufferevent_add(&bufev->ev_write, bufev->timeout_write);    return (res);}

bufferevent_write_buffer

这个函数对上一个进行了一层封装,如果调用失败,会清除缓冲区

intbufferevent_write_buffer(struct bufferevent *bufev, struct evbuffer *buf){    int res;    res = bufferevent_write(bufev, buf->buffer, buf->off);    if (res != -1)        evbuffer_drain(buf, buf->off);      return (res);}

读出缓冲区

bufferevent_read

size_tbufferevent_read(struct bufferevent *bufev, void *data, size_t size){    struct evbuffer *buf = bufev->input;    /* 如果小于要读的字节数     * 就读实际有的数据     */    if (buf->off < size)             size = buf->off;       /* Copy the available data to the user buffer */    memcpy(data, buf->buffer, size);    //调整缓冲区大小    if (size)        evbuffer_drain(buf, size);    return (size);}

小结

在本小节中,我们分析了当缓冲区发生读/写事件时,会进行回调的函数,以及留给用户对缓冲区进行操作的接口。加上上一节所讲的,你应该对bufferevent的作用有了一定的理解。缓冲区部分讲完,对libevent源码的分析也接近尾声了。

原创粉丝点击