Libevent学习之处理可读 and 可写事件

来源:互联网 发布:suse12 linux 网卡配置 编辑:程序博客网 时间:2024/05/24 07:33

处理可读事件:

底层fd接收到数据以后,bufferevent是如何工作的。
通过函数bufferevent_setwatermask可以设置读和写的水位.

低水位比较容易懂,就是当可读的数据量到达这个低水位后,才会调用用户设置的回调函数。比如用户想每次读取100字节,那么就可以把低水位设置为100。当可读数据的字节数小于100时,即使有数据都不会打扰用户(即不会调用用户设置的回调函数)。可读数据大于等于100字节后,才会调用用户的回调函数.

高水位:把读事件的evbuffer的数据量限制在高水位之下,类似于限速限流量功能.
如果用户设置了读事件的高水位,那么当读缓冲区的数据量达到这个高水位时,使用函数bufferevent_wm_suspend_read把监听读事件的event挂起来,暂时不读取socket fd中的数据.

下面看一下Libevent是怎么把一个event挂起来的

bufferevent_readcb(evutil_socket_t fd, short event, void *arg){...    /*     * If we have a high watermark configured then we don't want to     * read more data than would make us reach the watermark.     */    if (bufev->wm_read.high != 0) {        //当前读buffer比高水位要高        howmuch = bufev->wm_read.high - evbuffer_get_length(input);        /* we somehow lowered the watermark, stop reading */        if (howmuch <= 0) {            bufferevent_wm_suspend_read(bufev);            goto done;        }    }    readmax = _bufferevent_get_read_max(bufev_p);...}

当前读缓冲区大小大于设置的高水位时,会调用函数将读事件挂起

//bufferevent-internal.h文件  #define bufferevent_wm_suspend_read(b) \      bufferevent_suspend_read((b), BEV_SUSPEND_WM)  //bufferevent.c文件  void  bufferevent_suspend_read(struct bufferevent *bufev, bufferevent_suspend_flags what)  {      struct bufferevent_private *bufev_private =          EVUTIL_UPCAST(bufev, struct bufferevent_private, bev);      BEV_LOCK(bufev);      if (!bufev_private->read_suspended)//不能挂多次          bufev->be_ops->disable(bufev, EV_READ);//实际调用be_socket_disable函数      bufev_private->read_suspended |= what;//记录被挂起原因      BEV_UNLOCK(bufev);  } //bufferevent_sock.c文件  static int  be_socket_disable(struct bufferevent *bufev, short event)  {      struct bufferevent_private *bufev_p =          EVUTIL_UPCAST(bufev, struct bufferevent_private, bev);      if (event & EV_READ) {          if (event_del(&bufev->ev_read) == -1)              return -1;      }      /* Don't actually disable the write if we are trying to connect. */      if ((event & EV_WRITE) && ! bufev_p->connecting) {          if (event_del(&bufev->ev_write) == -1)//删掉这个event              return -1;      }      return 0;  } 

居然是直接删除了监听读事件的event!看来不能随便设置高水位,因为会导致暂停读.

那么什么时候取消挂起读事件呢,让bufferevent可以继续读取socket fd的数据呢?当移除evbuffer中的数据时,Libevent会检查这个evbuffer的数据是否会小于高水位,如果小于的话,那么就恢复读事件,也就是说evbuffer在添加或删除数据时,应该会有一个回调函数.

bufferevent_setwatermark不仅为高水位设置回调函数,还会检查当前evbuffer的数据量是否超过了高水位。因为这个设置水位函数可能是在bufferevent工作一段时间后才添加的,所以evbuffer是有可能已经有数据的了,因此需要检查。如果超过了水位值,那么就需要挂起读。当然也存在另外一种可能:用户之前设置过了一个比较大的高水位,挂起了读。现在发现错了,就把高水位调低一点,此时就需要恢复读。

现在假设用户移除了一些evbuffer的数据,进而触发了evbuffer的回调函数,当然也就调用了函数bufferevent_inbuf_wm_cb。下面看一下这个函数是怎么恢复读的。

//bufferevent.c文件  static void  bufferevent_inbuf_wm_cb(struct evbuffer *buf,      const struct evbuffer_cb_info *cbinfo,      void *arg)  {      struct bufferevent *bufev = arg;      size_t size;      size = evbuffer_get_length(buf);      if (size >= bufev->wm_read.high)          bufferevent_wm_suspend_read(bufev);      else          bufferevent_wm_unsuspend_read(bufev);  }  //bufferevent-internal.h文件  #define bufferevent_wm_unsuspend_read(b) \      bufferevent_unsuspend_read((b), BEV_SUSPEND_WM)  //bufferevent.c文件  void  bufferevent_unsuspend_read(struct bufferevent *bufev, bufferevent_suspend_flags what)  {      struct bufferevent_private *bufev_private =          EVUTIL_UPCAST(bufev, struct bufferevent_private, bev);      BEV_LOCK(bufev);      bufev_private->read_suspended &= ~what;      if (!bufev_private->read_suspended && (bufev->enabled & EV_READ))          bufev->be_ops->enable(bufev, EV_READ);//重新把event插入到event_base中      BEV_UNLOCK(bufev);  }  

因为用户可以手动为这个evbuffer添加数据,此时也会调用bufferevent_inbuf_wm_cb函数。此时就要检查evbuffer的数据量是否已经超过高水位了,而不能仅仅检查是否低于高水位。

从socket中读取数据

从前面的一系列博文可以知道,如果一个socket可读了,那么监听可读事件的event的回调函数就会被调用。这个回调函数是在bufferevent_socket_new函数中被Libevent内部设置的,设置为bufferevent_readcb函数,用户并不知情。
当socket有数据可读时,Libevent就会监听到,然后调用bufferevent_readcb函数处理。该函数会调用evbuffer_read函数,把数据从socket fd中读取到evbuffer中。然后再调用用户在bufferevent_setcb函数中设置的读事件回调函数。所以,当用户的读事件回调函数被调用时,数据已经在evbuffer中了,用户拿来就用,无需调用read这类会阻塞的函数。

处理写事件

如果一个event监听了可写事件,那么这个event就会一直被触发(死循环)。因为一般情况下,如果不是发大量的数据这个写缓冲区是不会满的。不能监听可写事件。但我们确实要往fd中写数据,那怎么办?Libevent的做法是:当我们确实要写入数据时,才监听可写事件。也就是说我们调用bufferevent_write写入数据时,Libevent才会把监听可写事件的那个event注册到event_base中。当Libevent把数据都写入到fd的缓冲区后,Libevent又会把这个event从event_base中删除。比较烦琐.

同前面的监听可读一样,Libevent是在bufferevent_socket_new函数设置可写的回调函数,为bufferevent_writecb。

//bufferevent_sock.c文件  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 = 0;      short what = BEV_EVENT_WRITING;      int connected = 0;      ev_ssize_t atmost = -1;      _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;      }      ...//判断这个socket是否已经连接上服务器了      //用户可能设置了限速,如果没有限速,那么atmost将返回16384(16K)      atmost = _bufferevent_get_write_max(bufev_p);      //一些原因导致写被挂起来了      if (bufev_p->write_suspended)          goto done;      //如果evbuffer有数据可以写到sockfd中      if (evbuffer_get_length(bufev->output)) {          //解冻链表头          evbuffer_unfreeze(bufev->output, 1);          //将output这个evbuffer的数据写到socket fd 的缓冲区中          //会把已经写到socket fd缓冲区的数据,从evbuffer中删除          res = evbuffer_write_atmost(bufev->output, fd, atmost);          evbuffer_freeze(bufev->output, 1);          if (res == -1) {              int err = evutil_socket_geterror(fd);              if (EVUTIL_ERR_RW_RETRIABLE(err))//可以恢复的错误。一般是EINTR或者EAGAIN                  goto reschedule;              what |= BEV_EVENT_ERROR;          } else if (res == 0) {//该socket已经断开连接了              what |= BEV_EVENT_EOF;          }          if (res <= 0)              goto error;      }      //如果把写缓冲区的数据都写完成了。为了防止event_base不断地触发可写      //事件,此时要把这个监听可写的event删除。      //前面的atmost限制了一次最大的可写数据。如果还没写所有的数据      //那么就不能delete这个event,而是要继续监听可写事情,知道把所有的      //数据都写到socket fd中。      if (evbuffer_get_length(bufev->output) == 0) {          event_del(&bufev->ev_write);      }      //如果evbuffer里面的数据量已经写得七七八八了,小于设置的低水位值,那么      //就会调用用户设置的写事件回调函数      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) == 0) {          event_del(&bufev->ev_write);      }      goto done;   error:      bufferevent_disable(bufev, EV_WRITE);//有错误。把这个写event删除      _bufferevent_run_eventcb(bufev, what);   done:      _bufferevent_decref_and_unlock(bufev);  }  
0 0
原创粉丝点击