Libevent應用 (二) 與事件一起工作
libevent的基本操作單元是事件。每個事件代表一組條件的集合,這些條件包括:
(1)檔案描述符已經就緒,可以讀取或者寫入
(2)檔案描述符變為就緒狀態,可以讀取或者寫入(僅對於邊沿觸發IO)
(3)超時事件
(4)發生某訊號
(5)使用者觸發事件
所有事件具有相似的生命週期。呼叫libevent函式設定事件並且關聯到event_base之後,事件進入“已初始化(initialized)”狀態。此時可以將事件新增到event_base中,這使之進入“未決(pending)”狀態。
在未決狀態下,如果觸發事件的條件發生(比如說,檔案描述符的狀態改變,或者超時時間到達),則事件進入“啟用(active)”狀態,(使用者提供的)事件回撥函式將被執行。
如果配置為“持久的(persistent)”,事件將保持為未決狀態。否則,執行完回撥後,事件不再是未決的。刪除操作可以讓未決事件成為非未決(已初始化)的;新增操作可以讓非未決事件再次成為未決的。
2.1 建立事件
使用event_new()介面建立事件。
struct event *event_new(struct event_base *base, evutil_socket_t fd, short what, event_callback_fn cb, void *arg); /*這個標誌表示某超時時間流逝後事件成為啟用的。超時發生時,回撥函式的what引數將帶有這個標誌。*/ /*表示指定的檔案描述符已經就緒,可以讀取的時候,事件將成為啟用的。*/ /*表示指定的檔案描述符已經就緒,可以寫入的時候,事件將成為啟用的。*/ //用於實現訊號檢測 //表示事件是“持久的” //表示如果底層的event_base後端支援邊沿觸發事件,則事件應該是邊沿觸發的。這個標誌影響EV_READ和EV_WRITE的語義。 typedef void (*event_callback_fn)(evutil_socket_t fd, short what, void * arg); void event_free(struct event *event);
event_new()試圖分配和構造一個用於base的新事件。what引數是上述標誌的集合。如果fd非負,則它是將被觀察其讀寫事件的檔案。事件被啟用時,libevent將呼叫cb函式,傳遞這些引數:檔案描述符fd,表示所有被觸發事件的位欄位,以及構造事件時的arg引數。
發生內部錯誤,或者傳入無效引數時,event_new()將返回NULL。
要釋放事件,呼叫event_free()。
使用event_assign二次修改event的相關引數:
int event_assign(struct event *event, struct event_base *base, evutil_socket_t fd, short what, void (*callback)(evutil_socket_t, short, void *), void *arg);
除了event引數必須指向一個未初始化的事件之外,event_assign()的引數與event_new()的引數相同。成功時函式返回0,如果發生內部錯誤或者使用錯誤的引數,函式返回-1。
警告:
不要對已經在event_base中未決的事件呼叫event_assign(),這可能會導致難以診斷的錯誤。如果已經初始化和成為未決的,呼叫event_assign()之前需要呼叫event_del()。
2.2 讓事件未決和非未決
2.2.1 讓事件未決
所有新建立的事件都處於已初始化和非未決狀態,呼叫event_add()可以使其成為未決的。
int event_add(struct event *ev, const struct timeval *tv);
在非未決的事件上呼叫event_add()將使其在配置的event_base中成為未決的。成功時函式返回0,失敗時返回-1。如果tv為NULL,新增的事件不會超時。否則,tv以秒和微秒指定超時值。
如果對已經未決的事件呼叫event_add(),事件將保持未決狀態,並在指定的超時時間被重新排程。
2.2.2 讓事件非未決
int event_del(struct event *ev);
對已經初始化的事件呼叫event_del()將使其成為非未決和非啟用的。如果事件不是未決的或者啟用的,呼叫將沒有效果。成功時函式返回0,失敗時返回-1。
2.3 設定事件的優先順序
多個事件同時觸發時,libevent沒有定義各個回撥的執行次序。可以使用優先順序來定義某些事件比其他事件更重要。
int event_priority_set(struct event *event, int priority);
示例程式碼:
void read_cb(evutil_socket_t, short, void *); void write_cb(evutil_socket_t, short, void *); void main_loop(evutil_socket_t fd) { struct event *important, *unimportant; struct event_base *base; base = event_base_new(); event_base_priority_init(base, 2); important = event_new(base, fd, EV_WRITE|EV_PERSIST, write_cb, NULL); unimportant = event_new(base, fd, EV_READ|EV_PERSIST, read_cb, NULL); event_priority_set(important, 0); event_priority_set(unimportant, 1); }
2.4 關於事件永續性
預設情況下,每當未決事件成為啟用的(因為fd已經準備好讀取或者寫入,或者因為超時),事件將在其回撥被執行前成為非未決的。
如果想讓事件再次成為未決的,可以在回撥函式中再次對其呼叫event_add()。然而,如果設定了EV_PERSIST標誌,事件就是持久的。這意味著即使其回撥被啟用,事件還是會保持為未決狀態。如果想在回撥中讓事件成為非未決的,可以對其呼叫event_del()。
每次執行事件回撥的時候,持久事件的超時值會被複位。因此,如果具有EV_READ|EV_PERSIST標誌,以及5秒的超時值,則事件將在以下情況下成為啟用的:
1. 套接字已經準備好被讀取的時候
2. 從最後一次成為啟用的開始,已經逝去5秒
2.5 事件迴圈
一旦有了一個已經註冊了某些事件的event_base,就需要讓libevent等待事件並且通知事件的發生。
2.5.1 啟動事件迴圈
int event_base_loop(struct event_base *base, int flags); int event_base_dispatch(struct event_base *base);
預設情況下,event_base_loop()函式執行event_base直到其中沒有已經註冊的事件為止。
執行迴圈的時候,函式重複地檢查是否有任何已經註冊的事件被觸發(比如說,讀事件的檔案描述符已經就緒,可以讀取了;或者超時事件的超時時間即將到達)。如果有事件被觸發,函式標記被觸發的事件為“啟用的”,並且執行這些事件。
在flags引數中設定一個或者多個標誌就可以改變event_base_loop()的行為。如果設定了EVLOOP_ONCE,迴圈將等待某些事件成為啟用的,執行啟用的事件直到沒有更多的事件可以執行,然後返回。
如果設定了EVLOOP_NONBLOCK,迴圈不會等待事件被觸發:迴圈將僅僅檢測是否有事件已經就緒,可以立即觸發,如果有,則執行事件的回撥。
完成工作後,如果正常退出,event_base_loop()返回0;如果因為後端中的某些未處理錯誤而退出,則返回-1。
event_base_dispatch()等同於沒有設定標誌的event_base_loop()。
示例程式碼:
void cb_func(evutil_socket_t fd, short what, void *arg) { const char *data = arg; printf("Got an event on socket %d:%s%s [%s]\n", (int) fd, (what&EV_TIMEOUT) ? " timeout" : "", (what&EV_READ) ? " read" : "", data); } void main_loop(evutil_socket_t fd) { struct event *ev; struct timeval five_seconds = {5,0}; struct event_base *base = event_base_new(); ev = event_new(base, fd, EV_TIMEOUT|EV_READ|EV_PERSIST, cb_func, (char*)"Reading event"); event_add(ev, &five_seconds); event_base_dispatch(base); }
2.5.2 停止事件迴圈
如果想在移除所有已註冊的事件之前停止活動的事件迴圈,可以呼叫兩個稍有不同的函式。
int event_base_loopexit(struct event_base *base, const struct timeval *tv); int event_base_loopbreak(struct event_base *base);
event_base_loopexit()讓event_base在給定時間之後停止迴圈。如果tv引數為NULL,event_base會立即停止迴圈,沒有延時。如果event_base當前正在執行任何啟用事件的回撥,則回撥會繼續執行,直到執行完所有啟用事件的回撥之後才退出。
event_base_loopbreak()讓event_base立即退出迴圈。它與event_base_loopexit(base,NULL)的不同在於,如果event_base當前正在執行啟用事件的回撥,它將在執行完當前正在處理的事件後立即退出。
注意event_base_loopexit(base,NULL)和event_base_loopbreak(base)在事件迴圈沒有執行時的行為不同:前者安排下一次事件迴圈在下一輪迴調完成後立即停止(就好像帶EVLOOP_ONCE標誌呼叫一樣);後者卻僅僅停止當前正在執行的迴圈,如果事件迴圈沒有執行,則沒有任何效果。
這兩個函式都在成功時返回0,失敗時返回-1。
示例:立即關閉
void cb(int sock, short what, void *arg) { struct event_base *base = arg; event_base_loopbreak(base); } void main_loop(struct event_base *base, evutil_socket_t watchdog_fd) { struct event *watchdog_event; watchdog_event = event_new(base, watchdog_fd, EV_READ, cb, base); event_add(watchdog_event, NULL); event_base_dispatch(base); }
示例: 執行事件迴圈10秒,然後退出
void run_base_with_ticks(struct event_base *base) { struct timeval ten_sec; ten_sec.tv_sec = 10; ten_sec.tv_usec = 0; event_base_loopexit(base, &ten_sec); event_base_dispatch(base); puts("Tick"); }
2.5.3 檢查迴圈是否退出
有時候需要知道對event_base_dispatch()或者event_base_loop()的呼叫是正常退出的,還是因為呼叫event_base_loopexit()或者event_base_break()而退出的。可以呼叫下述函式來確定是否呼叫了loopexit或者break函式。
int event_base_got_exit(struct event_base *base); int event_base_got_break(struct event_base *base);
這兩個函式分別會在迴圈是因為呼叫event_base_loopexit()或者event_base_break()而退出的時候返回true,否則返回false。下次啟動事件迴圈的時候,這些值會被重設。
2.6 配置一次觸發事件
如果不需要多次新增一個事件,或者要在新增後立即刪除事件,而事件又不需要是持久的,則可以使用event_base_once()。
int event_base_once(struct event_base *, evutil_socket_t, short, void (*)(evutil_socket_t, short, void *), void *, const struct timeval *);
除了不支援EV_SIGNAL或者EV_PERSIST之外,這個函式的介面與event_new()相同。安排的事件將以預設的優先順序加入到event_base並執行。回撥被執行後,libevent內部將會釋放event結構。成功時函式返回0,失敗時返回-1。
2.7 檢查事件狀態
有時候需要了解事件是否已經新增,檢查事件代表什麼。
/*event_get_fd()返回為事件配置的檔案描述符*/ evutil_socket_t event_get_fd(const struct event *ev); /*event_get_base()返回為事件配置的event_base。*/ struct event_base *event_get_base(const struct event *ev); /*event_get_events()返回事件的標誌(EV_READ、EV_WRITE等)。*/ short event_get_events(const struct event *ev); /*event_get_callback()和event_get_callback_arg()返回事件的回撥函式及其引數指標。*/ event_callback_fn event_get_callback(const struct event *ev); void *event_get_callback_arg(const struct event *ev); /*獲取事件的優先順序*/ int event_get_priority(const struct event *ev);
2.8 tcp示例程式碼
伺服器端:tcp_server.c
/************************************************************************* # File Name: tcp_server.c # Author: wenong # mail: huangwenlong@520it.com # Created Time: 2016年09月03日 星期六 21時51分08秒 ************************************************************************/ void read_cb(evutil_socket_t clientfd, short what, void* arg) { int i, recvlen; char buf[MAXBYTES]; struct event* ev = (struct event*)arg; printf("read_cd clientfd %d\n", clientfd); recvlen = read(clientfd, buf, sizeof(buf)); if(recvlen <= 0) { puts("the other size close or error occur"); event_del(ev); event_free(ev); close(clientfd); } for(i = 0; i < recvlen; i++) { buf[i] = toupper(buf[i]); } write(clientfd, buf, recvlen); } void accept_cb(evutil_socket_t serverfd, short what, void * arg) { struct sockaddr_in clientaddr; struct event* ev; int clientaddrlen; int clientfd; puts("Accept client connect"); clientaddrlen = sizeof(clientaddr); bzero((void*)&clientaddr, sizeof(clientaddr)); clientfd = accept(serverfd, (struct sockaddr*)&clientaddr, &clientaddrlen); printf("recv clientfd %d\n", clientfd); ev = event_new((struct event_base*)arg, clientfd, EV_READ | EV_PERSIST, NULL, NULL); event_assign(ev, (struct event_base*)arg, clientfd, EV_READ | EV_PERSIST, (event_callback_fn)read_cb, (void*)ev); event_add(ev, NULL); } void main_loop(evutil_socket_t fd) { struct event_base * base; struct event* ev; base = event_base_new(); ev = event_new(base, fd, EV_READ | EV_PERSIST, (event_callback_fn)accept_cb, (void*)base); event_add(ev, NULL); puts("server begin listenning..."); event_base_dispatch(base); event_free(ev); event_base_free(base); } int main(int argc, char** argv) { int serverfd; socklen_t serveraddrlen; struct sockaddr_in serveraddr; serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(SERVERPORT); serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); serverfd = socket(AF_INET, SOCK_STREAM, 0); serveraddrlen = sizeof(serveraddr); bind(serverfd, (struct sockaddr*)&serveraddr, serveraddrlen); listen(serverfd, 128); main_loop(serverfd); close(serverfd); return 0; }
客戶端: tcp_client.c
/************************************************************************* # File Name: tcp_client.c # Author: wenong # mail: huangwenlong@520it.com # Created Time: 2016年09月03日 星期六 22時10分11秒 ************************************************************************/ void read_cb(evutil_socket_t clientfd, short what, void* arg) { puts("read clientfd"); char buf[MAXBYTES]; int ret; ret = read(clientfd, buf, sizeof(buf)); if(ret <= 0) { close(clientfd); event_base_loopexit((struct event_base*)arg, NULL); } write(STDOUT_FILENO, buf, ret); } void cmd_msg_cb(evutil_socket_t stdinfd, short what, void* arg) { int ret; int clientfd = (int)arg; char buf[MAXBYTES]; puts("get msg from stdin"); ret = read(stdinfd, buf, sizeof(buf)); write(clientfd, buf, ret); } void main_loop(int clientfd) { struct event_base* base; struct event* ev_socket, *ev_stdin; base = event_base_new(); ev_stdin = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST , (event_callback_fn)cmd_msg_cb, (void*)clientfd); ev_socket = event_new(base, clientfd, EV_READ | EV_PERSIST , (event_callback_fn)read_cb, (void*)base); event_add(ev_socket, NULL); event_add(ev_stdin, NULL); event_base_dispatch(base); puts("event free and exit"); event_free(ev_socket); event_free(ev_stdin); event_base_free(base); } int main(int argc, char** argv) { int clientfd; struct sockaddr_in serveraddr; serveraddr.sin_family = AF_INET; inet_pton(AF_INET, SERVERIP, &serveraddr.sin_addr.s_addr); serveraddr.sin_port = htons(SERVERPORT); clientfd = socket(AF_INET, SOCK_STREAM, 0); connect(clientfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)); main_loop(clientfd); return 0; }
相關文章
- Libevent應用 (零) Libevent簡單介紹與安裝
- Libevent應用 (一) 建立event_base
- Libevent應用 (三) 資料緩衝
- Libevent應用(六)從bufferevent中取出evbuffer
- Libevent應用 (四) 輔助型別和函式型別函式
- 委託與事件-事件詳解(二)事件
- Libevent應用 (五) 連線監聽器,接收tcp連線TCP
- 與NewBing一起寫作:《Web應用安全入門》Web
- Libevent教程001: 簡介與配置
- 由Spring應用的瑕疵談談DDD的概念與應用(二)Spring
- Laravel 模型事件的應用Laravel模型事件
- 玩轉 PHP 網路程式設計全套之 libevent 框架多人聊天應用PHP程式設計框架
- 我們一起來學RabbitMQ 二:RabbiMQ 的 6 種模式的基本應用MQ模式
- AI助手:Agent工作流程與應用場景詳解AI
- libevent之event
- libevent之bufferevents
- libevent之evbuffer
- libevent之evconnlistener
- iOS GestureRecognizer與UIResponder touch事件響應iOSUI事件
- Spring Boot應用程式事件教程 - reflectoringSpring Boot事件
- 【Linux系統程式設計】libevent庫bufferevent與evconnlistenerLinux程式設計
- Kubernetes(二) 應用部署
- 我跟你說@RefreshScope跟Spring事件監聽一起用有坑!Spring事件
- 用Unity做個遊戲(二) – 事件系統Unity遊戲事件
- 陪你一起學習之javascript事件JavaScript事件
- 免費OA工作流實際應用中的問與答
- Session工作原理和典型應用分析Session
- [.NET專案實戰] Elsa開源工作流元件應用(二):核心解讀元件
- 和我一起理解js中的事件物件JS事件物件
- libevent入門介紹
- libevent之event_base
- Spring Boot Actuator詳解與深入應用(二):Actuator 2.xSpring Boot
- Google Play 應用與遊戲使用者體驗指南 (二)Go遊戲
- Spring事件,ApplicationEvent在業務中的應用Spring事件APP
- 事件流與事件溯源事件
- 雲原生事件驅動引擎(RocketMQ-EventBridge)應用場景與技術解析事件MQ
- SSH原理與運用(二)
- 全面系統講解CSS 工作應用CSS