libevent框架、带缓冲区事件bufferevent的使用

1.简介

特点

源码包安装

2.libevent框架

创建event_base

创建添加事件

循环监听事件满足

释放event_base

相关函数了解

3.常规事件event

未决与非未决

使用fifo的读写

4.带缓冲区事件bufferevent

bufferevent

A.服务器创建监听器

C.给读写缓冲区设置回调

D.禁用、启用缓冲区

E.客户端连接服务器

server

client


1.简介

libevent

libevent版本一共有1.4系列和2.0系列两个稳定版本。

1.4系列比较古老,但是源码简单,适合源码的学习

2.0系列比较新,建议直接使用2.0

需要注意的是,1.4系列和2.0系列两个版本的接口并不兼容,就是2.0将一些接口的原型发>生了改变,所以将1.4升级到2.0需要重新编码。

特点

开源、精简、跨平台、专注于网络通信

基于“事件”异步通信模型 -- 回调

事件:event库中将所有的事情封装成了事件,就像操作系统将所有的东西成文件

异步:对应同步(所有的事情都要讲究先后顺序),注册函数的时间和函数被调用的时间不同,只有当内核监听到事件的条件满足时,才会去调用函数(回调函数)。和信号一样,先注册信号捕捉函数,有信号来时才会去调用回调函数

核心其实就是epoll

源码包安装

参考README、readme

1.下载压缩包  -->    
        libevent-2.0.20-stable.tar.gz
2.解压 -->           
        tar -xvf libevent-2.0.20-stable.tar.gz  
3.进入解压后的目录 -->
        cd /....
4.检查安装环境,生产makefile -->
        ./configure
            参数可加可不加
5.生成 .o 和可执行文件 -->
        make
6.将必要的资源cp到系统指定目录(软、硬连接,必要的.so文件等)
        sudo make install
7.进入simple目录,运行demo验证库按安装使用情况,要添加上-levent,这里其实是采用动态库链接的方式,去掉lib前缀和.so后缀
        cd ./simple
        gcc hello-world.c -o hell -levent
        //-l库名:指定动态库库名,-L和-I不添加采用默认
        ./a.out

由于编译运行时指定的是动态库,运行可执行文件时动态链接器会去系统指定位置找该动态库,6 中其实就是将软、硬连接,必要的.so文件放到系统固定位置,这样链接时动态链接器才找得到 库.so(是个软链接,然后根据软链接去找到相应的可执行文件的目录)

链接时必要的.so文件存储在/user/lib或是/user/loal/lib

动态链接器查找.so文件的目录一般是/user/lib

2.libevent框架

创建event_base

【头文件】:#include<event2/event.h>
struct event_base *event_base_new(void);
struct event_base *base = NULL;
base = event_base_new();

创建添加事件

1.创建事件event -- 常规事件、带缓冲区事件:
    strcut event *event_new(strcut event_base *base, 
                             evutil_socket_t fd,
                             short what, 
                             event_callback_fn cd,
                             void *arg);
        what: EV_READ、EV_WRITE、EV_PERSIST(连续触发 -- whike(read() / write() ))
        cd: 回调函数 -- typedef void(*event_callback_fn)(evutil_socket_t fd, short,void *);

    struct bufferevent *bufferevent_socket_new
            (struct event_base *base,
             evutil socket_t fd, 
             enum bufferevent_options options);.
        base: 函数返回值
        options:BEV_OPT_CLOSE_FREE

2.将事件event添加到base上
    int event_add(struct event *ev, const struct timeval *tv);
        ev: event_new()函数返回的事件。
        tv: 为 NULL,不会超时。意为:一直等到事件被触发,回调函数会被调用。
            为非 0,等待期间,检查事件没有被触发,时间到,回调函数依旧会被调用。

    bufferevent暂时不看

循环监听事件满足

启动循环:相当于epoll中while{ epoll_wait(); ... } -- 阻塞监听到客户端的连接请求、读请求等然后调用回调函数进行处理
    它内部是等待事件满足然后根据事件去调用自己写的对应的回调函数
    int event_base_dispatch(struct event_base *base);
        base: event_base_new 函数的返回值
        返回值:1表示成功,0表示失败
    只有 event_new 中指定了EV_PERSIST 才持续触发,否则值触发一次,就跳出循环
    通常这样:EV_WRITEIEV_PERSIST、EV_READIEV_PERSIST

在指定事件后停止循环:
    int event_base_loopexit(struct event_base *base, const struct timeval *tv)
立即停止循环:
    int evnet_base_loopbread(struct event_base *base);

释放event_base

void event_base_new(struct event_base *base);

相关函数了解

查看 支持哪些 多路 IO
    const char **event_get_supported_methods(void);

查看 当前用的 多路 IO
    const char * event_base_get_method(const struct event base *base);

查看 fork后子进程使用的 event_base
    int event _reinit(struct event_base *base);
    成功:0,失败:-1
    使用该函数后,父进程的base才能在子进程中生效

3.常规事件event

可以用在本地中,如管道

步骤:

1.创建一个事件对象,监听的事件发生等于该ev发生
strcut event *event_new(strcut event_base *base,  //指明事件要插入的底座 -- 要调用event_add才真的插入
                             evutil_socket_t fd,  //事件对象中的fd,将其捆绑事件对象ev上,-- 事件/请求来源,回调函数处理的就是fd的请求
                             short what,          //要监听的事件 -- r、w、e -- r发生,监听条件满足,调用回调读取数据然后处理
                             event_callback_fn cd,//事件发生,监听条件满足,调用该回调函数
                             void *arg);          //回调函数的参数,传base自己或NULL
        what: EV_READ  一次读,event_base_dispatch(base)客户端有数据过来,服务器要读,监听到条件满足,读取然后调用回调函数处理数据,退出结束
              EV_WRITE 一次写
              EV_PERSIST 结合event_base_dispatch(base)处理完客户端的请求进入下一轮循环,继续等待客户端的数据然后读取,该函数不退出
                  (连续触发 -- whike(read() / write() ))    

        cb: 回调函数 -- typedef void(*event_callback_fn)(evutil_socket_t fd, short,void *);
        返回值:成功则返回创建的事件event

2.将事件添到base上,该base在ev当中已经被指明
  int event_add(struct event *ev, const struct timeval *tv);
        ev: event_new()函数返回的事件。
        tv: 为 NULL,不会超时。意为:一直等到事件被触发,回调函数会被调用。
            为非 0,等待期间,检查事件没有被触发,时间到,回调函数依旧会被调用。
        一旦事件ev中的对端fd有事件(r、w、e)发生满足监听条件,base监听到将调用ev中的回调函数去处理事件

3.将事件从base上拿下
int event_del(struct event *ev);
    ev: event_new()函数返回的事件

4.释放事件
int event_free(struct event *ev);
    成功:0;失败:-1

未决与非未决

未决:有资格被处理,但还没有被处理(事件还没发生,监听调价按不满足,无法调用回调处理事件 -- 处理数据)

非未决:没有资格被处理(还没准备好,即使事件到来也不会调用回调函数处理事件)

使用fifo的读写

服务器这边:处理读事件 -- 读取出来然后处理数据 -- base在这边

客户端:写事件,写给服务器

4.带缓冲区事件bufferevent

bufferevent

主要应用于套接字通信

#include <event2/bufferevent.h>
原理:bufferevent有两个缓冲区:也是队列实现,读走没,先进先出。
    读:有数据-->读回调函数被调用-->使用 bufferevent_read() --> 读数据。
    写:使用 bufferevent_write()-->向写缓冲中写数据 -->该缓冲区有数据自动写出-->写完,回调函数被调用(鸡肋)

A.服务器创建监听器

