深度解讀:基于Libevent實(shí)現(xiàn)百萬(wàn)級(jí)并發(fā)
想象一下,熱門(mén)電商大促時(shí),瞬間涌入的海量訂單請(qǐng)求;火爆在線直播中,無(wú)數(shù)觀眾同時(shí)發(fā)送的彈幕互動(dòng)。每一個(gè)場(chǎng)景背后,都是對(duì)系統(tǒng)并發(fā)處理能力的嚴(yán)苛考驗(yàn)。如何構(gòu)建能夠穩(wěn)定承載百萬(wàn)級(jí)并發(fā)的系統(tǒng),成為開(kāi)發(fā)者們亟待攻克的難題。而在眾多解決方案中,Libevent 庫(kù)脫穎而出,成為構(gòu)建高并發(fā)系統(tǒng)的一把利刃。
它作為一個(gè)輕量級(jí)、跨平臺(tái)的事件通知庫(kù),默默在諸多知名項(xiàng)目背后提供支撐,像分布式緩存系統(tǒng) Memcached,就借助 Libevent 實(shí)現(xiàn)高效的網(wǎng)絡(luò)通信與事件處理。那么,Libevent 究竟有著怎樣的魔力,能讓系統(tǒng)輕松應(yīng)對(duì)百萬(wàn)級(jí)并發(fā)的挑戰(zhàn)?它的底層原理如何精妙設(shè)計(jì)?具體又該如何實(shí)現(xiàn)?接下來(lái),就讓我們一同走進(jìn) Libevent 的世界,深度解讀基于 Libevent 實(shí)現(xiàn)百萬(wàn)級(jí)并發(fā)的奧秘 。
Part1.什么是libevent
1.1 libevent概述
簡(jiǎn)單來(lái)說(shuō),libevent 是一個(gè)輕量級(jí)的開(kāi)源高性能網(wǎng)絡(luò)庫(kù) ,用 C 語(yǔ)言編寫(xiě),猶如一位低調(diào)而強(qiáng)大的幕后英雄,為眾多網(wǎng)絡(luò)應(yīng)用提供了堅(jiān)實(shí)的底層支持。它就像是一個(gè)精心構(gòu)建的舞臺(tái),各種網(wǎng)絡(luò)事件在上面有序上演,開(kāi)發(fā)者則如同導(dǎo)演,通過(guò) libevent 提供的接口,指揮著這些事件的發(fā)生與處理。
libevent 采用了事件驅(qū)動(dòng)(event-driven)的設(shè)計(jì)模式,這是它的核心魅力所在。想象一下,你開(kāi)了一家餐廳,傳統(tǒng)的服務(wù)方式是服務(wù)員逐個(gè)詢問(wèn)顧客需求,效率低下。而事件驅(qū)動(dòng)就像是安裝了一套智能呼叫系統(tǒng),顧客有需求時(shí)按下按鈕(觸發(fā)事件),服務(wù)員(事件處理函數(shù))就會(huì)立即響應(yīng)。在網(wǎng)絡(luò)編程中,當(dāng)有新的網(wǎng)絡(luò)連接到來(lái)、數(shù)據(jù)可讀或可寫(xiě),又或者是定時(shí)器超時(shí)、信號(hào)觸發(fā)時(shí),這些都被視為一個(gè)個(gè)事件。libevent 會(huì)密切關(guān)注這些事件的發(fā)生,一旦事件觸發(fā),就會(huì)迅速調(diào)用預(yù)先注冊(cè)好的回調(diào)函數(shù)來(lái)處理,大大提高了程序的響應(yīng)速度和效率。
跨平臺(tái)的特性使 libevent 成為了開(kāi)發(fā)者手中的一把萬(wàn)能鑰匙。無(wú)論你是在 Windows 系統(tǒng)下開(kāi)發(fā)桌面應(yīng)用,還是在 Linux 服務(wù)器上構(gòu)建大型網(wǎng)絡(luò)服務(wù),亦或是在 Mac OS 系統(tǒng)上進(jìn)行創(chuàng)意開(kāi)發(fā),甚至是在 * BSD 等小眾系統(tǒng)中探索,libevent 都能完美適配,就像一位適應(yīng)能力超強(qiáng)的旅行者,無(wú)論走到哪里都能迅速融入當(dāng)?shù)丨h(huán)境。它封裝了不同操作系統(tǒng)的底層 API,為開(kāi)發(fā)者提供了統(tǒng)一的編程接口,讓開(kāi)發(fā)者無(wú)需為不同平臺(tái)的差異而煩惱,可以專(zhuān)注于業(yè)務(wù)邏輯的實(shí)現(xiàn)。
在 I/O 多路復(fù)用技術(shù)的選擇上,libevent 更是展現(xiàn)出了強(qiáng)大的兼容性和適應(yīng)性,支持 epoll、poll、dev/poll、select 和 kqueue 等多種技術(shù)。這就好比你有一輛多功能的交通工具,在不同的路況下可以切換不同的行駛模式。在 Linux 系統(tǒng)中,它可以充分利用 epoll 的高效性能,處理大量并發(fā)連接;在 BSD 系統(tǒng)中,kqueue 則能發(fā)揮其優(yōu)勢(shì),為程序提供出色的事件通知機(jī)制。這種靈活的選擇,使得 libevent 在各種場(chǎng)景下都能游刃有余,為高性能網(wǎng)絡(luò)編程提供了有力保障。
此外,libevent 還支持 I/O、定時(shí)器和信號(hào)等多種事件類(lèi)型,并且可以注冊(cè)事件優(yōu)先級(jí)。這意味著開(kāi)發(fā)者可以根據(jù)業(yè)務(wù)需求,合理安排事件的處理順序,確保重要的事件能夠得到及時(shí)處理,就像在一場(chǎng)考試中,先完成分值高的題目,以獲得更好的成績(jī)。
1.2 安裝libevent
(1)Linux上使用如下命令安裝
sudo apt-get install libevent-dev(2)或者是源碼安裝 http://libevent.org/
#在當(dāng)前目錄下解壓安裝包:
tar -zxvf libevent-2.0.22-stable.tzr.gz
cd libevent-2.0.22-stable
#配置安裝庫(kù)的目標(biāo)路徑:
./configure --prefix=/usr
#編譯安裝libevent庫(kù):
make
make install
#查看libevent庫(kù)是否安裝成功:
ls -al /usr/lib | grep libevent通過(guò)函數(shù)**event_get_version()**可以查看libevent的版本。
Part2.libevent 核心原理剖析
2.1 事件驅(qū)動(dòng)機(jī)制
事件驅(qū)動(dòng)機(jī)制是 libevent 的核心所在,也是其高性能的關(guān)鍵秘訣。在傳統(tǒng)的網(wǎng)絡(luò)編程中,我們常常采用輪詢的方式來(lái)檢查網(wǎng)絡(luò)事件,就像一個(gè)勤勞的工人,不停地在各個(gè)工位之間巡查,詢問(wèn)是否有工作需要處理。這種方式雖然簡(jiǎn)單直接,但效率低下,尤其是在處理大量并發(fā)連接時(shí),就如同在一個(gè)龐大的工廠里,工人需要花費(fèi)大量的時(shí)間在路途上,真正用于工作的時(shí)間反而被壓縮了。
而 libevent 的事件驅(qū)動(dòng)機(jī)制則完全不同,它采用了一種更加智能的方式。應(yīng)用程序就像是工廠的管理者,只需要將需要關(guān)注的事件(如網(wǎng)絡(luò)連接的建立、數(shù)據(jù)的可讀可寫(xiě)等)以及對(duì)應(yīng)的回調(diào)函數(shù)注冊(cè)到 libevent 中,就像是給工人分配了明確的任務(wù)清單。當(dāng)這些事件發(fā)生時(shí),libevent 就像是一個(gè)高效的調(diào)度員,會(huì)立即觸發(fā)相應(yīng)的回調(diào)函數(shù)來(lái)處理事件,無(wú)需應(yīng)用程序主動(dòng)去查詢。這種方式大大提高了程序的響應(yīng)速度和效率,使得 libevent 能夠輕松應(yīng)對(duì)大量并發(fā)連接的場(chǎng)景。
2.2 I/O 復(fù)用技術(shù)
I/O 復(fù)用技術(shù)是 libevent 的另一大法寶,它為 libevent 的高性能提供了有力支持。在網(wǎng)絡(luò)編程中,I/O 操作往往是最耗時(shí)的部分,如何高效地處理 I/O 操作,成為了提高程序性能的關(guān)鍵。libevent 支持多種 I/O 復(fù)用技術(shù),如 epoll、select、poll 等,這些技術(shù)就像是不同的工具,在不同的場(chǎng)景下發(fā)揮著各自的優(yōu)勢(shì)。
以 epoll 為例,它是 Linux 系統(tǒng)下的一種高效 I/O 復(fù)用技術(shù),采用了事件驅(qū)動(dòng)的方式,通過(guò)回調(diào)函數(shù)只處理活躍的文件描述符 。當(dāng)文件描述符的狀態(tài)發(fā)生變化時(shí),epoll 會(huì)觸發(fā)相應(yīng)的事件,從而提高了處理效率。在一個(gè)高并發(fā)的網(wǎng)絡(luò)服務(wù)器中,可能同時(shí)有 thousands 甚至數(shù)萬(wàn)個(gè)連接,使用 epoll 可以輕松地管理這些連接,只對(duì)有數(shù)據(jù)讀寫(xiě)的連接進(jìn)行處理,避免了無(wú)效的輪詢操作,大大提高了服務(wù)器的性能。
而 select 則是一種比較傳統(tǒng)的 I/O 復(fù)用技術(shù),它通過(guò)輪詢的方式檢查文件描述符的狀態(tài),每次調(diào)用都需要遍歷所有的文件描述符集合,以檢查是否有文件描述符就緒。這種方式在文件描述符數(shù)量較少時(shí),表現(xiàn)尚可,但當(dāng)文件描述符數(shù)量較多時(shí),效率會(huì)顯著下降,就像是一個(gè)人要同時(shí)照顧很多個(gè)孩子,難免會(huì)顧此失彼。
libevent 對(duì)這些 I/O 復(fù)用技術(shù)進(jìn)行了封裝,為開(kāi)發(fā)者提供了統(tǒng)一的編程接口,使得開(kāi)發(fā)者無(wú)需深入了解底層技術(shù)的細(xì)節(jié),就可以輕松地選擇適合自己應(yīng)用場(chǎng)景的 I/O 復(fù)用技術(shù),就像在一個(gè)工具庫(kù)中,開(kāi)發(fā)者可以根據(jù)自己的需求,輕松地選擇合適的工具。
2.3 定時(shí)器實(shí)現(xiàn)
在許多網(wǎng)絡(luò)應(yīng)用中,定時(shí)器是不可或缺的一部分,它就像是一個(gè)精準(zhǔn)的時(shí)鐘,按照設(shè)定的時(shí)間間隔觸發(fā)相應(yīng)的事件。在一個(gè)實(shí)時(shí)通信系統(tǒng)中,我們可能需要定時(shí)發(fā)送心跳包,以保持連接的活躍;在一個(gè)任務(wù)調(diào)度系統(tǒng)中,我們可能需要定時(shí)執(zhí)行某些任務(wù),以保證系統(tǒng)的正常運(yùn)行。
libevent 中的定時(shí)器采用了最小堆(Min Heap)數(shù)據(jù)結(jié)構(gòu)來(lái)管理定時(shí)器事件,這是一種非常巧妙的設(shè)計(jì)。最小堆是一種特殊的二叉樹(shù),它的每個(gè)節(jié)點(diǎn)的值都小于或等于其左右子節(jié)點(diǎn)的值,這使得堆頂元素始終是最小的。在 libevent 中,定時(shí)器事件按照超時(shí)時(shí)間的先后順序存儲(chǔ)在最小堆中,每次檢查定時(shí)器時(shí),只需要檢查堆頂元素是否超時(shí)即可。如果堆頂元素未超時(shí),那么其他元素也一定未超時(shí),這樣就大大減少了檢查定時(shí)器的時(shí)間復(fù)雜度,提高了效率。就像是在一個(gè)有序的隊(duì)伍中,我們只需要檢查排在最前面的人是否到達(dá)時(shí)間,就可以知道整個(gè)隊(duì)伍的情況,無(wú)需逐個(gè)檢查每個(gè)人。
2.4 信號(hào)處理
在操作系統(tǒng)中,信號(hào)是一種異步通知機(jī)制,它可以在程序運(yùn)行的任何時(shí)刻發(fā)送給進(jìn)程,通知進(jìn)程發(fā)生了某些特定的事件,如用戶按下了 Ctrl+C 組合鍵,系統(tǒng)會(huì)向進(jìn)程發(fā)送 SIGINT 信號(hào),通知進(jìn)程需要進(jìn)行相應(yīng)的處理。
libevent 采用了統(tǒng)一事件源的方式來(lái)處理信號(hào)事件,將信號(hào)也轉(zhuǎn)換成 I/O 事件,集成到 libevent 的事件驅(qū)動(dòng)機(jī)制中。具體來(lái)說(shuō),當(dāng)用戶注冊(cè)了對(duì)某個(gè)信號(hào)的監(jiān)聽(tīng)時(shí),libevent 會(huì)在內(nèi)部創(chuàng)建一個(gè)管道(實(shí)際上使用的是 socketpair),并將這個(gè)管道加入到多路 I/O 復(fù)用函數(shù)的監(jiān)聽(tīng)之中。
同時(shí),libevent 會(huì)為這個(gè)信號(hào)設(shè)置捕抓函數(shù),當(dāng)信號(hào)發(fā)生時(shí),捕抓函數(shù)將會(huì)被調(diào)用,它的工作就是往管道寫(xiě)入一個(gè)字符(這個(gè)字符往往等于所捕抓到信號(hào)的信號(hào)值)。此時(shí),這個(gè)管道就變成是可讀的了,多路 I/O 復(fù)用函數(shù)能檢測(cè)到這個(gè)管道變成可讀的了,也就完成了對(duì)信號(hào)的監(jiān)聽(tīng)工作。這種方式巧妙地將信號(hào)處理與事件驅(qū)動(dòng)機(jī)制結(jié)合起來(lái),使得 libevent 能夠統(tǒng)一處理各種類(lèi)型的事件,提高了程序的靈活性和可擴(kuò)展性。
Part3.libevent 實(shí)現(xiàn)細(xì)節(jié)
3.1 關(guān)鍵數(shù)據(jù)結(jié)構(gòu)
在 libevent 的世界里,有一些關(guān)鍵的數(shù)據(jù)結(jié)構(gòu),它們就像是精密儀器中的重要零件,各自發(fā)揮著不可或缺的作用,共同支撐起了 libevent 強(qiáng)大的功能。
event_base 是 libevent 中的核心結(jié)構(gòu)體之一,它就像是一個(gè)大管家,負(fù)責(zé)管理事件循環(huán)、事件處理器以及各種資源。在一個(gè)基于 libevent 的網(wǎng)絡(luò)程序中,首先要?jiǎng)?chuàng)建一個(gè) event_base 對(duì)象,它為整個(gè)程序提供了一個(gè)運(yùn)行的環(huán)境。event_base 中包含了一個(gè)事件隊(duì)列,用于存儲(chǔ)所有注冊(cè)的事件;還包含了對(duì) I/O 復(fù)用機(jī)制的封裝,通過(guò)它可以方便地使用不同的 I/O 復(fù)用技術(shù),如 epoll、select 等。可以把 event_base 想象成一個(gè)大型活動(dòng)的組織者,它手里拿著一份嘉賓名單(事件隊(duì)列),并負(fù)責(zé)協(xié)調(diào)各種資源(I/O 復(fù)用機(jī)制),確保活動(dòng)(事件處理)能夠順利進(jìn)行。
event 結(jié)構(gòu)體則代表了一個(gè)具體的事件,它包含了事件的類(lèi)型(如 I/O 事件、定時(shí)器事件、信號(hào)事件等)、關(guān)聯(lián)的文件描述符、回調(diào)函數(shù)以及其他一些相關(guān)信息。當(dāng)我們需要關(guān)注某個(gè)文件描述符的讀或?qū)懯录r(shí),就會(huì)創(chuàng)建一個(gè) event 對(duì)象,并將其注冊(cè)到 event_base 中。event 就像是活動(dòng)中的一位嘉賓,它有自己的身份信息(事件類(lèi)型)、聯(lián)系方式(文件描述符)以及特定的任務(wù)(回調(diào)函數(shù)),當(dāng)相應(yīng)的事件發(fā)生時(shí),就會(huì)調(diào)用它的回調(diào)函數(shù)來(lái)處理。
除了 event_base 和 event,libevent 中還有一些其他重要的數(shù)據(jù)結(jié)構(gòu),如用于管理定時(shí)器事件的最小堆(Min Heap)、用于處理緩沖區(qū)的 evbuffer 等。這些數(shù)據(jù)結(jié)構(gòu)相互協(xié)作,共同實(shí)現(xiàn)了 libevent 高效的事件處理機(jī)制。
3.2 API 使用方法
libevent 提供了一系列簡(jiǎn)潔而強(qiáng)大的 API,這些 API 就像是一把把鑰匙,能夠幫助開(kāi)發(fā)者輕松地開(kāi)啟 libevent 的強(qiáng)大功能之門(mén)。
event_init 是早期版本中用于初始化 libevent 庫(kù)的函數(shù),不過(guò)現(xiàn)在已經(jīng)被標(biāo)記為過(guò)時(shí),推薦使用 event_base_new 來(lái)創(chuàng)建一個(gè)新的 event_base 實(shí)例。event_base_new 就像是創(chuàng)建一個(gè)新的工作空間,為后續(xù)的事件處理做好準(zhǔn)備。
event_add 用于將一個(gè)事件添加到 event_base 中,它的參數(shù)包括要添加的事件對(duì)象、事件的超時(shí)時(shí)間等。這就像是將一位嘉賓邀請(qǐng)到活動(dòng)現(xiàn)場(chǎng),并告知組織者嘉賓的到達(dá)時(shí)間(超時(shí)時(shí)間)。例如:
struct event_base *base = event_base_new();
struct event *ev = event_new(base, fd, EV_READ | EV_PERSIST, callback, NULL);
struct timeval tv = {2, 0}; // 2秒超時(shí)
event_add(ev, &tv);event_dispatch 則是啟動(dòng)事件循環(huán),開(kāi)始處理注冊(cè)的事件。它會(huì)不斷地檢查事件隊(duì)列,一旦有事件發(fā)生,就會(huì)調(diào)用相應(yīng)的回調(diào)函數(shù)進(jìn)行處理,就像活動(dòng)組織者開(kāi)始按順序接待嘉賓,處理他們的需求。在實(shí)際應(yīng)用中,通常會(huì)將 event_dispatch 放在程序的主循環(huán)中,以確保程序能夠持續(xù)響應(yīng)各種事件。
3.3 網(wǎng)絡(luò)通信實(shí)現(xiàn)
以 TCP 連接為例,libevent 在網(wǎng)絡(luò)通信的實(shí)現(xiàn)上展現(xiàn)出了其高效和靈活的特點(diǎn)。在建立 TCP 連接時(shí),首先需要?jiǎng)?chuàng)建一個(gè) socket,并將其綁定到指定的地址和端口。然后,使用 libevent 提供的 API,如 evconnlistener_new_bind,創(chuàng)建一個(gè)監(jiān)聽(tīng)對(duì)象,用于監(jiān)聽(tīng)客戶端的連接請(qǐng)求。這就像是在一個(gè)熱鬧的集市上,商家(服務(wù)器)擺好攤位(socket),并掛上招牌(監(jiān)聽(tīng)對(duì)象),等待顧客(客戶端)的到來(lái)。
當(dāng)有客戶端連接到來(lái)時(shí),監(jiān)聽(tīng)對(duì)象的回調(diào)函數(shù)會(huì)被觸發(fā),在這個(gè)回調(diào)函數(shù)中,可以創(chuàng)建一個(gè) bufferevent 對(duì)象,用于處理與客戶端的通信。bufferevent 就像是一個(gè)高效的通信員,它封裝了 socket 的讀寫(xiě)操作,并提供了方便的回調(diào)函數(shù)機(jī)制,使得數(shù)據(jù)的收發(fā)變得更加簡(jiǎn)單。通過(guò) bufferevent_setcb 函數(shù),可以設(shè)置讀、寫(xiě)和事件回調(diào)函數(shù),當(dāng)有數(shù)據(jù)可讀、可寫(xiě)或者發(fā)生其他事件時(shí),相應(yīng)的回調(diào)函數(shù)就會(huì)被調(diào)用。例如,當(dāng)有數(shù)據(jù)可讀時(shí),讀回調(diào)函數(shù)會(huì)被觸發(fā),在這個(gè)函數(shù)中可以使用 bufferevent_read 讀取數(shù)據(jù):
void read_cb(struct bufferevent *bev, void *ctx) {
struct evbuffer *input = bufferevent_get_input(bev);
size_t len = evbuffer_get_length(input);
char *data = malloc(len + 1);
evbuffer_remove(input, data, len);
data[len] = '\0';
printf("Received data: %s\n", data);
free(data);
}在處理連接斷開(kāi)時(shí),事件回調(diào)函數(shù)會(huì)被觸發(fā),通過(guò)檢查事件標(biāo)志,可以判斷連接是正常關(guān)閉還是出現(xiàn)了錯(cuò)誤,從而進(jìn)行相應(yīng)的處理。
3.4 libevent的實(shí)現(xiàn)
①創(chuàng)建默認(rèn)的event_base
event_base算是Libevent最基礎(chǔ)、最重要的對(duì)象,因?yàn)樾薷呐渲谩⑻砑邮录?,基本都需要將它作為參?shù)傳遞進(jìn)去。
#include <event2/event.h>
struct event_base *event_base_new(void);event_base_new()函數(shù)分配并且返回一個(gè)新的具有默認(rèn)設(shè)置的event_base。函數(shù)會(huì)檢測(cè)環(huán)境變量,返回一個(gè)到event_base的指針。如果發(fā)生錯(cuò)誤,則返回NULL。選擇各種方法時(shí),函數(shù)會(huì)選擇OS支持的最快方法。 使用完event_base之后,使用event_base_free()進(jìn)行釋放。
void event_base_free(struct event_base *base);注意:這個(gè)函數(shù)不會(huì)釋放當(dāng)前與event_base關(guān)聯(lián)的任何事件,或者關(guān)閉他們的套接字,或者釋放任何指針。 編譯的時(shí)候需要加上-levent。
②創(chuàng)建事件
使用event_new()接口創(chuàng)建事件;所有事件具有相似的生命周期。調(diào)用libevent函數(shù)設(shè)置事件并且關(guān)聯(lián)到event_base之后,事件進(jìn)入“已初始化 (initialized)”狀態(tài)。此時(shí)可以將事件添加到event_base中,這使之進(jìn)入“未決(pending)”狀態(tài)。
在未決狀態(tài)下,如果觸發(fā)事件的條件發(fā)生(比如說(shuō),文件描述符的狀態(tài)改變,或者超時(shí)時(shí)間到達(dá)),則事件進(jìn) 入“激活(active)”狀態(tài),(用戶提供的)事件回調(diào)函數(shù)將被執(zhí)行。如果配置為“持久的(persistent)”,事件將保持為未決狀態(tài)。否則,執(zhí)行完回調(diào)后,事件不再是未決的。刪除操作可以讓未決事件成為非未決(已初始化)的;添加操作可以讓非未決事件再次成為未決的。
struct event *event_new(struct event_base *base, evutil_socket_t fd,
short what, event_callback_fn cb, void *arg);
/*這個(gè)標(biāo)志表示某超時(shí)時(shí)間流逝后事件成為激活的。超時(shí)發(fā)生時(shí),回調(diào)函數(shù)的what參數(shù)將帶有這個(gè)標(biāo)志。*/
#define EV_TIMEOUT 0x01
/*表示指定的文件描述符已經(jīng)就緒,可以讀取的時(shí)候,事件將成為激活的。*/
#define EV_READ 0x02
/*表示指定的文件描述符已經(jīng)就緒,可以寫(xiě)入的時(shí)候,事件將成為激活的。*/
#define EV_WRITE 0x04
#define EV_SIGNAL 0x08 //用于實(shí)現(xiàn)信號(hào)檢測(cè)
#define EV_PERSIST 0x10 //表示事件是“持久的”
#define EV_ET 0x20 //表示如果底層的event_base后端支持邊沿觸發(fā)事件,則事件應(yīng)該是邊沿觸發(fā)的。
這個(gè)標(biāo)志影響EV_READ和EV_WRITE的語(yǔ)義。
typedef void (*event_callback_fn)(evutil_socket_t fd, short what, void * arg);
void event_free(struct event *event);event_new()試圖分配和構(gòu)造一個(gè)用于base的新事件。what參數(shù)是上述標(biāo)志的集合。如果fd非負(fù),則它是將被觀察 其讀寫(xiě)事件的文件。事件被激活時(shí),libevent將調(diào)用cb函數(shù),傳遞這些參數(shù):文件描述符fd,表示所有被觸發(fā)事件 的位字段,以及構(gòu)造事件時(shí)的arg參數(shù)。發(fā)生內(nèi)部錯(cuò)誤,或者傳入無(wú)效參數(shù)時(shí),event_new()將返回NULL。
要釋放事件,調(diào)用event_free()。 使用event_assign二次修改event的相關(guān)參數(shù):
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參數(shù)必須指向一個(gè)未初始化的事件之外,event_assign()的參數(shù)與event_new()的參數(shù)相同。成功時(shí)函數(shù)返回0,如果發(fā)生內(nèi)部錯(cuò)誤或者使用錯(cuò)誤的參數(shù),函數(shù)返回-1。
警告:不要對(duì)已經(jīng)在event_base中未決的事件調(diào)用event_assign(),這可能會(huì)導(dǎo)致難以診斷的錯(cuò)誤。如果已經(jīng)初始化和成為未決的,調(diào)用event_assign()之前需要調(diào)用event_del()。
③讓事件未決和非未決
讓事件未決:所有新創(chuàng)建的事件都處于已初始化和非未決狀態(tài),調(diào)用event_add()可以使其成為未決的。
int event_add(struct event *ev, const struct timeval *tv);在非未決的事件上調(diào)用event_add()將使其在配置的event_base中成為未決的。成功時(shí)函數(shù)返回0,失敗時(shí)返回-1。如果tv為NULL,添加的事件不會(huì)超時(shí)。否則,tv以秒和微秒指定超時(shí)值。
如果對(duì)已經(jīng)未決的事件調(diào)用event_add(),事件將保持未決狀態(tài),并在指定的超時(shí)時(shí)間被重新調(diào)度。
讓事件非未決:
int event_del(struct event *ev);對(duì)已經(jīng)初始化的事件調(diào)用event_del()將使其成為非未決和非激活的。如果事件不是未決的或者激活的,調(diào)用將 沒(méi)有效果。成功時(shí)函數(shù)返回0,失敗時(shí)返回-1。
④啟動(dòng)事件循環(huán)
#define EVLOOP_ONCE
0x01
#define EVLOOP_NONBLOCK 0x02
int event_base_loop(struct event_base *base, int flags);
int event_base_dispatch(struct event_base *base);默認(rèn)情況下,event_base_loop()函數(shù)運(yùn)行event_base直到其中沒(méi)有已經(jīng)注冊(cè)的事件為止。 執(zhí)行循環(huán)的時(shí)候,函數(shù)重復(fù)地檢查是否有任何已經(jīng)注冊(cè)的事件被觸發(fā)(比如說(shuō),讀事件的文件描述符已經(jīng)就緒,可以讀取了;或者超時(shí)事件的超時(shí)時(shí)間即將到達(dá))。如果有事件被觸發(fā),函數(shù)標(biāo)記被觸發(fā)的事件為“激活的”,并且執(zhí)行這些事件。
在?ags參數(shù)中設(shè)置一個(gè)或者多個(gè)標(biāo)志就可以改變event_base_loop()的行為。如果設(shè)置了EVLOOP_ONCE,循環(huán) 將等待某些事件成為激活的,執(zhí)行激活的事件直到?jīng)]有更多的事件可以執(zhí)行,然后返回。如果設(shè)置了EVLOOP_NONBLOCK,循環(huán)不會(huì)等待事件被觸發(fā):循環(huán)將僅僅檢測(cè)是否有事件已經(jīng)就緒,可以立即觸 發(fā),如果有,則執(zhí)行事件的回調(diào)。完成工作后,如果正常退出,event_base_loop()返回0;如果因?yàn)楹蠖酥械哪承┪刺幚礤e(cuò)誤而退出,則返回-1。
event_base_dispatch()等同于沒(méi)有設(shè)置標(biāo)志的event_base_loop();如果想在移除所有已注冊(cè)的事件之前停止活動(dòng)的事件循環(huán),可以調(diào)用兩個(gè)稍有不同的函數(shù)。
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在給定時(shí)間之后停止循環(huán)。如果tv參數(shù)為NULL,event_base會(huì)立即停止循環(huán),沒(méi)有延時(shí)。如果event_base當(dāng)前正在執(zhí)行任何激活事件的回調(diào),則回調(diào)會(huì)繼續(xù)運(yùn)行,直到運(yùn)行完所有激活事件的回調(diào)之后才退出。
event_base_loopbreak()讓event_base立即退出循環(huán)。它與event_base_loopexit(base,NULL)的不同在于, 如果event_base當(dāng)前正在執(zhí)行激活事件的回調(diào),它將在執(zhí)行完當(dāng)前正在處理的事件后立即退出。
Part4.實(shí)際應(yīng)用案例與技巧
4.1 應(yīng)用場(chǎng)景舉例
libevent 在實(shí)際項(xiàng)目中有著廣泛的應(yīng)用,就像一位多才多藝的演員,在不同的舞臺(tái)上都能展現(xiàn)出卓越的風(fēng)采。以 memcached 為例,這是一個(gè)高性能的分布式內(nèi)存對(duì)象緩存系統(tǒng),常用于減輕數(shù)據(jù)庫(kù)負(fù)載,加速動(dòng)態(tài) Web 應(yīng)用程序 。memcached 主要基于 Libevent 庫(kù)進(jìn)行開(kāi)發(fā),利用了 libevent 的事件驅(qū)動(dòng)和高效的 I/O 處理機(jī)制。
在一個(gè)高并發(fā)的 Web 應(yīng)用中,可能會(huì)有成千上萬(wàn)的用戶同時(shí)請(qǐng)求數(shù)據(jù)。如果每次請(qǐng)求都直接從數(shù)據(jù)庫(kù)獲取,數(shù)據(jù)庫(kù)的壓力會(huì)非常大,響應(yīng)速度也會(huì)變慢。而 memcached 就像是一個(gè)高速緩存區(qū),它會(huì)將經(jīng)常被訪問(wèn)的數(shù)據(jù)存儲(chǔ)在內(nèi)存中,當(dāng)有新的請(qǐng)求到來(lái)時(shí),首先檢查 memcached 中是否有相應(yīng)的數(shù)據(jù),如果有,就直接返回,大大減少了數(shù)據(jù)庫(kù)的訪問(wèn)次數(shù),提高了響應(yīng)速度。在一個(gè)新聞網(wǎng)站中,熱門(mén)新聞的內(nèi)容可以被緩存到 memcached 中,當(dāng)大量用戶請(qǐng)求這些新聞時(shí),就可以從 memcached 中快速獲取,而不需要每次都從數(shù)據(jù)庫(kù)中讀取。
libevent 的事件驅(qū)動(dòng)機(jī)制使得 memcached 能夠高效地處理大量并發(fā)連接。當(dāng)有新的連接到來(lái)時(shí),libevent 會(huì)迅速響應(yīng),將連接分配給相應(yīng)的處理線程,確保每個(gè)連接都能得到及時(shí)處理。在 I/O 操作方面,libevent 支持非阻塞 I/O,這意味著在等待數(shù)據(jù)讀寫(xiě)完成的過(guò)程中,線程不會(huì)被阻塞,可以繼續(xù)處理其他任務(wù),進(jìn)一步提高了系統(tǒng)的并發(fā)處理能力。
除了 memcached,libevent 還在許多其他項(xiàng)目中發(fā)揮著重要作用,如 Nginx、Varnish 等高性能服務(wù)器軟件,它們都借助 libevent 的強(qiáng)大功能,實(shí)現(xiàn)了高效的網(wǎng)絡(luò)通信和事件處理,為構(gòu)建高性能的網(wǎng)絡(luò)應(yīng)用提供了堅(jiān)實(shí)的基礎(chǔ)。
4.2 案例分析
示例一:簡(jiǎn)單使用Libevent注冊(cè)信號(hào)事件以及定時(shí)事件
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <unistd.h>
#include <event.h>
#include <signal.h>
void signal_cb(int fd,short event,void* arg)
{
if(event&EV_SIGNAL)
{
printf("sig=%d\n",fd);
}
}
void timeout_cb(int fd,short event,void* arg)
{
if(event&EV_TIMEOUT)
{
printf("time out\n");
}
}
int main()
{
//調(diào)用libevent示例
struct event_base* base=event_init();
//注冊(cè)信號(hào)事件
struct event* signal_event=evsignal_new(base,SIGINT,signal_cb,NULL);
event_add(signal_event,NULL);
//注冊(cè)超時(shí)事件
struct timeval tv = {2,0};
struct event* timeout_event=evtimer_new(base,timeout_cb,NULL);
event_add(timeout_event,&tv);
//啟動(dòng)事件循環(huán)
event_base_dispatch(base);
//free
event_free(signal_event);
event_free(timeout_event);
event_base_free(base);
}由于上述代碼中并沒(méi)有將注冊(cè)的事件變?yōu)橛谰檬录?,因此一次之后就結(jié)束了,所以程序運(yùn)行結(jié)果如下:
圖片
示例二:Libevent實(shí)現(xiàn)TCP服務(wù)器
服務(wù)器端:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <event.h>
//創(chuàng)建監(jiān)聽(tīng)套接字
int socket_init()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1)
{
return -1;
}
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family=AF_INET;
saddr.sin_port=htons(6000);
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res==-1)
{
return -1;
}
res=listen(sockfd,5);
if(res==-1)
{
return -1;
}
return sockfd;
}
void recv_cb(int fd,short event,void* arg)
{
if(event&EV_READ)
{
char buff[1024]={0};
int n=recv(fd,buff,1024,0);
if(n<=0)
{
struct event** p_cev=(struct event**)arg;
event_free(*p_cev);
free(p_cev);
close(fd);
printf("client close\n");
return ;
}
printf("recv:%s\n",buff);
send(fd,"ok",2,0);
}
}
void accept_cb(int fd,short event,void* arg)
{
struct event_base* base=(struct event_base*)arg;
if(event&EV_READ)
{
struct sockaddr_in caddr;
int len=sizeof(caddr);
int c=accept(fd,(struct sockaddr*)&caddr,&len);
if(c<0)
{
return ;
}
printf("accept c=%d\n",c);
struct event** p_cev=(struct event**)malloc(sizeof(struct event*));
if(p_cev==NULL)
{
return ;
}
*p_cev=event_new(base,c,EV_READ|EV_PERSIST,recv_cb,p_cev);
if(*p_cev==NULL)
{
close(c);
return ;
}
event_add(*p_cev,NULL);
}
}
int main()
{
struct event_base* base=event_init();
int sockfd=socket_init();
assert(sockfd!=-1);
struct event* sock_ev=event_new(base,sockfd,EV_READ|EV_PERSIST,accept_cb,base);
event_add(sock_ev,NULL);
event_base_dispatch(base);
event_free(sock_ev);
event_base_free(base);
return 0;
}客戶端:
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
assert(sockfd!=-1);
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family=AF_INET;
saddr.sin_port=htons(6000);
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res!=-1);
while(1)
{
printf("please input:\n");
char buff[1024]={0};
fgets(buff,1024,stdin);
if(strncmp(buff,"end",3)==0)
{
break;
}
int n=send(sockfd,buff,strlen(buff),0);
if(n<=0)
{
printf("send error\n");
break;
}
memset(buff,0,1024);
n=recv(sockfd,buff,1024,0);
if(n<=0)
{
printf("recv error\n");
}
printf("buff=%s\n",buff);
}
close(sockfd);
exit(0);
}程序運(yùn)行結(jié)果:
圖片
4.3 性能優(yōu)化技巧
在使用 libevent 時(shí),掌握一些性能優(yōu)化技巧可以讓你的程序如虎添翼,充分發(fā)揮 libevent 的優(yōu)勢(shì)。
合理選擇 I/O 復(fù)用技術(shù)是優(yōu)化性能的關(guān)鍵一步。不同的 I/O 復(fù)用技術(shù)在不同的場(chǎng)景下有著不同的表現(xiàn),就像不同的交通工具在不同的路況下有著不同的速度。在 Linux 系統(tǒng)中,如果你的應(yīng)用需要處理大量并發(fā)連接,epoll 通常是一個(gè)不錯(cuò)的選擇,它采用了事件驅(qū)動(dòng)的方式,能夠高效地處理大量活躍的文件描述符 。而在 BSD 系統(tǒng)中,kqueue 則能發(fā)揮其獨(dú)特的優(yōu)勢(shì),提供出色的事件通知機(jī)制。在選擇 I/O 復(fù)用技術(shù)時(shí),要根據(jù)應(yīng)用的具體需求和運(yùn)行環(huán)境進(jìn)行評(píng)估,選擇最適合的技術(shù)。
優(yōu)化事件回調(diào)函數(shù)也是提高性能的重要手段。事件回調(diào)函數(shù)是處理事件的核心代碼,它的執(zhí)行效率直接影響著整個(gè)程序的性能。要盡量減少回調(diào)函數(shù)中的復(fù)雜計(jì)算和阻塞操作,確?;卣{(diào)函數(shù)能夠快速執(zhí)行。在回調(diào)函數(shù)中,避免進(jìn)行大量的磁盤(pán) I/O 操作或復(fù)雜的數(shù)據(jù)庫(kù)查詢,因?yàn)檫@些操作往往比較耗時(shí),會(huì)導(dǎo)致其他事件的處理被延遲。如果確實(shí)需要進(jìn)行這些操作,可以考慮將它們放到單獨(dú)的線程或進(jìn)程中執(zhí)行,以避免阻塞事件循環(huán)。
合理設(shè)置事件的超時(shí)時(shí)間也能對(duì)性能產(chǎn)生影響。如果超時(shí)時(shí)間設(shè)置得過(guò)短,可能會(huì)導(dǎo)致一些正常的操作被誤判為超時(shí);而如果設(shè)置得過(guò)長(zhǎng),又可能會(huì)導(dǎo)致資源的浪費(fèi)和程序響應(yīng)速度的下降。要根據(jù)具體的業(yè)務(wù)需求和網(wǎng)絡(luò)環(huán)境,合理地設(shè)置事件的超時(shí)時(shí)間,確保程序能夠及時(shí)響應(yīng)事件,同時(shí)避免不必要的資源消耗。在一個(gè)網(wǎng)絡(luò)請(qǐng)求的場(chǎng)景中,如果網(wǎng)絡(luò)狀況較好,可以將超時(shí)時(shí)間設(shè)置得相對(duì)較短,以提高程序的響應(yīng)速度;而如果網(wǎng)絡(luò)狀況不穩(wěn)定,就需要適當(dāng)延長(zhǎng)超時(shí)時(shí)間,以確保請(qǐng)求能夠正常完成。






























