App下載

Linux高性能I/O框架庫(kù)Libevent介紹

猿友 2020-08-13 14:48:45 瀏覽數(shù) (10295)
反饋

這篇文章主要講一下Libevent庫(kù)的內(nèi)容,順便對(duì)I/O庫(kù)整體做個(gè)介紹。

Linux服務(wù)器程序必須處理的三類(lèi)事件:

  • I/O事件
  • 信號(hào)
  • 定時(shí)事件

在處理這三類(lèi)事件時(shí)我們通常需要考慮如下三個(gè)問(wèn)題:

  • 統(tǒng)一事件源。很明顯,統(tǒng)一處理這三類(lèi)事件既能使代碼簡(jiǎn)單易懂,又能避免一些潛在的邏輯錯(cuò)誤。
  • 可移植性。不同的操作系統(tǒng)具有不同的I/O復(fù)用方式,比如Solarisdev/poll文件,FressBSDkqueue機(jī)制,Linuxepoll系統(tǒng)調(diào)用
  • 對(duì)并發(fā)編程的支持,在多進(jìn)程和多線(xiàn)程環(huán)境下,我們需要考慮各執(zhí)行實(shí)體如何協(xié)同處理客戶(hù)連接、信號(hào)和定時(shí)器,以避免競(jìng)態(tài)條件。

幸運(yùn)的是,開(kāi)源社區(qū)提供了很多優(yōu)秀的I/O框架庫(kù),他們不僅解決了上述問(wèn)題,讓開(kāi)發(fā)者可以將精力完全放在程序的邏輯上,而且穩(wěn)定性、性能等各方面都相當(dāng)出色。而Libevent就是其中相對(duì)輕量級(jí)的框架庫(kù)。

I/O框架庫(kù)概述

I/O框架庫(kù)以庫(kù)函數(shù)的形式,封裝了較為底層的系統(tǒng)調(diào)用,給應(yīng)用程序提供了一組更便于使用的接口。這些庫(kù)函數(shù)往往比程序員自己實(shí)現(xiàn)的同樣功能的函數(shù)更合理、更高效、且更健壯。因?yàn)樗鼈兘?jīng)受住了真實(shí)網(wǎng)絡(luò)環(huán)境下的高壓測(cè)試,以及時(shí)間的考驗(yàn)。

各種I/O框架庫(kù)的實(shí)現(xiàn)原理基本相似,要么以Reactor模式實(shí)現(xiàn),要么以Procator模式實(shí)現(xiàn)(高性能服務(wù)器程序框架 - 兩種高效的事件處理模式),要么同時(shí)以這兩種模式實(shí)現(xiàn)。舉例來(lái)說(shuō),基于Reactor模式的I/O框架庫(kù)包含如下幾個(gè)組件:

  • 句柄Handle
  • 事件多路分發(fā)器EventDemultiplexer
  • 事件處理器Eventhandler
  • 具體的事件處理器ConcreteEventHandler
  • Reactor