【头文件】: #include <event2/listener.h>

struct evconnlistener* evconnlistener_new(  
    struct event base *base,
    evconnlistener_cb cb,
    void *ptr,
    unsigned flags,
    int backlog,*
    evutil_socket_t fd
);         【了解】

1.【掌握】                                                                
struct evconnlistener *evconnlistener_new_bind (
        struct event_base *base,   //event_base_new(), 底座的创建
        evconnlistener_cb cb, //监听器的回调,在里面封装事件创建B、设置缓冲区回调C、启动缓冲区函数D
        void *ptr,  //cb的参数,cb中添加事件、bufferevent回调函数、设置缓冲区回调等函数都需要base,靠ptr传进回调cb中的回调使用
        unsigned flags,
        int backlog,
        const struct sockaddr *sa,
        int sockln);
    cb:监听回调函数。接受连接之后,用户要做的操作
    ptr:回调函数的参数
    flags:“可识别的标志”
            LEV_OPT_CLOSE_ON_FREE:释放 bufferevent 时关闭底层传输端口。这将关闭底层套接字.释放底层 bufferevent等
            LEV_OPT_REUSEABLE 端口复用。可以“ | ”
    backlog:listen的2参。传-1:表使用默认最大值
    sa:服务器IP+Port
    socklen:sa的大小
这一个函数,可完成系统调用socket(), bind(), listen(), accept()的作用

2. evconnlistener_cb cb回调函数 -- 在该回调函数体内创建事件处理服务器读请求,将B、C、D都封装进该回调
【注:该回调函数不由我们调用,是框架自动调用,因此只需知晓参数含义即可】
typedef void (*evconnlistener_cb) (
            struct evconnlistener *listener,
            evutil_socket_t sock,
            struct sockaddr *addr,
            int len,
            void *ptr);
    listener: evconnlistener_new_bind 函数的返回值
    sock: 用于通信的文件描述符
    addr: 客户端的 IP+端口
    len: addr 的len.
    ptr:evconnlistener_new_bind()中ptr传递进来的值,一般传base,因为事件创建B、设置缓冲回调C、启动缓冲区函数D参数都需要base

B.带缓冲区的事件对象创建、释放

