您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“Linux高性能I/O框架庫Libevent怎么用”,內容詳細,步驟清晰,細節處理妥當,希望這篇“Linux高性能I/O框架庫Libevent怎么用”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
I/O事件
信號
定時事件
在處理這三類事件時我們通常需要考慮如下三個問題:
統一事件源。很明顯,統一處理這三類事件既能使代碼簡單易懂,又能避免一些潛在的邏輯錯誤。
可移植性。不同的操作系統具有不同的I/O
復用方式,比如Solaris
的dev/poll
文件,FressBSD
的kqueue
機制,Linux
的epoll
系統調用
對并發編程的支持,在多進程和多線程環境下,我們需要考慮各執行實體如何協同處理客戶連接、信號和定時器,以避免競態條件。
幸運的是,開源社區提供了很多優秀的I/O
框架庫,他們不僅解決了上述問題,讓開發者可以將精力完全放在程序的邏輯上,而且穩定性、性能等各方面都相當出色。而Libevent
就是其中相對輕量級的框架庫。
I/O
框架庫以庫函數的形式,封裝了較為底層的系統調用,給應用程序提供了一組更便于使用的接口。這些庫函數往往比程序員自己實現的同樣功能的函數更合理、更高效、且更健壯。因為它們經受住了真實網絡環境下的高壓測試,以及時間的考驗。
各種I/O
框架庫的實現原理基本相似,要么以Reactor
模式實現,要么以Procator
模式實現(高性能服務器程序框架 - 兩種高效的事件處理模式),要么同時以這兩種模式實現。舉例來說,基于Reactor
模式的I/O
框架庫包含如下幾個組件:
句柄Handle
事件多路分發器EventDemultiplexer
事件處理器Eventhandler
具體的事件處理器ConcreteEventHandler
Reactor
句柄: I/O
框架庫要處理的對象,即I/O
事件、信號和定時事件,統一稱為事件源。一個事件源通常和一個句柄綁定在一起。句柄的作用是,當內核檢測到就緒事件時,它將通過句柄來通知應用程序這一事件。在Linux
環境下,I/O
事件對應的句柄是文件描述符,信號事件對應的句柄就是信號值。
事件多路分發器:事件的到來是隨機的、異步的。我們無法預知程序何時收到一個客戶連接請求,又亦活收到一個暫停信號。所以程序需要循環地等待并處理事件,這就是事件循環。在事件循環中,等待事件一般使用I/O
復用技術來實現。I/O
框架庫一般將系統支持的各種I/O
復用系統調用封裝成統一的接口,稱為事件多路分發器。事件多路分發器的demultiplex
方法是等待事件的核心函數,其內部調用的是select
、poll
、epoll_wait
等函數。此外事件多路分發器還需實現register_event
和remove_event
方法,以供調用者往事件多路分發器中添加事件和從事件多路分發器中刪除事件。
事件處理器和具體時間處理器:事件處理器執行事件對應的業務邏輯。它通常包含一個或多個handle_event
回調函數,這些回調函數在事件循環中被執行。I/O
框架庫提供的事件處理器通常是一個接口,用戶需要繼承它來實現自己的事件處理器,即具體事件處理器。因此,事件處理器中的回調函數一般被聲明為需函數,以支持用戶的擴展。此外,事件處理器一般還提供一個get_handle
方法,它返回與該事件處理器關聯的句柄。那么事件處理器和句柄有什么關系?當時間多路分發器檢測到有事件發生時,它是通過句柄來通知應用程序的。因此,我們必須將事件處理器和句柄綁定,才能在事件發生時獲取到正確的事件處理器。
Reactor:Reactor是I/O框架的核心。它提供的幾個主要方法是:
handle_events
:該方法執行事件循環。它重復如下過程:等待事件,然后依次處理所有就緒事件對應的事件處理器。
register_handler
: 該方法調用事件多路分發器的register_event
方法來往事件多路分發器中注冊一個事件。
-remove_handler
:該方法調用事件多路分發器的remove_event
方法來往刪除事件多路分發器中注冊一個事件。
Libevent是開源社區的一款高性能的I/O框架庫,具有如下特點:
跨平臺支持
統一事件源
線程安全
基于Reactor模式的實現
(推薦微課:Linux微課)
下面是用Libevent
庫實現的一個“Hello World”
程序。
#include <event2/event.h> void signal_cb(int fd, short event, void *argc) { struct event_base* base = (event_base*)argc; struct timeval delay = {2, 0}; printf("Caught an interrupt signal; exiting cleanly in two seconds....\n"); event_base_loopexit(base, &delay); } void timeout_cb(int fd, short event, void* argc) { printf("timeout\n"); } int main(int argc, char const *argv[]) { struct event_base* base = event_base_new(); struct event* signal_event = evsignal_new(base, SIGINT, signal_cb, base); event_add(signal_event, NULL); timeval tv = {1, 0}; struct event* timeout_event = evtimer_new(base, timeout_cb, NULL); event_add(timeout_event, &tv); event_base_dispatch(base); event_free(timeout_event); event_free(signal_event); event_base_free(base); return 0; }
上述代碼雖然簡單,但卻基本描述了Libevent
庫的主要邏輯:
調用event_base_new
函數創建event_base
對象。一個event_base
相當于一個Reactor
實例。
創建具體的事件處理器,并設置它們所從屬的Reactor
實例。evsignal_new
和evtimer_new
分別用于創建信號事務處理器和定時事件處理器。
event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg)) #define evtimer_new(b, cb, arg) event_new((b), -1, 0, (cb), (arg))
可見,他們的統一入口是event_new
函數,即用于創建通用事件處理器的函數,定義如下:
event_new(struct event_base base, evutil_socket_t fd, short events, void (cb)(evutil_socket_t, short, void ), void arg)其中,base參數指定行
其中:
base
參數指定新創建的事件處理器從屬的Reactor
。
fd
參數指定與事件處理器關聯的句柄。創建I/O
事件處理器時,應該給fd
參數傳遞文件描述符;創建信號事件處理器時,應該給fd
參數傳遞信號值,比如之前實例代碼中的SIGINT
;創建定時事件處理器時則應該給fd
參數傳遞-1
。
events
參數指定事件類型,定義如下:
#define EV_TIMEOUT 0x01 /*定時事件*/ #define EV_READ 0x02 /*可讀事件*/ #define EV_WRITE 0x04 /*可寫事件*/ #define EV_SIGNAL 0x08 /*信號事件*/ #define EV_PERSIST 0x10 /*永久事件*/ /*邊緣觸發事件,需要I/O復用系統調用支持,比如epoll */ #define EV_ET 0x20
上述代碼中,EV_PERSIST
的作用是:事件被觸發后,自動重新對這個event
調用event_add
函數。
cb
參數指定目標事件對應的回調函數,相當于事件處理器handle_event
方法.
arg
則是Reactor
傳遞給回調函數的參數。
event_new
函數成功時返回一個event
類型的對象,也就是Libevent
的事件處理器。Libevent
用單詞“event”
來描述事件處理器,而不是事件,所以約定如下:
事件指的是一個句柄上綁定的事件,比如文件描述符 0 上的可讀事件
事件處理器,也就是event
結構提類型的對象,除了包含事件必須具備的兩個要素(句柄和事件類型)外,還有很多其他成員,比如回調函數
事件由事件多路分發器管理,事件處理器則由事件隊列管理,事件隊列包括多種,比如event_base
中的注冊事件隊列。
事件循環對一個被激活事件(就緒事件)的處理,指的是執行該事件對應的事件處理器中的回調函數。
調用event_add
函數,將事件處理器添加到注冊事件隊列中,并將該事件處理器對應的事件添加到事件多路分發器中。even_add
函數相當于Reactor
中的register_handler
方法。
調用event_base_dispatch
函數來執行事件循環
事件循環結束后,使用*_free
系列釋放系統資源
(推薦課程:Linux就該這么學)
頭文件目錄include/event2
。該目錄是自Libevent
主板本升級到2.0之后引入的,是提供給應用程序使用的,比如event.h
頭文件是核心函數,http.h
頭文件提供HTTP
協議相關服務,rpc.h
頭文件提供遠程過程調用支持。
源碼根目錄下的頭文件。這些頭文件分為兩類:
一類是對include/event2
目錄下的部分頭文件的包裝
另外一類是供Libevent
內部使用的輔助性頭文件,它們的文件名都具有*-internal.h
的形式。
通用數據目錄compat/sys
。該目錄下僅有一個文件----queue.h
。它封裝了跨平臺的基礎數據結構,包括單向鏈表、雙向鏈表、隊列、尾隊列和循環隊列。
sample
目錄。提供一些示例代碼
test
目錄。提供一次額測試代碼
WIN32-Code
。提供Windows
平臺上的一些專用代碼。
event.c
文件。該文件時間Libevent
的整體框架,主要是event
和event_base
兩個結構體的相關操作。
debpoll.c
、kqueue.c
、evport.c
、select.c
、win32select.c
、poll.c
和epoll.c
文件。它們分別封裝了如下I/O
復用機制:/dev/poll
、kqueue
、event ports
、POSIX select
、Windows select
、poll
和epoll
。這些文件的主要內容相似,都是針對結構體eventop
所定義的接口函數的具體實現。
minheap-internal.h
:該文件實現了一個事件堆,以提供對定時事件的支持。
signal.c
:提供對信號的支持。其內容也是針對結構體eventop
所定義的接口函數的具體實現
evmap.c
文件:它維護句柄(文件描述符或信號)與時間處理器的映射關系
event_tagging.c
:提供往緩沖區中添加標記數據,比如一個正數,以及從緩沖區中讀取標記數據的函數
event_iocp
文件:提供對Windows IOCP
(Input/Output Completion Port,輸入輸出完成端口)的支持
buffer*.c
文件:提供對網絡I/O
緩沖的控制,包括:輸入輸出數據過濾,傳輸速率限制,使用SSL
(Secure Sockets Layer)協議對應用數據進行保護,以及零拷貝文件傳輸等。
evthread*.c
文件:提供對多線程的支持
listener.c
:封裝了對監聽socket
的操作,包括監聽連接和接受連接
logs.c
文件。它是Libevent
的日志文件系統
evutil.c
、evutil_rand.c
、strlcpy.c
和arc4random.c
文件:提供了一些基本操作,比如生成隨機數、獲取socket
地址信息、讀取文件、設置socket
屬性等
evdns.c
、http.c
和evrpc.c
地址信息:分別提供了對DNS
協議、HTTP
協議和RPC
(Remote Procddure Call,遠程過程調用)協議的支持
epoll_sub.c
文件,該文件未見使用
在整個源碼中,event-internal.h
、include/event2/event_struct.h
、event.c
和evmap.c
等4個文件最為重要。它們定義了event
和event_base
結構體,并實現了這兩個結構體的相關操作。
讀到這里,這篇“Linux高性能I/O框架庫Libevent怎么用”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。