【Linux C编程】第十九章 libevent(二)

举报
Yuchuan 发表于 2021/05/04 08:20:48 2021/05/04
【摘要】 使用 libevent 函数之前需要分配一个或者多个 event_base 结构体。每个event_base 结构体持有一个事件集合,可以检测以确定哪个事件是激活的。

一、事件处理框架(event_base)

1.  event_base

     使用 libevent 函数之前需要分配一个或者多个 event_base 结构体。每个event_base 结构体持有一个事件集合,可以检测以确定哪个事件是激活的。

  • 相当于epoll红黑树的树根

  • 抽象层, 完成对event_base的封装
  • 每个 event_base 都有一种用于检测哪种事件已经就绪的 ”方法“,或者说后端。

2.  相关函数

(1)创建event_base

struct event_base* event_base_new(void);
失败返回NULL

(2)释放event_base

event_base_free(struct event_base* base);

(3)循环监听base对应的事件, 等待条件满足

event_base_dispatch();

(4)查看event_base封装的后端

const char **event_get_supported_methods(void);
char* str[];
const char * event_base_get_method(
    const struct event_base *base
);

注意:event_base和fork

  • 子进程创建成功之后, 父进程可以继续使用event_base
  • 子进程中需要继续使用event_base需要重新进程初始化
int event_reinit(struct event_base* base);

二、事件循环(event_loop) 

1. 事件处理方式

    一旦有了一个已经注册了某些事件的 event_base, 就需要让 libevent 等待事件并且通知事件的发生。

#define EVLOOP_ONCE                  0x01
    事件只会被触发一次
    事件没有被触发, 阻塞等
#define EVLOOP_NONBLOCK              0x02
    非阻塞 等方式去做事件检测
    不关心事件是否被触发了
#define EVLOOP_NO_EXIT_ON_EMPTY      0x04
    没有事件的时候, 也不退出轮询检测

2. 相关函数

 (1)int event_base_loop(struct event_base *base, int flags);
          正常退出返回0, 失败返回-1
 (2)int event_base_dispatch(struct event_base* base);

  • 等同于没有设置标志的 event_base_loop ( )
  • 将一直运行,直到没有已经注册的事件了,或者调用 了event_base_loopbreak()或者 event_base_loopexit()为止。

3. 循环停止

(1)如果 event_base 当前正在执行激活事件的回调 ,它将在执行完当前正在处理的事件后立即退出

int event_base_loopexit(
    struct event_base *base,
    const struct timeval *tv
);

(2)让event_base 立即退出循环

int event_base_loopbreak(struct event_base *base);

返回值: 成功 0, 失败 -1

    其中:

struct timeval {
    long tv_sec; 
    long tv_usec; 
};

三、事件创建 (event) 

1. 创建新事件

#define  EV_TIMEOUT              0x01    // 废弃
#define  EV_READ                 0x02
#define  EV_WRITE                0x04
#define  EV_SIGNAL               0x08
#define  EV_PERSIST              0x10    // 持续触发
#define  EV_ET                   0x20    // 边沿模式

typedef void (*event_callback_fn)(evutil_socket_t, short, void *); 
struct event *event_new(
    struct event_base *base, 
    evutil_socket_t fd, // 文件描述符 - int
    short what, 
    event_callback_fn cb, // 事件的处理动作
    void *arg
);

2. 释放事件

void event_free(struct event *event);

3. 设置未决事件

     构造事件之后,在将其添加到 event_base 之前实际上是不能对其做任何操作的。使用event_add()将事件添加到event_base, 非未决事件 -> 未决事件。

int event_add(
    struct event *ev, 
    const struct timeval *tv
); 

参数:

     ev:创建的事件

 tv: 
   NULL: 事件被触发, 对应的回调被调用
   tv = {0, 100}, 如果设置的时间,在改时间段内检测的事件没被触发, 时间到达之后, 回调函数还是会被调用

返回值:

    函数调用成功返回 0,失败返回 -1 。

 4. 设置非未决

int event_del(struct event *ev); 

