本文將libevent,zeromq,和muduo三個(gè)網(wǎng)絡(luò)庫進(jìn)行對(duì)比分析:
libevent:
- 數(shù)組定義TAILQ_HEAD和TAILQ_ENTRY:
#define TAILQ_HEAD(name, type) \
struct name { \
struct type *tqh_first; /* first element */ \
struct type **tqh_last; /* 二級(jí)指針指向最后一個(gè)type的tqe_prev變量 */ \
}
//和前面的TAILQ_HEAD不同活鹰,這里的結(jié)構(gòu)體并沒有name.即沒有結(jié)構(gòu)體名陈哑。
//所以該結(jié)構(gòu)體只能作為一個(gè)匿名結(jié)構(gòu)體悠瞬。所以叽躯,它一般都是另外一個(gè)結(jié)構(gòu)體
//或者共用體的成員
#define TAILQ_ENTRY(type) \
struct { \
struct type *tqe_next; /* next element */ \
struct type **tqe_prev; /* address of previous next element */ \
}
-
2.信號(hào)event的處理
采用統(tǒng)一事件源的方式來處理信號(hào)event,即將信號(hào)轉(zhuǎn)換成IO來處理。
統(tǒng)一事件源的工作原理如下:假如用戶要監(jiān)聽SIGINT信號(hào)诀紊,那么在實(shí)現(xiàn)的內(nèi)部就對(duì)SIGINT這個(gè)信號(hào)設(shè)置捕抓函數(shù)述暂。此外娩鹉,在實(shí)現(xiàn)的內(nèi)部還要建立一條管道(pipe),并把這個(gè)管道加入到多路IO復(fù)用函數(shù)中元媚。當(dāng)SIGINT這個(gè)信號(hào)發(fā)生后轧叽,捕抓函數(shù)將會(huì)被調(diào)用。而這個(gè)捕抓函數(shù)的工作就是往管道寫入一個(gè)字符(這個(gè)字符往往等于所捕抓到信號(hào)的信號(hào)值)惠毁。此時(shí)犹芹,這個(gè)管道就變成是可讀的了,多路IO復(fù)用函數(shù)能檢測(cè)到這個(gè)管道變成可讀的了鞠绰。換言之腰埂,多路IO復(fù)用函數(shù)檢測(cè)到SIGINT信號(hào)的發(fā)生,也就完成了對(duì)信號(hào)的監(jiān)聽工作蜈膨。
具體實(shí)現(xiàn)細(xì)節(jié):1.創(chuàng)建一個(gè)管道(實(shí)際上使用socketpair);
2.為這個(gè)socketpair的一個(gè)讀端創(chuàng)建一個(gè)event,并將之加入到多路IO復(fù)用函數(shù)的監(jiān)聽中屿笼;
說明:以上2條都是在選定一個(gè)多路IO復(fù)用函數(shù)后牺荠,就會(huì)調(diào)用:
base->evbase = base->evsel->init(base);
選定的IO復(fù)用Init函數(shù)會(huì)創(chuàng)建一個(gè)socketpair,并將其讀端與ev_signal這個(gè)event相關(guān)聯(lián),用于監(jiān)聽信號(hào)驴一;
3.設(shè)置信號(hào)抓捕函數(shù);
4.有信號(hào)發(fā)生休雌,往socketpair寫入一個(gè)字節(jié);
說明:函數(shù)event_add會(huì)將信號(hào)event加入到event_base中,其中會(huì)調(diào)用到信號(hào)event的add函數(shù)evsig_add,此函數(shù)中會(huì)設(shè)置libevent的信號(hào)抓捕函數(shù),并將ev_signal作為信號(hào)監(jiān)聽的時(shí)間來通知event_base有信號(hào)發(fā)生了肝断。只需一個(gè)event即可完成工作杈曲,即使用戶要監(jiān)聽多個(gè)不同的信號(hào),因?yàn)檫@個(gè)event已經(jīng)和socketpair的讀端相關(guān)聯(lián)了胸懈。如果要監(jiān)聽多個(gè)信號(hào)担扑,那么就在信號(hào)處理函數(shù)中往這個(gè)socketpair寫入不同的值即可。event_base能監(jiān)聽到可讀趣钱,并可以從讀到的內(nèi)容可以判斷是哪個(gè)信號(hào)發(fā)生了涌献。
注意:當(dāng)我們對(duì)某個(gè)信號(hào)進(jìn)行event_new和event_add后,就不應(yīng)該再次設(shè)置該信號(hào)的信號(hào)捕抓函數(shù)首有。否則event_base將無法監(jiān)聽到信號(hào)的發(fā)生燕垃。
-
3.bufferevent:用于管理和調(diào)度IO事件,托管客戶端連接上的讀事件井联,寫時(shí)間卜壕,和事件處理。
主要函數(shù)包括:
//創(chuàng)建一個(gè)Bufferevent struct bufferevent *bufferevent_socket_new(struct event_base *base, evutil_socket_t fd, enum bufferevent_options options); //釋放Bufferevent void bufferevent_free(struct bufferevent *bev); //設(shè)置回調(diào)函數(shù) void bufferevent_setcb(struct bufferevent *bufev,bufferevent_data_cb readcb,bufferevent_data_cb writecb,bufferevent_event_cb eventcb,void *cbarg); //設(shè)置buffer時(shí)間類型 bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST); //設(shè)置水位 void bufferevent_setwatermark(struct bufferevent *bufev, short events,size_t lowmark, size_t highmark); //寫入 int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size); //輸出 size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);
水位設(shè)置說明:
低水位:表示當(dāng)evbuffer緩沖區(qū)的數(shù)據(jù)少于該水位時(shí)低矮,不會(huì)觸發(fā)用戶設(shè)定的讀回調(diào)函數(shù)印叁;
高水位:表示當(dāng)evbuffer緩沖區(qū)的數(shù)據(jù)大于該水位時(shí),將不會(huì)從socket的緩沖區(qū)繼續(xù)讀取數(shù)據(jù)军掂,避免了因socket緩沖區(qū)有數(shù)據(jù)而一直觸發(fā)監(jiān)聽讀的event形成的死循環(huán)轮蜕。當(dāng)水位低于該水位時(shí)將繼續(xù)從socket緩沖區(qū)讀取數(shù)據(jù);讀監(jiān)聽和寫監(jiān)聽的區(qū)別:
讀監(jiān)聽:當(dāng)監(jiān)聽讀事件的event檢測(cè)到socket讀緩沖區(qū)有數(shù)據(jù)時(shí)即認(rèn)為可讀蝗锥,觸發(fā)讀回調(diào)函數(shù)跃洛;
寫監(jiān)聽:當(dāng)監(jiān)聽寫事件的event檢測(cè)到socket寫緩沖區(qū)可寫時(shí)認(rèn)為可寫,調(diào)用寫回調(diào)函數(shù),因?yàn)閟ocket寫緩沖區(qū)大部分時(shí)間都是空的终议,所以這樣判斷會(huì)導(dǎo)致event一直觸發(fā)寫回調(diào)函數(shù)汇竭,造成死循環(huán)。實(shí)際libevent只用在調(diào)用寫入函數(shù)bufferevent_write時(shí)才會(huì)將監(jiān)聽寫的事件event添加(event_add)到event_base中進(jìn)行監(jiān)聽穴张,當(dāng)所有數(shù)據(jù)都寫入到socket緩沖區(qū)后就從event_base刪除event,避免形成死循環(huán)细燎; -
4.evbuffer:
//evbuffer-internal.h文件 struct evbuffer_chain; struct evbuffer { struct evbuffer_chain *first; struct evbuffer_chain *last; //這是一個(gè)二級(jí)指針。使用*last_with_datap時(shí)皂甘,指向的是鏈表中最后一個(gè)有數(shù)據(jù)的evbuffer_chain玻驻。 //所以last_with_datap存儲(chǔ)的是倒數(shù)第二個(gè)evbuffer_chain的next成員地址。 //一開始buffer->last_with_datap = &buffer->first;此時(shí)first為NULL。所以當(dāng)鏈表沒有節(jié)點(diǎn)時(shí) //*last_with_datap為NULL璧瞬。當(dāng)只有一個(gè)節(jié)點(diǎn)時(shí)*last_with_datap就是first户辫。 struct evbuffer_chain **last_with_data; size_t total_len;//鏈表中所有chain的總字節(jié)數(shù) ... }; struct evbuffer_chain { struct evbuffer_chain *next; size_t buffer_len;//buffer的大小 //錯(cuò)開不使用的空間。該成員的值一般等于0 ev_off_t misalign; //evbuffer_chain已存數(shù)據(jù)的字節(jié)數(shù) //所以要從buffer + misalign + off的位置開始寫入數(shù)據(jù) size_t off; ... unsigned char *buffer; };
說明:evbuffer為鏈表結(jié)構(gòu)嗤锉,鏈表的每個(gè)節(jié)點(diǎn)為evbuffer_chain渔欢,并且用二級(jí)指針last_with_data指向鏈表中最后一個(gè)有數(shù)據(jù)的成員地址。
evbuffer結(jié)構(gòu)圖解析:
如圖:在evbuffer鏈表中存在沒有數(shù)據(jù)的空節(jié)點(diǎn)瘟忱,這個(gè)如同STL中的vector一樣奥额,提前分配好空閑內(nèi)存,在下次添加數(shù)據(jù)時(shí),不需要額外申請(qǐng)空間就能保存數(shù)據(jù)访诱。
注意:evbuffer_chain中buffer指針指向的地址中保存的是真正的數(shù)據(jù)披坏,按理來說應(yīng)該用malloc單獨(dú)分配一塊內(nèi)存,但此處buffer指向的內(nèi)存是和evbuffer_chain本身存儲(chǔ)的內(nèi)存是一塊分配的盐数,代碼是在函數(shù)evbuffer_chain_new()中。
鏈表尾添加數(shù)據(jù):
int evbuffer_add(struct evbuffer *buf, const void *data_in, size_t datlen)
1.當(dāng)鏈表為空時(shí)直接新建一個(gè)evbuffer_chain伞梯,并調(diào)用函數(shù)evbuffer_chain_insert()添加到鏈表中玫氢。
2.當(dāng)鏈表最后一個(gè)有數(shù)據(jù)的chain(即last_with_data指向的內(nèi)存地址) 的空閑內(nèi)存大于用戶數(shù)據(jù)size時(shí),直接添加到此chain谜诫。
3.當(dāng)鏈表最后一個(gè)有數(shù)據(jù)的chain(即last_with_data指向的內(nèi)存地址) 的空閑內(nèi)存小于于用戶數(shù)據(jù)size時(shí)漾峡,這時(shí)就把數(shù)據(jù)分開到兩個(gè)chain中保存。
鏈表頭添加數(shù)據(jù):
int evbuffer_prepend(struct evbuffer *buf, const void *data, size_t datlen)
說明: misalign表示空閑未用的空間喻旷,可以隨時(shí)使用生逸。在頭部添加數(shù)據(jù)時(shí),新加數(shù)據(jù)使用的空間其實(shí)就是chain中的misalign空間且预,新建的chain的misalign空間就是全部的buffer內(nèi)存槽袄,隨著數(shù)據(jù)添加,misalign會(huì)越來越小锋谐。
待續(xù)...