Libevent之bufferevent详解

#简介
bufferevent专门为封装成带有缓冲区的socket套接字。当有数据到来时,我们只需要在回调函数里面通过封装函数bufferevent_read读取数据即可,根本不需要自己处理一些细节,以及缓存的问题。
bufferevent其实也就是在event_base的基础上再进行一层封装,其本质还是离不开event和event_base,从bufferevent的结构体就可以看到这一点。
bufferevent结构体中有两个event,分别用来监听同一个fd的可读事件和可写事件。为什么不用一个event同时监听可读和可写呢?这是因为监听可写是困难的,下面会说到原因。读者也可以自问一下,自己之前有没有试过用最原始的event监听一个fd的可写。由于socket是全双工的,所以在bufferevent结构体中,也有两个evbuffer成员,分别是读缓冲区和写缓冲区。 bufferevent结构体定义如下:

//bufferevent_struct.h文件  
struct bufferevent {  
    struct event_base *ev_base;  //执行ev_base,最后需要将socket fd添加进ev_base。
      
    //操作结构体,成员有一些函数指针。类似struct eventop结构体  
    const struct bufferevent_ops *be_ops;  
  
    struct event ev_read;//读事件event  
    struct event ev_write;//写事件event  
  
    struct evbuffer *input;//读缓冲区  
  
    struct evbuffer *output; //写缓冲区  
  
    struct event_watermark wm_read;//读水位  
    struct event_watermark wm_write;//写水位  
  
      
    bufferevent_data_cb readcb;//可读时的回调函数指针  
    bufferevent_data_cb writecb;//可写时的回调函数指针  
    bufferevent_event_cb errorcb;//错误发生时的回调函数指针  
    void *cbarg;//回调函数的参数  
  
    struct timeval timeout_read;//读事件event的超时值  
    struct timeval timeout_write;//写事件event的超时值  
  
    /** Events that are currently enabled: currently EV_READ and EV_WRITE 
        are supported. */  
    short enabled;  
};  

如果看过Libevent的参考手册的话,应该还会知道bufferevent除了用于socket外,还可以用于socketpair和filter。如果用面向对象的思维,应从这三个应用中抽出相同的部分作为父类,然后派生出三个子类。
Libevent虽然是用C语言写的,不过它还是提取出一些公共部分,然后定义一个bufferevent_private结构体,用于保存这些公共部分成员。从集合的角度来说,bufferevent_private应该是bufferevent的一个子集,即一部分。但在Libevent中,bufferevent确是bufferevent_private的一个成员。下面是bufferevent_private结构体。

//bufferevent-internal.h文件  
struct bufferevent_private {  
    struct bufferevent bev;  
  
    //设置input evbuffer的高水位时,需要一个evbuffer回调函数配合工作  
    struct evbuffer_cb_entry *read_watermarks_cb;  
  
    /** If set, we should free the lock when we free the bufferevent. */  
    //锁是Libevent自动分配的,还是用户分配的  
    unsigned own_lock : 1;  
  
    ...  
  
    //这个socket是否处理正在连接服务器状态  
    unsigned connecting : 1;  
    //标志连接被拒绝  
    unsigned connection_refused : 1;  
  
    //标志是什么原因把读挂起来  
    bufferevent_suspend_flags read_suspended;  
    
    //标志是什么原因把写挂起来  
    bufferevent_suspend_flags write_suspended;  
  
    enum bufferevent_options options;  
        int refcnt;// bufferevent的引用计数  
  
    //锁变量  
    void *lock;  
};  

#新建bufferevent
在已有event_base、evbuffer基础上面再加上一层封装,使得对于socket、socketpair、filter的读写都非常方便,专注于实际业务代码对应的回调函数,忽略平台之间的差异。

//bufferevent-internal.h文件  
struct bufferevent_ops {  
    const char *type;//类型名称  
  
    off_t mem_offset;//成员bev的偏移量  
  
    //启动。将event加入到event_base中  
    int (*enable)(struct bufferevent *, short);  
  
    //关闭。将event从event_base中删除  
    int (*disable)(struct bufferevent *, short);  
    //销毁  
    void (*destruct)(struct bufferevent *);  
    //调整event的超时值  
    int (*adj_timeouts)(struct bufferevent *);  
    /** Called to flush data. */  
    int (*flush)(struct bufferevent *, short, enum bufferevent_flush_mode);  
    //获取成员的值。具体看实现  
    int (*ctrl)(struct bufferevent *, enum bufferevent_ctrl_op, union bufferevent_ctrl_data *);  
};  

因为bufferevent可以处理三种操作,并且每种操作对应的函数都不尽相同,所以通过bufferevent_ops结构体变量,存储不同操作对应的函数。使用起来就非常方便了。
以下针对socket说明上述的bufferevent_ops

//bufferevent-internal.h文件  
struct bufferevent_ops {  
    const char *type;//类型名称  
  
    off_t mem_offset;//成员bev的偏移量  
  
    //启动。将event加入到event_base中  
    int (*enable)(struct bufferevent *, short);  
  
    //关闭。将event从event_base中删除  
    int (*disable)(struct bufferevent *, short);  
    //销毁  
    void (*destruct)(struct bufferevent *);  
    //调整event的超时值  
    int (*adj_timeouts)(struct bufferevent *);  
    /** Called to flush data. */  
    int (*flush)(struct bufferevent *, short, enum bufferevent_flush_mode);  
    //获取成员的值。具体看实现  
    int (*ctrl)(struct bufferevent *, enum bufferevent_ctrl_op, union bufferevent_ctrl_data *);  
};  
//由于有几个不同类型的bufferevent,而且它们的enable、disable等操作是不同的。所以  
//需要的一些函数指针指明某个类型的bufferevent应该使用哪些操作函数。结构体bufferevent_ops_socket  
//就应运而生。对于socket,其操作函数如上。 