(推薦教程:Linux教程

這些組件關(guān)系如下圖:

組件關(guān)系

  1. 句柄: I/O框架庫(kù)要處理的對(duì)象,即I/O事件、信號(hào)和定時(shí)事件,統(tǒng)一稱(chēng)為事件源。一個(gè)事件源通常和一個(gè)句柄綁定在一起。句柄的作用是,當(dāng)內(nèi)核檢測(cè)到就緒事件時(shí),它將通過(guò)句柄來(lái)通知應(yīng)用程序這一事件。在Linux環(huán)境下,I/O事件對(duì)應(yīng)的句柄是文件描述符,信號(hào)事件對(duì)應(yīng)的句柄就是信號(hào)值。
  2. 事件多路分發(fā)器:事件的到來(lái)是隨機(jī)的、異步的。我們無(wú)法預(yù)知程序何時(shí)收到一個(gè)客戶(hù)連接請(qǐng)求,又亦活收到一個(gè)暫停信號(hào)。所以程序需要循環(huán)地等待并處理事件,這就是事件循環(huán)。在事件循環(huán)中,等待事件一般使用I/O復(fù)用技術(shù)來(lái)實(shí)現(xiàn)。I/O框架庫(kù)一般將系統(tǒng)支持的各種I/O復(fù)用系統(tǒng)調(diào)用封裝成統(tǒng)一的接口,稱(chēng)為事件多路分發(fā)器。事件多路分發(fā)器的demultiplex方法是等待事件的核心函數(shù),其內(nèi)部調(diào)用的是selectpoll、epoll_wait等函數(shù)。此外事件多路分發(fā)器還需實(shí)現(xiàn)register_eventremove_event方法,以供調(diào)用者往事件多路分發(fā)器中添加事件和從事件多路分發(fā)器中刪除事件。
  3. 事件處理器和具體時(shí)間處理器:事件處理器執(zhí)行事件對(duì)應(yīng)的業(yè)務(wù)邏輯。它通常包含一個(gè)或多個(gè)handle_event回調(diào)函數(shù),這些回調(diào)函數(shù)在事件循環(huán)中被執(zhí)行。I/O框架庫(kù)提供的事件處理器通常是一個(gè)接口,用戶(hù)需要繼承它來(lái)實(shí)現(xiàn)自己的事件處理器,即具體事件處理器。因此,事件處理器中的回調(diào)函數(shù)一般被聲明為需函數(shù),以支持用戶(hù)的擴(kuò)展。此外,事件處理器一般還提供一個(gè)get_handle方法,它返回與該事件處理器關(guān)聯(lián)的句柄。那么事件處理器和句柄有什么關(guān)系?當(dāng)時(shí)間多路分發(fā)器檢測(cè)到有事件發(fā)生時(shí),它是通過(guò)句柄來(lái)通知應(yīng)用程序的。因此,我們必須將事件處理器和句柄綁定,才能在事件發(fā)生時(shí)獲取到正確的事件處理器。
  4. Reactor:Reactor是I/O框架的核心。它提供的幾個(gè)主要方法是:
    • handle_events:該方法執(zhí)行事件循環(huán)。它重復(fù)如下過(guò)程:等待事件,然后依次處理所有就緒事件對(duì)應(yīng)的事件處理器。
    • register_handler: 該方法調(diào)用事件多路分發(fā)器的register_event方法來(lái)往事件多路分發(fā)器中注冊(cè)一個(gè)事件。 -remove_handler:該方法調(diào)用事件多路分發(fā)器的remove_event方法來(lái)往刪除事件多路分發(fā)器中注冊(cè)一個(gè)事件。

I/O框架庫(kù)的工作時(shí)序如下:

I/O工作時(shí)序

Libevent源碼分析

Libevent是開(kāi)源社區(qū)的一款高性能的I/O框架庫(kù),具有如下特點(diǎn):

  • 跨平臺(tái)支持
  • 統(tǒng)一事件源
  • 線(xiàn)程安全
  • 基于Reactor模式的實(shí)現(xiàn)

(推薦微課:Linux微課

一個(gè)實(shí)例

下面是用Libevent庫(kù)實(shí)現(xiàn)的一個(gè)“Hello World”程序。

include <sys/signal.h>

#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;
}

上述代碼雖然簡(jiǎn)單,但卻基本描述了Libevent庫(kù)的主要邏輯:

  1. 調(diào)用event_base_new函數(shù)創(chuàng)建event_base對(duì)象。一個(gè)event_base相當(dāng)于一個(gè)Reactor實(shí)例。
  2. 創(chuàng)建具體的事件處理器,并設(shè)置它們所從屬的Reactor實(shí)例。evsignal_newevtimer_new分別用于創(chuàng)建信號(hào)事務(wù)處理器和定時(shí)事件處理器。它們是定義在如下:

定義

define evsignal_new(b, x, cb, arg) \

    event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))
#define evtimer_new(b, cb, arg)     event_new((b), -1, 0, (cb), (arg))

