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





