创建: -- 有客户端连接了才能创建,所以得先创建监听器,在监听器的回调函数中去等待客户端的请求连接然后创建事件绑定,处理请求
struct bufferevent *bufferevent_socket_new
       (struct event_base *base, 
        evutil_socket_t fd, 
        enum bufferevent_options options);
    base:event_base_new 函数的返回值。
    fd:跟 bufferevent绑定的套接字的文件描述符。类比 event_new()
    options:BEV_OPT_CLOSE_ON_FREE 只用这一个即可。
            [可参考《libevenet 参考手册(中文版)》p53,“bufferevent 的选项标志”】

释放:
void bufferevent_free(struct bufferevent *bev);
    bev:上面函数的返回值。

C.给读写缓冲区设置回调

void bufferevent_setcb(struct bufferevent *bufev,
                       bufferevent_data_cb readcb,
                       bufferevent_data_cb writecb,
                       bufferevent_event_cb eventcb
                       *void *cbarg );
    bufev:bufferevent_socket_new()函数的返回值。
    readcb:读缓冲对应的回调,自己封装,在其内部读数据。
        【注意】使用 bufferevent_read()读,而不是 read()
    Writecb:不用它,传NULL即可!!!
    eventcb:可传 NULL,用来处理异常或额外事件的回调
    cbarg :传给回调函数用的参数。

1.bufferevent_data_cb回调函数:
typedef vdid (*bufferevent_data_cb)(struct bufferevent *bev, void*ctx);
如:
    void read_cb(struct bufferevent *bev, void *arg){ 
        ......
        //size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);
        //    通常用在 readcb 中,替代read()!  //库提供的
        bufferevent_read()//读数据,类似 read()的作用

        //buffervent_write();
        //int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);
        //    常用在 bufferevent_read 之后,替代 write()!  //库提供的
        ......
    }  //自己设计的回调函数
    读数据:从 bufferevnet 输入缓冲区 中 移除数据


2.bufferevent_event_cb eventcb回调函数:
void event_cb(struct bufferevent *bev, short events, void *ctx){
    ......
    buffervent_event_cb(bev,events,ctx);
    /*
    typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short events, void *ctx);
    events:不同标志位,代表不同的事件。
        【可参考《libevenet 参考手册(中文版)》p52,“回调和水位”】
        EV_EVENT_READING:读取操作时发生某事件,具体是哪种事件,看其他标志。
        BEV_EVENT_WRITING:写入操作时发生某事件,具体是哪种事件请看其他标志。
        BEV_EVENT_ERROR:操作时发生错误。关于错误的更多信息,请调用EVUTILSOCKET_ERRORO()。
        BEV_EVENT_TIMEOUT:发生超时。
        BEV_EVENT_EOF:遇到文件结束指示。

        BEV_EVENT_CONNECTED:请求的连接过程已经完成,实现客户端时可用。
    /*

    ......
}

D.禁用、启用缓冲区

默认:新建的 bufferevent 写缓冲是enable的。而,读缓冲是 disable 的。
void bufferevent_enable(struct bufferevent *bufev,short events);启用缓冲区
    events :EV_READ、EV_WRITE、EV_READIEV_WRITE
    通常用来启用 bufferevent的read 缓冲。

void bufferevent_disable(struct bufferevent *bufev,short events); 禁用
    events :EV_READ、EV_WRITE、EV_READIEV_WRITE

short bufferevent_get_enabled(struct bufferevent *bufev);、
    获取缓冲区的禁用状态,需要借助 & 来得到。

E.客户端连接服务器

int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *address,int addrlen);
    address、len:类似 connect()中的用法 -- 服务器的地址结构和地址结构的长度
    bev: 封装了客户端cfd的事件对象

server

#include <stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<arpa/inet.h>
#include<event2/bufferevent.h>
#include<event2/event.h>

// 读缓冲区回调
void read_cb(struct bufferevent *bev, void *arg) {
    char buf[1024] = {0};
    bufferevent_read(bev, buf, sizeof(buf));
    printf("client say: %s\n", buf);
    
    char *p = "我是服务器,已经成功收到你发送的数据!";
    
    // 写数据给客户端
    bufferevent_write(bev, p, strlen(p) + 1);
    sleep(1);
}

// 写缓冲区回调
void write_cb(struct bufferevent *bev, void *arg) {
    printf("I'm 服务器,成功写数据给客户端,写缓冲区回调函数被回调...\n");
}

// 事件回调 -- 可用于给一个信号让服务器关闭
void event_cb(struct bufferevent *bev, short events, void *arg) {
    if (events & BEV_EVENT_EOF)
        printf("connection closed\n");
    else if (events & BEV_EVENT_ERROR)
        printf("some other error\n");
    
    bufferevent_free(bev);
    printf("buffevent 资源已经被释放...\n");
}

// 监听器的回调函数
void cb_listener(
                struct evconnlistener* listener,
                evutil_socket_t fd,
                struct sockaddr *addr, int len, void *ptr) 
{    
    printf("connect new client\n");
    struct event_base* base = (struct event_base*)ptr;
    
    // 添加新事件
    struct bufferevent *bev;
    bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
    
    // 给 bufferevent 缓冲区设置回调
    bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);
    
    // 启用 bufferevent 的读缓冲。默认是 disable 的
    bufferevent_enable(bev, EV_READ);
}


int main(int argc, const char* argv[]) {
    // init server
    struct sockaddr_in serv;  // 服务器的地址结构
    
    memset(&serv, 0, sizeof(serv));
    serv.sin_family = AF_INET;
    serv.sin_port = htons(9876);
    serv.sin_addr.s_addr = htonl(INADDR_ANY);
    
    struct event_base* base;
    base = event_base_new();  // 创建事件基础结构
    //创建套接字
    //绑定
    //接收连接请求

    //事件的创建都应该在cb_listener中创建,先有监听器libevent内部才能去处理请求事件和将新来的事件对象添加给base,不能在main中创建,无法向监听器中传事件对象
    //不像event可以用add将事件添加给base,启动循环处理请求 -- 还是有差别的,fifo是PIC机制,不是网络通信
    struct evconnlistener* listener; // 监听器
    listener = evconnlistener_new_bind(base, cb_listener, base,
                                       LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
                                       36, (struct sockaddr*)&serv, sizeof(serv));
        //这一个函数,可完成系统调用socket(), listen(), bind(), accept()的作用,accept返回客户端的套接字,就相当于事件对象的创建
    //这和在listen创建监听器后才能accept获得客户端套接字一个道理,得先有监听器才能处理链接请求,创建客户端对应的事件对象,监听器内部再将新来的事件对象添加到base上
    //还有监听器listener监听已经在base上的事件的请求事件
    //cb_listener函数内核自己调用,监听到事件就会调用,不像epoll自己设定的读请求等回调函数还是需要自己调用的
    
    // 启动循环监听
    event_base_dispatch(base);
    evconnlistener_free(listener);
    event_base_free(base);
    return 0;
}

client

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <arpa/inet.h>
#include <event2/bufferevent.h>
#include <event2/event.h>

// 读缓冲区回调
void read_cb(struct bufferevent *bev, void *arg) {
    char buf[1024] = {0};
    bufferevent_read(bev, buf, sizeof(buf));
    
    printf("server say: %s\n", buf);
    
    char *p = "客户端收到服务器数据!";
    
    bufferevent_write(bev, p, strlen(p) + 1);
    sleep(1);
}

// 写缓冲区回调
void write_cb(struct bufferevent *bev, void *arg) {
    printf("------我是客户端的写回调函数\n");
}

// 事件回调
void event_cb(struct bufferevent *bev, short events, void *arg) {
    if (events & BEV_EVENT_EOF)
        printf("connection closed\n");
    else if (events & BEV_EVENT_ERROR)
        printf("some other error\n");
    else if (events & BEV_EVENT_CONNECTED) {
        printf("已经连接服务器.....\n");
        return;
    }

    bufferevent_free(bev);
}

// 客户端与用户交互,从终端读取数据写给服务器
void read_terminal(evutil_socket_t fd, short what, void *arg) {
    // 读数据
    char buf[1024] = {0};
    int len = read(fd, buf, sizeof(buf));
    
    struct bufferevent* bev = (struct bufferevent*)arg;
    
    // 发送数据
    bufferevent_write(bev, buf, len + 1);
}

int main(int argc, const char* argv[]) {
    struct event_base *base = NULL;
    base = event_base_new();
    
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    
    // 通信的 fd 放到 bufferevent 中
    struct bufferevent *bev = NULL;
    bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
    
    // 初始化 server 信息
    struct sockaddr_in serv; // server 的地址结构
    memset(&serv, 0, sizeof(serv));
    serv.sin_family = AF_INET;
    serv.sin_port = htons(9876);
    inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);

    // 连接服务器
    bufferevent_socket_connect(bev, (struct sockaddr*)&serv, sizeof(serv));
    
    // 设置回调
    bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);
    
    // 启用 bufferevent 的读回调
    bufferevent_enable(bev, EV_READ);
    
    // 创建事件
    struct event *ev = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST, read_terminal, bev);
    // 将事件 ev 添加到指定的 base 中
    event_add(ev, NULL);
    
    event_base_dispatch(base); // 循环监听调用回调事件对象 ev 中的 read_terminal 处理请求
    event_free(ev);
    event_base_free(base);
    
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值