可見(jiàn),他們的統(tǒng)一入口是event_new函數(shù),即用于創(chuàng)建通用事件處理器的函數(shù),定義如下:

event_new(struct event_base base, evutil_socket_t fd, short events, void (cb)(evutil_socket_t, short, void ), void arg)其中,base參數(shù)指定行

其中:

  • base參數(shù)指定新創(chuàng)建的事件處理器從屬的Reactor
  • fd參數(shù)指定與事件處理器關(guān)聯(lián)的句柄。創(chuàng)建I/O事件處理器時(shí),應(yīng)該給fd參數(shù)傳遞文件描述符;創(chuàng)建信號(hào)事件處理器時(shí),應(yīng)該給fd參數(shù)傳遞信號(hào)值,比如之前實(shí)例代碼中的SIGINT;創(chuàng)建定時(shí)事件處理器時(shí)則應(yīng)該給fd參數(shù)傳遞-1
  • events參數(shù)指定事件類(lèi)型,定義如下:

    #define EV_TIMEOUT  0x01   /*定時(shí)事件*/
    #define EV_READ     0x02         /*可讀事件*/
    #define EV_WRITE    0x04        /*可寫(xiě)事件*/
    #define EV_SIGNAL   0x08       /*信號(hào)事件*/
    #define EV_PERSIST  0x10     /*永久事件*/
    /*邊緣觸發(fā)事件,需要I/O復(fù)用系統(tǒng)調(diào)用支持,比如epoll */
    #define EV_ET       0x20

上述代碼中,EV_PERSIST的作用是:事件被觸發(fā)后,自動(dòng)重新對(duì)這個(gè)event調(diào)用event_add函數(shù)。

  • cb參數(shù)指定目標(biāo)事件對(duì)應(yīng)的回調(diào)函數(shù),相當(dāng)于事件處理器handle_event方法.
  • arg則是Reactor傳遞給回調(diào)函數(shù)的參數(shù)。

event_new函數(shù)成功時(shí)返回一個(gè)event類(lèi)型的對(duì)象,也就是Libevent的事件處理器。Libevent用單詞“event”來(lái)描述事件處理器,而不是事件,所以約定如下:

  • 事件指的是一個(gè)句柄上綁定的事件,比如文件描述符 0 上的可讀事件
  • 事件處理器,也就是event結(jié)構(gòu)提類(lèi)型的對(duì)象,除了包含事件必須具備的兩個(gè)要素(句柄和事件類(lèi)型)外,還有很多其他成員,比如回調(diào)函數(shù)
  • 事件由事件多路分發(fā)器管理,事件處理器則由事件隊(duì)列管理,事件隊(duì)列包括多種,比如event_base中的注冊(cè)事件隊(duì)列。
  • 事件循環(huán)對(duì)一個(gè)被激活事件(就緒事件)的處理,指的是執(zhí)行該事件對(duì)應(yīng)的事件處理器中的回調(diào)函數(shù)。

  1. 調(diào)用event_add函數(shù),將事件處理器添加到注冊(cè)事件隊(duì)列中,并將該事件處理器對(duì)應(yīng)的事件添加到事件多路分發(fā)器中。even_add函數(shù)相當(dāng)于Reactor中的register_handler方法。
  2. 調(diào)用event_base_dispatch函數(shù)來(lái)執(zhí)行事件循環(huán)
  3. 事件循環(huán)結(jié)束后,使用*_free系列釋放系統(tǒng)資源

