Linux C編程之十九(2) libevent


一、事件處理框架(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
); 

 注意:調用event_new()函數之后,新事件處於已初始化和非未決狀態 。 

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 實現進程間通過管道來通信:

 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 }
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 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 }
read_fifo.c

注意:在執行上述代碼時可能會出現下面錯誤

[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. 示例

    實現服務器和客戶端:

 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 }
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/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 }
client.c

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM