這篇文章介紹下libevent在socket非同步程式設計中的應用。在一些對效能要求較高的網路應用程式中,為了防止程式阻塞在socket i/o操作上造成程式效能的下降,需要使用非同步程式設計,即程式準備好讀寫的函式(或介面)並向系統註冊,然後在需要的時候只向系統提交讀寫的請求之後就繼續做自己的事情,實際的讀寫操作由系統在合適的時候呼叫我們程式註冊的介面進行。非同步程式設計會給一些程式猿帶來一些理解和編寫上的困難,因為我們通常寫的一些簡單的程式都是順序執行的,而非同步程式設計將程式的執行順序打亂了,有些**什麼情況下執行往往不是太清晰,因此也使得程式設計的複雜度大大增加。
note:這裡系統這個詞使用的不準確,實際上可以是自己封裝的非同步呼叫機制,更常見的是一些可用的庫,比如libevent,ace等
首先,安裝libevent到任意目錄下
wget
.13-stable.tar
.gztar –xzvf libevent-1.4
.13-stable.tar
.gzcd libevent-1.4
.13-stable
./configure --prefix=/home/mydir/libevent
make && make install
現在假定我們要設計乙個伺服器程式,用於接收客戶端的資料,並將接收的資料回寫給客戶端。下面來構造該程式,由於本僅僅是展示乙個demo,因此程式中將不對錯誤進行處理,假設所有的呼叫都成功
#define port 25341
#define backlog 5
#define mem_size 1024
struct event_base* base;
int main(int argc, char* argv)
第13行說明建立的是乙個tcp socket。第15行是伺服器程式的通常做法,設定了該選項後,在父子程序模型中,當子程序為客戶服務的時候如果父程序退出,可以重新啟動程式完成服務的無縫公升級,否則在所有父子程序完全退出前再啟動程式會在該埠上繫結失敗,也即不能完成無縫公升級的操作(更多資訊可以參考該函式說明或steven先生的《網路程式設計》)。第24行用於建立乙個事件處理的全域性變數,可以理解為這是乙個負責集中處理各種出入io事件的總管家,它負責接收和派發所有輸入輸出io事件的資訊,這裡呼叫的是函式event_base_new(), 很多程式裡這裡用的是event_init(),區別就是前者是執行緒安全的、而後者是非執行緒安全的,後者在其官方說明中已經被標誌為過時的函式、且建議用前者代替,libevent中還有很多類似的函式,比如建議用event_base_dispatch代替event_dispatch,用event_assign代替event_set和event_base_set等,關於libevent介面的詳細說明見其官方說明libevent_doc. 第25行說明在listen_en這個事件監聽sock這個描述字的讀操作,當讀訊息到達是呼叫on_accept函式,ev_persist引數告訴系統持續的監聽sock上的讀事件,如果不加該引數,每次要監聽該事件時就要重複的呼叫26行的event_add函式,從前面的**可知,sock這個描述字是bind到本地的socket埠上,因此其對應的可讀事件自然就是來自客戶端的連線到達,我們就可以呼叫accept無阻塞的返回客戶的連線了。第26行將listen_ev註冊到base這個事件中,相當於告訴處理io的管家請留意我的listen_ev上的事件。第27行相當於告訴處理io的管家,當有我的事件到達時你發給我(呼叫on_accept函式),至此對listen_ev的初始化完畢。第28行正式啟動libevent的事件處理機制,使系統執行起來,執行程式的話會發現event_base_dispatch是乙個無限迴圈。
下面是on_accept函式的內容
void on_accept(int sock, short
event, void* arg)
第9-12與前面main函式的24-26相同,即在代表客戶的描述字newfd上監聽可讀事件,當有資料到達是呼叫on_read函式。這裡有亮點需要注意,一是read_ev需要從堆裡malloc出來,如果是在棧上分配,那麼當函式返回時變數占用的記憶體會被釋放,因此事件主迴圈event_base_dispatch會訪問無效的記憶體而導致程序崩潰(即crash);第二個要注意的是第9行read_ev作為引數傳遞給了on_read函式。
下面是on_read函式的內容
void on_read(int sock, short
event, void* arg)
write_ev = (struct
event*) malloc(sizeof(struct
event));;
event_set(write_ev, sock, ev_write, on_write, buffer);
event_base_set(base, write_ev);
event_add(write_ev, null);
}
第9行,當從socket讀返回0標誌對方已經關閉了連線,因此這個時候就沒必要繼續監聽該套介面上的事件,由於ev_read在on_accept函式裡是用ev_persist引數註冊的,因此要顯示的呼叫event_del函式取消對該事件的監聽。第18-21行與on_accept函式的6-11行類似,當可寫時呼叫on_write函式,注意第19行將buffer作為引數傳遞給了on_write。這段程式還有比較嚴重的問題,後面進行說明。
on_write函式的實現
void on_write(int sock, short event, void* arg)
on_write函式中向客戶端回寫資料,然後釋放on_read函式中malloc出來的buffer。在很多書合程式設計指導中都很強調資源的所有權,經常要求誰分配資源、就由誰釋放資源,這樣對資源的管理指責就更明確,不容易出問題,但是通過該例子我們發現在非同步程式設計中資源的分配與釋放往往是由不同的所有者操作的,因此也是比較容易出問題的地方。
再來看看前面提到的on_read函式中存在的問題,首先write_ev是動態分配的記憶體,但是沒有釋放,因此存在記憶體洩漏,另外,on_read中進行malloc操作,那麼當多次呼叫該函式的時候就會造成記憶體的多次洩漏。這裡的解決方法是對socket的描述字可以封裝乙個結構體來保護讀、寫的事件以及資料緩衝區,整理後的完整**如下
#include
#include
#include
#include
#include
#define port 25341
#define backlog 5
#define mem_size 1024
struct event_base* base;
struct sock_ev ;
void release_sock_event(struct sock_ev* ev)
void on_write(int sock, short
event, void* arg)
void on_read(int sock, short
event, void* arg)
*/ event_set(ev->write_ev, sock, ev_write, on_write, ev->buffer);
event_base_set(base, ev->write_ev);
event_add(ev->write_ev, null);
}void on_accept(int sock, short
event, void* arg)
int main(int argc, char* argv)
Socket非同步程式設計
以 socket 通訊中的非同步方法為例 public static manualresetevent connectdone new manualresetevent false public static void connectcallback iasyncresult ar sclient....
非同步Socket程式設計I
下面,用乙個例子來解釋同步通訊和非同步通訊的根本區別。假定乙個伺服器應用程式在指定埠監聽客戶端傳送來的資料。在同步通訊接收中,當伺服器等待從客戶端接收資料時,如果流沒有資料,那麼,主線程會阻塞,直到資料請求得到。因此,在這段時間,伺服器端不能進行任何工作,直到接收到該客戶端的資料。如果此時,另乙個客...
非同步Socket程式設計II
下面的應用程式中有兩個類,乙個實現socket伺服器端,另乙個實現socket客戶端。socket伺服器端應用程式 socket伺服器應用程式在socketserver類中實現 檔名為socketserver.cs 這個類包含乙個主要的socket物件 m mainsocket 和一組工作socke...