(推薦課程:Linux就該這么學(xué)

源代碼組織結(jié)構(gòu)

  • github地址:https://github.com/libevent/libevent
  • 頭文件目錄include/event2。該目錄是自Libevent主板本升級(jí)到2.0之后引入的,是提供給應(yīng)用程序使用的,比如event.h頭文件是核心函數(shù),http.h頭文件提供HTTP協(xié)議相關(guān)服務(wù),rpc.h頭文件提供遠(yuǎn)程過(guò)程調(diào)用支持。
  • 源碼根目錄下的頭文件。這些頭文件分為兩類(lèi):
  • 一類(lèi)是對(duì)include/event2目錄下的部分頭文件的包裝
  • 另外一類(lèi)是供Libevent內(nèi)部使用的輔助性頭文件,它們的文件名都具有*-internal.h的形式。
  • 通用數(shù)據(jù)目錄compat/sys。該目錄下僅有一個(gè)文件----queue.h。它封裝了跨平臺(tái)的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),包括單向鏈表、雙向鏈表、隊(duì)列、尾隊(duì)列和循環(huán)隊(duì)列。
  • sample目錄。提供一些示例代碼
  • test目錄。提供一次額測(cè)試代碼
  • WIN32-Code。提供Windows平臺(tái)上的一些專(zhuān)用代碼。
  • event.c文件。該文件時(shí)間Libevent的整體框架,主要是eventevent_base兩個(gè)結(jié)構(gòu)體的相關(guān)操作。
  • debpoll.c、kqueue.c、evport.c、select.cwin32select.c、poll.cepoll.c文件。它們分別封裝了如下I/O復(fù)用機(jī)制:/dev/poll、kqueue、event ports 、POSIX selectWindows select、pollepoll。這些文件的主要內(nèi)容相似,都是針對(duì)結(jié)構(gòu)體eventop所定義的接口函數(shù)的具體實(shí)現(xiàn)。
  • minheap-internal.h:該文件實(shí)現(xiàn)了一個(gè)事件堆,以提供對(duì)定時(shí)事件的支持。
  • signal.c:提供對(duì)信號(hào)的支持。其內(nèi)容也是針對(duì)結(jié)構(gòu)體eventop所定義的接口函數(shù)的具體實(shí)現(xiàn)
  • evmap.c文件:它維護(hù)句柄(文件描述符或信號(hào))與時(shí)間處理器的映射關(guān)系
  • event_tagging.c:提供往緩沖區(qū)中添加標(biāo)記數(shù)據(jù),比如一個(gè)正數(shù),以及從緩沖區(qū)中讀取標(biāo)記數(shù)據(jù)的函數(shù)
  • event_iocp文件:提供對(duì)Windows IOCP(Input/Output Completion Port,輸入輸出完成端口)的支持
  • buffer*.c文件:提供對(duì)網(wǎng)絡(luò)I/O緩沖的控制,包括:輸入輸出數(shù)據(jù)過(guò)濾,傳輸速率限制,使用SSL(Secure Sockets Layer)協(xié)議對(duì)應(yīng)用數(shù)據(jù)進(jìn)行保護(hù),以及零拷貝文件傳輸?shù)取?/li>
  • evthread*.c文件:提供對(duì)多線(xiàn)程的支持
  • listener.c:封裝了對(duì)監(jiān)聽(tīng)socket的操作,包括監(jiān)聽(tīng)連接和接受連接
  • logs.c文件。它是Libevent的日志文件系統(tǒng)
  • evutil.cevutil_rand.c、strlcpy.carc4random.c文件:提供了一些基本操作,比如生成隨機(jī)數(shù)、獲取socket地址信息、讀取文件、設(shè)置socket屬性等
  • evdns.c、http.cevrpc.c地址信息:分別提供了對(duì)DNS協(xié)議、HTTP協(xié)議和RPC(Remote Procddure Call,遠(yuǎn)程過(guò)程調(diào)用)協(xié)議的支持
  • epoll_sub.c文件,該文件未見(jiàn)使用

在整個(gè)源碼中,event-internal.h、include/event2/event_struct.h、event.cevmap.c等4個(gè)文件最為重要。它們定義了eventevent_base結(jié)構(gòu)體,并實(shí)現(xiàn)了這兩個(gè)結(jié)構(gòu)體的相關(guān)操作。

以上就是關(guān)于Linux中高性能I/O框架庫(kù)Libevent的相關(guān)介紹了,希望對(duì)大家有所幫助。

1 人點(diǎn)贊