对已经初始化的事件调用 event_del() 将使其成为非未决和非激活的。如果事件不是未决的或者激活的,调用将没有效果。

返回值:

           成功时函数返回 0,失败时返回-1。

5. 事件的状态转换

6. 示例

    通过 libevent 实现进程间通过管道来通信:

write_fifo.c

1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <stdlib.h>
 4 #include <sys/types.h>
 5 #include <sys/stat.h>
 6 #include <string.h>
 7 #include <fcntl.h>
 8 #include <event2/event.h>
 9 
10 // 对操作处理函数
11 void write_cb(evutil_socket_t fd, short what, void *arg)
12 {
13     // write管道
14     char buf[1024] = {0};
15     static int num = 0;
16     sprintf(buf, "hello, world == %d\n", num++);
17     write(fd, buf, strlen(buf)+1);
18 }
19 
20 
21 // 写管道
22 int main(int argc, const char* argv[])
23 {
24     // open file
25     int fd = open("myfifo", O_WRONLY | O_NONBLOCK);
26     if(fd == -1)
27     {
28         perror("open error");
29         exit(1);
30     }
31 
32     // 写管道
33     struct event_base* base = NULL;
34     base = event_base_new();
35 
36     // 创建事件
37     struct event* ev = NULL;
38     // 检测的写缓冲区是否有空间写
39     ev = event_new(base, fd, EV_WRITE | EV_PERSIST, write_cb, NULL);
40 
41     // 添加事件
42     event_add(ev, NULL);
43 
44     // 事件循环
45     event_base_dispatch(base);
46 
47     // 释放资源
48     event_free(ev);
49     event_base_free(base);
50     close(fd);
51     
52     return 0;
53 }

read_fifo.c

1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <stdlib.h>
 4 #include <sys/types.h>
 5 #include <sys/stat.h>
 6 #include <string.h>
 7 #include <fcntl.h>
 8 #include <event2/event.h>
 9 
10 // 对操作处理函数
11 void read_cb(evutil_socket_t fd, short what, void *arg)
12 {
13     // 读管道
14     char buf[1024] = {0};
15     int len = read(fd, buf, sizeof(buf));
16     printf("data len = %d, buf = %s\n", len, buf);
17     printf("read event: %s", what & EV_READ ? "Yes" : "No");
18 }
19 
20 
21 // 读管道
22 int main(int argc, const char* argv[])
23 {
24     unlink("myfifo");
25     //创建有名管道
26     mkfifo("myfifo", 0664);
27 
28     // open file
29     int fd = open("myfifo", O_RDONLY | O_NONBLOCK);
30     if(fd == -1)
31     {
32         perror("open error");
33         exit(1);
34     }
35 
36     // 读管道
37     struct event_base* base = NULL;
38     base = event_base_new();
39 
40     // 创建事件
41     struct event* ev = NULL;
42     ev = event_new(base, fd, EV_READ | EV_PERSIST, read_cb, NULL);
43 
44     // 添加事件
45     event_add(ev, NULL);
46 
47     // 事件循环
48     event_base_dispatch(base);
49 
50     // 释放资源
51     event_free(ev);
52     event_base_free(base);
53     close(fd);
54     
55     return 0;
56 }

注意:在执行上述代码时可能会出现下面错误

[root@centos1 event]# ./read
./read: error while loading shared libraries: libevent-2.1.so.6: cannot open shared object file: No such file or directory

    查找 libevent-2.1.so.6 该动态库文件:

[root@centos1 event]# locate libevent-2.1.so.6
/home/linuxC/libevent/libevent-2.1.8-stable/.libs/libevent-2.1.so.6
/home/linuxC/libevent/libevent-2.1.8-stable/.libs/libevent-2.1.so.6.0.2
/usr/local/lib/libevent-2.1.so.6
/usr/local/lib/libevent-2.1.so.6.0.2

    将红色动态库路径添加到 /etc/ld.so.conf文件中,然后执行 ldconfig -v 即可:

include ld.so.conf.d/*.conf
/home/xuejiale/src/calc/lib/
/usr/local/lib/

四、数据缓冲区(Bufferevent)

1. bufferevent 理解

  • 是libevent为IO缓冲区操作提供的一种通用机制
  • bufferevent 由一个底层的传输端口(如套接字 ),一个读取缓冲区和一个写入缓冲区组成。
  • 与通常的事件在底层传输端口已经就绪,可以读取或者写入的时候执行回调不同的是,bufferevent 在读取或者写入了足够量的数据之后调用用户提供的回调。

2. 回调 - 缓冲区对应的操作

(1)每个 bufferevent 有两个数据相关的回调

  • 一个读取回调

          从底层传输端口读取了任意量的数据之后会调用读取回调(默认)

  • 一个写入回调

         输出缓冲区中足够量的数据被清空到底层传输端口后写入回调会被调用(默认)

(2)缓冲区数据存储方式      

      缓冲去内部数据存储 - 队列

(3)event 和 bufferevent 

      event --> 没有缓冲区

      bufferevent --> 有缓冲区

 3. 使用 bufferevent

 (1)创建基于套接字的bufferevent

    可以使用 bufferevent_socket_new()创建基于套接字的 bufferevent

struct bufferevent *bufferevent_socket_new(
    struct event_base *base,
    evutil_socket_t fd,
    enum bufferevent_options options
);
参数:
    options: BEV_OPT_CLOSE_ON_FREE 
    释放 bufferevent 时关闭底层传输端口。这将关闭底层套接字,释放底层 bufferevent 等
    libevent中文参考手册page53 - bufferevent的选项标志
    struct bufferevent也是一个 event
    成功时函数返回一个 bufferevent,失败则返回 NULL。

(2)在bufferevent上启动链接

int bufferevent_socket_connect(
    struct bufferevent *bev,
    struct sockaddr *address, //server ip和port 
    int addrlen
); 
1)address 和 addrlen 参数跟标准调用connect()的参数相同。如果还没有为bufferevent 设置套接字,调用函数将为其分配一个新的流套接字,并且设置为非阻塞的
2)如果已经为 bufferevent 设置套接字,调用bufferevent_socket_connect() 将告知libevent 套接字还未连接,直到连接成功之前不应该对其进行读取或者写入操作。
3)连接完成之前可以向输出缓冲区添加数据。

(3)释放bufferevent操作

void bufferevent_free(struct bufferevent *bev); 
这个函数释放 bufferevent

(4)bufferevent读写缓冲区回调操作

typedef void (*bufferevent_data_cb)(
    struct bufferevent *bev, 
    void *ctx
);
typedef void (*bufferevent_event_cb)(
    struct bufferevent *bev,
    short events, 
    void *ctx
);
events参数:
    EV_EVENT_READING:读取操作时发生某事件,具体是哪种事件请看其他标志。
    BEV_EVENT_WRITING:写入操作时发生某事件,具体是哪种事件请看其他标志。
    BEV_EVENT_ERROR:操作时发生错误。关于错误的更多信息,请调 用
                            EVUTIL_SOCKET_ERROR()。
    BEV_EVENT_TIMEOUT:发生超时。
    BEV_EVENT_EOF:遇到文件结束指示。
    BEV_EVENT_CONNECTED:请求的连接过程已经完成(实现客户端的时候可以判断)

void bufferevent_setcb(
    struct bufferevent *bufev,
    bufferevent_data_cb readcb, //在读回调中读数据,bufferevent_read()
    bufferevent_data_cb writecb, //可以是NULL
    bufferevent_event_cb eventcb, //可以是NULL
    void *cbarg
);

(5)禁用、启用缓冲区

禁用之后, 对应的回调就不会被调用了
void bufferevent_enable(
    struct bufferevent *bufev, 
    short events
); 
void bufferevent_disable(
    struct bufferevent *bufev, 
    short events
); 
short bufferevent_get_enabled(
    struct bufferevent *bufev
); 
可以启用或者禁用 bufferevent 上的 EV_READ、EV_WRITE 或者 EV_READ | EV_WRITE 事件。没有启用读取或者写入事件时, bufferevent 将不会试图进行数据读取或者写入。

(6)操作bufferevent中的数据

    1)向bufferevent的输出缓冲区添加数据

int bufferevent_write(
    struct bufferevent *bufev,
    const void *data, 
    size_t size
);

    2)从bufferevent的输入缓冲区移除数据

size_t bufferevent_read(
    struct bufferevent *bufev, 
    void *data, 
    size_t size
);

4. 链接监听器(evconnlistener)

 (1)创建和释放监听器

   1)创建监听器

typedef void (*evconnlistener_cb)(
    struct evconnlistener *listener,   
    evutil_socket_t sock,  //用于通信的文件描述符
    struct sockaddr *addr, //客户端的IP和端口信息
    int len, 
    void *ptr              //外部传进来的数据
); 

struct evconnlistener * evconnlistener_new(
    struct event_base *base,
    evconnlistener_cb cb, 
    void *ptr, 
    unsigned flags, 
    int backlog,
    evutil_socket_t fd
);
参数flags:
    参考手册 - page99-100 [可识别的标志]
    LEV_OPT_CLOSE_ON_FREE
    LEV_OPT_REUSEABLE
    
struct evconnlistener *evconnlistener_new_bind(
    struct event_base *base,
    evconnlistener_cb cb,   // 接受连接之后, 用户要做的操作
    void *ptr,              // 给回调传参
    unsigned flags, 
    int backlog,           //-1: 使用默认的最大值
    const struct sockaddr *sa, //服务器的IP和端口信息
    int socklen
);

    两个 evconnlistener_new*()函数都分配和返回一个新的连接监听器对象。连接监听器使 用 event_base 来得知什么时候在给定的监听套接字上有新的 TCP 连接。新连接到达时,监听 器调用你给出的回调函数。

    evconnlistener_new_bind 函数内部完成的是下面5步:

    a. socket - server
    b. 创建监听socket
    c. 绑定
    d. 监听
    f. 等待并接收连接

    2)释放监听器

void evconnlistener_free(struct evconnlistener *lev); 

(2)启用和禁用 evconnlistener

int evconnlistener_disable(struct evconnlistener *lev);
int evconnlistener_enable(struct evconnlistener *lev);

这两个函数暂时禁止或者重新允许监听新连接。

 (3)调整 evconnlistener 的回调函数

void evconnlistener_set_cb(
    struct evconnlistener *lev,
    evconnlistener_cb cb, 
    void *arg
); 

函数调整 evconnlistener 的回调函数和其参数。

 5. 示例

    实现服务器和客户端:

server.c

1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <stdlib.h>
 4 #include <sys/types.h>
 5 #include <sys/stat.h>
 6 #include <string.h>
 7 #include <event2/event.h>
 8 #include <event2/listener.h>
 9 #include <event2/bufferevent.h>
10 
11 // 读缓冲区回调
12 void read_cb(struct bufferevent *bev, void *arg)
13 {
14     char buf[1024] = {0};   
15     bufferevent_read(bev, buf, sizeof(buf));
16     char* p = "我已经收到了你发送的数据!";
17     printf("client say: %s\n", p);
18 
19     // 发数据给客户端
20     bufferevent_write(bev, p, strlen(p)+1);
21     printf("====== send buf: %s\n", p);
22 }
23 
24 // 写缓冲区回调
25 void write_cb(struct bufferevent *bev, void *arg)
26 {
27     printf("我是写缓冲区的回调函数...\n"); 
28 }
29 
30 // 事件
31 void event_cb(struct bufferevent *bev, short events, void *arg)
32 {
33     if (events & BEV_EVENT_EOF)
34     {
35         printf("connection closed\n");  
36     }
37     else if(events & BEV_EVENT_ERROR)   
38     {
39         printf("some other error\n");
40     }
41     
42     bufferevent_free(bev);    
43     printf("buffevent 资源已经被释放...\n"); 
44 }
45 
46 
47 
48 void cb_listener(
49         struct evconnlistener *listener, 
50         evutil_socket_t fd, 
51         struct sockaddr *addr, 
52         int len, void *ptr)
53 {
54    printf("connect new client\n");
55 
56    struct event_base* base = (struct event_base*)ptr;
57    // 通信操作
58    // 添加新事件
59    struct bufferevent *bev;
60    bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
61 
62    // 给bufferevent缓冲区设置回调
63    bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);
64    bufferevent_enable(bev, EV_READ);  //默认写enable,读disenable
65 }
66 
67 
68 int main(int argc, const char* argv[])
69 {
70 
71     // init server 
72     struct sockaddr_in serv;
73     memset(&serv, 0, sizeof(serv));
74     serv.sin_family = AF_INET;
75     serv.sin_port = htons(9876);
76     serv.sin_addr.s_addr = htonl(INADDR_ANY);
77 
78     struct event_base* base;
79     base = event_base_new();
80     // 创建套接字
81     // 绑定
82     // 接收连接请求
83     struct evconnlistener* listener;
84     //第二个base传给了cb_listener的ptr(base --> ptr)
85     listener = evconnlistener_new_bind(base, cb_listener, base, 
86                                   LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 
87                                   36, (struct sockaddr*)&serv, sizeof(serv));
88 
89     event_base_dispatch(base);
90 
91     evconnlistener_free(listener);
92     event_base_free(base);
93 
94     return 0;
95 }

client.c

1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <stdlib.h>
 4 #include <sys/types.h>
 5 #include <sys/stat.h>
 6 #include <string.h>
 7 #include <event2/event.h>
 8 #include <event2/bufferevent.h>
 9 
10 
11 void read_cb(struct bufferevent *bev, void *arg)
12 {
13     char buf[1024] = {0}; 
14     bufferevent_read(bev, buf, sizeof(buf));
15     printf("Server say: %s\n", buf);
16 }
17 
18 void write_cb(struct bufferevent *bev, void *arg)
19 {
20     printf("I am Write_cb function....\n");
21 }
22 
23 void event_cb(struct bufferevent *bev, short events, void *arg)
24 {
25     if (events & BEV_EVENT_EOF)
26     {
27         printf("connection closed\n");  
28     }
29     else if(events & BEV_EVENT_ERROR)   
30     {
31         printf("some other error\n");
32     }
33     else if(events & BEV_EVENT_CONNECTED)
34     {
35         printf("成功连接到服务器, O(∩_∩)O哈哈~\n");
36         return;
37     }
38     
39     bufferevent_free(bev);
40     printf("free bufferevent...\n");
41 }
42 
43 void send_cb(evutil_socket_t fd, short what, void *arg)
44 {
45     char buf[1024] = {0}; 
46     struct bufferevent* bev = (struct bufferevent*)arg;
47     printf("请输入要发送的数据: \n");
48     //注意这块的fd --> STDIN_FILENO,读终端输入中的数据到buf
49     read(fd, buf, sizeof(buf));
50     bufferevent_write(bev, buf, strlen(buf)+1);
51 }
52 
53 
54 int main(int argc, const char* argv[])
55 {
56     struct event_base* base;
57     base = event_base_new();
58 
59 
60     struct bufferevent* bev;
61     bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
62 
63     // 连接服务器
64     struct sockaddr_in serv;
65     memset(&serv, 0, sizeof(serv));
66     serv.sin_family = AF_INET;
67     serv.sin_port = htons(9876);
68     evutil_inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
69     bufferevent_socket_connect(bev, (struct sockaddr*)&serv, sizeof(serv));
70 
71     // 设置回调
72     bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);
73     bufferevent_enable(bev, EV_READ | EV_PERSIST);
74 
75     // 创建一个事件
76     struct event* ev = event_new(base, STDIN_FILENO, 
77                                  EV_READ | EV_PERSIST, 
78                                  send_cb, bev);
79     event_add(ev, NULL);
80     
81     event_base_dispatch(base);
82 
83     event_base_free(base);
84 
85     return 0;
86 }

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

举报
请填写举报理由
0/200