存储与socket相关的处理函数。

//bufferevent_sock.c文件  
struct bufferevent *  
bufferevent_socket_new(struct event_base *base, evutil_socket_t fd,  
    int options)  
{  
    struct bufferevent_private *bufev_p;  
    struct bufferevent *bufev;  
  
    ...//win32  
  
    //结构体内存清零,所有成员都为0  
    if ((bufev_p = mm_calloc(1, sizeof(struct bufferevent_private)))== NULL)  
        return NULL;  
  
    //如果options中需要线程安全,那么就会申请锁  
    //会新建一个输入和输出缓存区  
    if (bufferevent_init_common(bufev_p, base, &bufferevent_ops_socket,  
                    options) < 0) {  
        mm_free(bufev_p);  
        return NULL;  
    }  
    bufev = &bufev_p->bev;  
    //设置将evbuffer的数据向fd传  
    evbuffer_set_flags(bufev->output, EVBUFFER_FLAG_DRAINS_TO_FD);  
  
    //将fd与event相关联。同一个fd关联两个event  
    event_assign(&bufev->ev_read, bufev->ev_base, fd,  
        EV_READ|EV_PERSIST, bufferevent_readcb, bufev);  
    event_assign(&bufev->ev_write, bufev->ev_base, fd,  
        EV_WRITE|EV_PERSIST, bufferevent_writecb, bufev);  
  
    //设置evbuffer的回调函数,使得外界给写缓冲区添加数据时,能触发  
    //写操作,这个回调对于写事件的监听是很重要的  
    evbuffer_add_cb(bufev->output, bufferevent_socket_outbuf_cb, bufev);  
  
    //冻结读缓冲区的尾部,未解冻之前不能往读缓冲区追加数据  
    //也就是说不能从socket fd中读取数据  
    evbuffer_freeze(bufev->input, 0);  
  
    //冻结写缓冲区的头部,未解冻之前不能把写缓冲区的头部数据删除  
    //也就是说不能把数据写到socket fd  
    evbuffer_freeze(bufev->output, 1);  
  
    return bufev;  
}  

函数在最后面会冻结两个缓冲区。其实,虽然这里冻结了,但实际上Libevent在读数据或者写数据之前会解冻的读完或者写完数据后,又会马上冻结。这主要防止数据被意外修改。用户一般不会直接调用evbuffer_freeze或者evbuffer_unfreeze函数。一切的冻结和解冻操作都由Libevent内部完成。还有一点要注意,因为这里只是把写缓冲区的头部冻结了。所以还是可以往写缓冲区的尾部追加数据。同样,此时也是可以从读缓冲区读取数据。这个是必须的。因为在Libevent内部不解冻的时候,用户需要从读缓冲区中获取数据(这相当于从socket fd中读取数据),用户也需要把数据写到写缓冲区中(这相当于把数据写入到socket fd中)。
在bufferevent_socket_new函数里面会调用函数bufferevent_init_common完成公有部分的初始化。

//bufferevent.c文件  
int  
bufferevent_init_common(struct bufferevent_private *bufev_private,  
    struct event_base *base,  
    const struct bufferevent_ops *ops,  
    enum bufferevent_options options)  
{  
    struct bufferevent *bufev = &bufev_private->bev;  
  
    //分配输入缓冲区  
    if (!bufev->input) {  
        if ((bufev->input = evbuffer_new()) == NULL)  
            return -1;  
    }  
  
    //分配输出缓冲区  
    if (!bufev->output) {  
        if ((bufev->output = evbuffer_new()) == NULL) {  
            evbuffer_free(bufev->input);  
            return -1;  
        }  
    }  
  
    bufev_private->refcnt = 1;//引用次数为1  
    bufev->ev_base = base;  
  
    /* Disable timeouts. */  
    //默认情况下,读和写event都是不支持超时的  
    evutil_timerclear(&bufev->timeout_read);  
    evutil_timerclear(&bufev->timeout_write);  
  
    bufev->be_ops = ops;  
  
    /* 
     * Set to EV_WRITE so that using bufferevent_write is going to 
     * trigger a callback.  Reading needs to be explicitly enabled 
     * because otherwise no data will be available. 
     */  
     //可写是默认支持的  
    bufev->enabled = EV_WRITE;  
  
#ifndef _EVENT_DISABLE_THREAD_SUPPORT  
    if (options & BEV_OPT_THREADSAFE) {  
        //申请锁。  
        if (bufferevent_enable_locking(bufev, NULL) < 0) {  
            /* cleanup */  
            evbuffer_free(bufev->input);  
            evbuffer_free(bufev->output);  
            bufev->input = NULL;  
            bufev->output = NULL;  
            return -1;  
        }  
    }  
#endif  
    ...//延迟调用的初始化,一般不需要用到  
  
    bufev_private->options = options;  
  
    //将evbuffer和bufferevent相关联  
    evbuffer_set_parent(bufev->input, bufev);  
    evbuffer_set_parent(bufev->output, bufev);  
  
    return 0;  
}  

代码中可以看到,默认是enable EV_WRITE。

参考:
https://blog.csdn.net/column/details/libevent-src.html?&page=2

  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有时需要偏执狂

请我喝咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值