libevent和基于libevent的网络编程
1 libevent介紹和安裝
介紹
libevent是一個(gè)輕量級(jí)的基于事件驅(qū)動(dòng)的高性能的開源網(wǎng)絡(luò)庫,并且支持多個(gè)平臺(tái),對(duì)多個(gè)平臺(tái)的I/O復(fù)用技術(shù)進(jìn)行了封裝,當(dāng)我們編譯庫的代碼時(shí),編譯的腳本將會(huì)根據(jù)OS支持的處理事件機(jī)制,來編譯相應(yīng)的代碼,從而在libevent接口上保持一致。
在當(dāng)前的服務(wù)器上,面對(duì)的主要問題就是要能處理大量的連接。而通過libevent這個(gè)網(wǎng)絡(luò)庫,我們就可以調(diào)用它的API來很好的解決上面的問題。首先,可以來回顧一下,對(duì)這個(gè)問題的傳統(tǒng)解決方法。
問題: 如何處理多個(gè)客戶端連接
解決方案1:I/O復(fù)用技術(shù)
這幾種方式都是同步I/O,即當(dāng)讀寫事件就緒,他們自己需要負(fù)責(zé)進(jìn)行讀寫,這個(gè)讀寫過程是阻塞的,而異步I/O則不需要自己負(fù)責(zé)讀寫,只需要通知負(fù)責(zé)讀寫的程序就可以了。
循環(huán)
假設(shè)當(dāng)前我服務(wù)器有多個(gè)網(wǎng)絡(luò)連接需要看管,那么我就循環(huán)遍歷打開的網(wǎng)絡(luò)連接的列表,來判斷是否有要讀取的數(shù)據(jù)。這種方法的缺點(diǎn)很明顯,那就是 1.速度緩慢(必須遍歷所有的網(wǎng)絡(luò)連接) 2.效率低 (處理一個(gè)連接時(shí)可能發(fā)生阻塞,妨礙其他網(wǎng)絡(luò)連接的檢查和處理)select方式
select對(duì)應(yīng)于內(nèi)核中的sys_select調(diào)用,sys_select首先將第二三四個(gè)參數(shù)指向的fd_set拷貝到內(nèi)核,然后對(duì)每個(gè)被SET的描述符調(diào)用進(jìn)行poll,并記錄在臨時(shí)結(jié)果中(fdset),如果有事件發(fā)生,select會(huì)將臨時(shí)結(jié)果寫到用戶空間并返回;當(dāng)輪詢一遍后沒有任何事件發(fā)生時(shí),如果指定了超時(shí)時(shí)間,則select會(huì)睡眠到超時(shí),睡眠結(jié)束后再進(jìn)行一次輪詢,并將臨時(shí)結(jié)果寫到用戶空間,然后返回。
select返回后,需要逐一檢查關(guān)注的描述符是否被SET(事件是否發(fā)生)。(select支持的文件描述符數(shù)量太小了,默認(rèn)是1024)。poll方式
poll與select不同,通過一個(gè)pollfd數(shù)組向內(nèi)核傳遞需要關(guān)注的事件,故沒有描述符個(gè)數(shù)的限制,pollfd中的events字段和revents分別用于標(biāo)示關(guān)注的事件和發(fā)生的事件,故pollfd數(shù)組只需要被初始化一次。
poll的實(shí)現(xiàn)機(jī)制與select類似,其對(duì)應(yīng)內(nèi)核中的sys_poll,只不過poll向內(nèi)核傳遞pollfd數(shù)組,然后對(duì)pollfd中的每個(gè)描述符進(jìn)行poll,相比處理fdset來說,poll效率更高。
poll返回后,需要對(duì)pollfd中的每個(gè)元素檢查其revents值,來得指事件是否發(fā)生。epoll方式
epoll通過epoll_create創(chuàng)建一個(gè)用于epoll輪詢的描述符,通過epoll_ctl添加/修改/刪除事件,通過epoll_wait檢查事件,epoll_wait的第二個(gè)參數(shù)用于存放結(jié)果。
epoll與select、poll不同,首先,其不用每次調(diào)用都向內(nèi)核拷貝事件描述信息,在第一次調(diào)用后,事件信息就會(huì)與對(duì)應(yīng)的epoll描述符關(guān)聯(lián)起來。其次,epoll不是通過輪詢,而是通過在等待的描述符上注冊(cè)回調(diào)函數(shù),當(dāng)事件發(fā)生時(shí),回調(diào)函數(shù)負(fù)責(zé)把發(fā)生的事件存儲(chǔ)在就緒事件鏈表中,最后寫到用戶空間。
epoll返回后,該參數(shù)指向的緩沖區(qū)中即為發(fā)生的事件,對(duì)緩沖區(qū)中每個(gè)元素進(jìn)行處理即可,而不需要像poll、select那樣進(jìn)行輪詢檢查。
解決方案2:多線程技術(shù)或多進(jìn)程技術(shù)
多線程技術(shù)和多進(jìn)程技術(shù)也可以處理高并發(fā)的數(shù)據(jù)連接,因?yàn)樵诜?wù)器中可以產(chǎn)生大量的進(jìn)程和線程和處理我們需要監(jiān)視的連接。但是,這兩種方式也是有很大的局限性的,比如多進(jìn)程模型就不適合大量的短連接,因?yàn)檫M(jìn)程的產(chǎn)生和關(guān)閉需要消耗較大的系統(tǒng)性能,同樣,還要進(jìn)程進(jìn)程間的通信,在CPU性能不足的情況下不太適合。而多線程技術(shù)則不太適合處理長(zhǎng)連接,因?yàn)楫?dāng)我們建立一個(gè)進(jìn)程時(shí),linux中會(huì)消耗8G的??臻g,如果我們的每個(gè)連接都杵著不斷開,那么大量連接長(zhǎng)連接后,導(dǎo)致的結(jié)果就是內(nèi)存的大量消耗。
解決方案3:常用的上述二者復(fù)合使用
上述的兩種方法各具有優(yōu)缺點(diǎn),因此,我們可以將上述的方法結(jié)合起來,這也是目前使用較多的處理高并發(fā)的方法。多進(jìn)程+I/O復(fù)用或者多線程+I/O復(fù)用。而在具體的實(shí)現(xiàn)上,又可以分為很多的方式。比如多線程+I/O復(fù)用技術(shù),我們使用使用一個(gè)主線程負(fù)責(zé)監(jiān)聽一個(gè)端口和接受的描述符是否有讀寫事件產(chǎn)生,如果有,則將事件分發(fā)給其他的工作進(jìn)程去完成,這也是進(jìn)程池的理念。
在說完上述的高并發(fā)的處理方法之后,我們可以來介紹一個(gè)libevent的主要特色了。
同樣,lievent也是采用的上述系統(tǒng)提供的select,poll和epoll方法來進(jìn)行I/O復(fù)用,但是針對(duì)于多個(gè)系統(tǒng)平臺(tái)上的不同的I/O復(fù)用實(shí)現(xiàn)方式,libevent進(jìn)行了重新的封裝,并提供了統(tǒng)一的API接口。libevent在實(shí)現(xiàn)上使用了事件驅(qū)動(dòng)這種機(jī)制,其本質(zhì)上是一種Reactor模式。
Reactor模式,是一種事件驅(qū)動(dòng)機(jī)制。應(yīng)用程序需要提供相應(yīng)的接口并注冊(cè)到Reactor上,如果相應(yīng)的事件發(fā)生,Reactor將主動(dòng)調(diào)用應(yīng)用程序注冊(cè)的接口,這些接口又稱為“回調(diào)函數(shù)”。
在Libevent中也是一樣,向Libevent框架注冊(cè)相應(yīng)的事件和回調(diào)函數(shù);當(dāng)這些事件發(fā)生時(shí),Libevent會(huì)調(diào)用這些回調(diào)函數(shù)處理相應(yīng)的事件。
lbevent的事件支持三種,分別是網(wǎng)絡(luò)IO、定時(shí)器和信號(hào)。定時(shí)器的數(shù)據(jù)結(jié)構(gòu)使用最小堆(Min Heap),以提高效率。網(wǎng)絡(luò)IO和信號(hào)的數(shù)據(jù)結(jié)構(gòu)采用了雙向鏈表(TAILQ)。
安裝
libevent的安裝很簡(jiǎn)單,我是直接從github上clone下一個(gè)源碼,然后進(jìn)行編譯安裝的。
具體的命令是(假設(shè)你已經(jīng)安裝了git):
# git clone https://github.com/nmathewson/Libevent.git# cd Libevent# sh autogen.sh# ./configure && make# make install# make verify //驗(yàn)證安裝2 Linux下libevent主要API介紹
現(xiàn)在的libevent版本已經(jīng)到達(dá)libevent2了,其增加了多線程的支持,API函數(shù)也發(fā)生了一些微小的變化。
創(chuàng)建事件集
struct event_base *event_base_new(void)
創(chuàng)建事件
struct event event_new(struct event_base ,evutil_socket_t ,short ,event_callback_fn,void*)
參數(shù)一:事件所在的事件集。
參數(shù)二:socket的描述符。
參數(shù)三:事件類型,其中EV_READ表示等待讀事件發(fā)生,EV_WRITE表示寫事件發(fā)生,或者它倆的組合,EV_SIGNAL表示需要等待事件的號(hào)碼,如 果不包含上述的標(biāo)志,就是超時(shí)事件或者手動(dòng)激活的事件。
參數(shù)四:事件發(fā)生時(shí)需要調(diào)用的回調(diào)函數(shù)。
參數(shù)五:回調(diào)函數(shù)的參數(shù)值。添加事件和刪除事件
int event_add(struct event * ev,const struct timeval* timeout)
參數(shù)一:需要添加的事件
參數(shù)二:事件的最大等待事件,如果是NULL的話,就是永久等待int event_del(struct event *)
參數(shù)一:需要?jiǎng)h除的事件分配監(jiān)聽事件
int event_base_dispatch(struct event_base * )
參數(shù)一:需要監(jiān)視的事件集
I/O buffer事件
struct bufferevent* bufferevent_socket_new
(struct event_base * base,evutil_socket_t fd,int options)參數(shù)一:需要添加到的時(shí)間集
參數(shù)二:相關(guān)的文件描述符
參數(shù)三:0或者是相應(yīng)的BEV_OPT_*可選標(biāo)志int bufferevent_enable(struct bufferevent * bev,short event)
參數(shù)一:需要啟用的bufferevent
參數(shù)二:any combination of EV|READ | EV_WRITEint bufferevent_disable(struct bufferevent * bev,short event)
參數(shù)說明:同上
size_t bufferevent_read(struct bufferevent bev,void data,size_t size)
參數(shù)一:讀取的buffer_event事件
參數(shù)二:存儲(chǔ)數(shù)據(jù)的指針
參數(shù)三:數(shù)據(jù)buffer的大小返回值:讀取數(shù)據(jù)的字節(jié)數(shù)
int bufferevent_write(struct bufferevent bev,const void data,size_t size)
參數(shù)一:讀取的buffer_event事件
參數(shù)二:存儲(chǔ)數(shù)據(jù)的指針
參數(shù)三:要寫入的數(shù)據(jù)的大小,字節(jié)數(shù)
如果你想知道更多的API使用情況,請(qǐng)點(diǎn)擊這里。
3.1 編程實(shí)例之聊天室服務(wù)器
下面,就基于libevent2編寫一個(gè)聊天室服務(wù)器。
設(shè)計(jì)思想:首先創(chuàng)建一個(gè)套接字,進(jìn)而創(chuàng)建一個(gè)事件對(duì)此端口進(jìn)行監(jiān)聽,將所請(qǐng)求的用戶組成一個(gè)隊(duì)列,并監(jiān)聽所有的用戶事件,當(dāng)某個(gè)用戶說話了,產(chǎn)生了讀事件,就將該用戶的發(fā)言發(fā)送給隊(duì)列中的其他用戶。
程序分析
需要包含的libevent函數(shù)頭:
#include <event2/event.h> #include <event2/event_struct.h> #include <event2/bufferevent.h> #include <event2/buffer.h>創(chuàng)建一個(gè)client結(jié)構(gòu)體,接受連接后存放數(shù)據(jù):
struct client { /* The clients socket. */int fd;/* The bufferedevent for this client. */ struct bufferevent *buf_ev;struct bufferevent *buf_ev; /** This holds the pointers to the next and previous entries in* the tail queue.*/TAILQ_ENTRY(client) entries; };先來看下mian函數(shù)的處理:
int main(int argc, char **argv) {int listen_fd;struct sockaddr_in listen_addr;struct event ev_accept;int reuseaddr_on;/* Initialize libevent. */evbase = event_base_new();/* Initialize the tailq. */TAILQ_INIT(&client_tailq_head);/* Create our listening socket. */listen_fd = socket(AF_INET, SOCK_STREAM, 0);if (listen_fd < 0)err(1, "listen failed");memset(&listen_addr, 0, sizeof(listen_addr));listen_addr.sin_family = AF_INET;listen_addr.sin_addr.s_addr = INADDR_ANY;listen_addr.sin_port = htons(SERVER_PORT);if (bind(listen_fd, (struct sockaddr *)&listen_addr,sizeof(listen_addr)) < 0)err(1, "bind failed");if (listen(listen_fd, 5) < 0)err(1, "listen failed");reuseaddr_on = 1;setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_on, sizeof(reuseaddr_on));/* Set the socket to non-blocking, this is essential in event* based programming with libevent. */if (setnonblock(listen_fd) < 0)err(1, "failed to set server socket to non-blocking");/* We now have a listening socket, we create a read event to* be notified when a client connects. */event_assign(&ev_accept, evbase, listen_fd, EV_READ|EV_PERSIST, on_accept, NULL);event_add(&ev_accept, NULL);/* Start the event loop. */event_base_dispatch(evbase);return 0; }首先,函數(shù)初始化了一個(gè)用戶隊(duì)列tailq,接著創(chuàng)建了一個(gè)socket套接字,并將套接字設(shè)定為非阻塞模式,接著對(duì)一個(gè)全局的evbase事件集合,注冊(cè)了事件,事件源是listen_fd,回調(diào)函數(shù)是on_accept,事件發(fā)生的情況是EV_READ,而且標(biāo)志EV_PESIST表明該事件一直存在,而后開啟事件掃描循環(huán)event_base_dispatch(evbase)。
再看一下回調(diào)函數(shù)on_accpet實(shí)現(xiàn):
void on_accept(int fd, short ev, void *arg) {int client_fd;struct sockaddr_in client_addr;socklen_t client_len = sizeof(client_addr);struct client *client;client_fd = accept(fd, (struct sockaddr *)&client_addr, &client_len);if (client_fd < 0) {warn("accept failed");return;}/* Set the client socket to non-blocking mode. */if (setnonblock(client_fd) < 0)warn("failed to set client socket non-blocking");/* We've accepted a new client, create a client object. */client = calloc(1, sizeof(*client));if (client == NULL)err(1, "malloc failed");client->fd = client_fd;client->buf_ev = bufferevent_socket_new(evbase, client_fd, 0);bufferevent_setcb(client->buf_ev, buffered_on_read, NULL,buffered_on_error, client);/* We have to enable it before our callbacks will be* called. */bufferevent_enable(client->buf_ev, EV_READ);/* Add the new client to the tailq. */TAILQ_INSERT_TAIL(&client_tailq_head, client, entries);printf("Accepted connection from %s\n", inet_ntoa(client_addr.sin_addr)); }這個(gè)回調(diào)函數(shù)的作用很顯然,就是接受了一個(gè)客戶端的請(qǐng)求,并申請(qǐng)好了一個(gè)client信息,將需要的內(nèi)容填寫好,在填寫中需要注意的是,又向上述的事件集evbase中注冊(cè)了一個(gè)bufferevent事件client->buf_ev,并注冊(cè)了回調(diào)函數(shù)buffered_on_read,buffered_on_error,這三個(gè)函數(shù)分別是當(dāng)接受后的連接發(fā)生了讀或者錯(cuò)誤事件后的執(zhí)行函數(shù)。接著,將用戶的client結(jié)構(gòu)放入了用戶的隊(duì)列tailq中去。
用戶的buffer可讀后的執(zhí)行函數(shù):
void buffered_on_read(struct bufferevent *bev, void *arg) {struct client *this_client = arg;struct client *client;uint8_t data[8192];size_t n;/* Read 8k at a time and send it to all connected clients. */for (;;) {n = bufferevent_read(bev, data, sizeof(data));if (n <= 0) {/* Done. */break;}/* Send data to all connected clients except for the* client that sent the data. */TAILQ_FOREACH(client, &client_tailq_head, entries) {if (client != this_client) {bufferevent_write(client->buf_ev, data, n);}}}}執(zhí)行函數(shù)的作用很明顯,將libevent管理中的buffer數(shù)據(jù)讀取出,存入本地的data數(shù)組內(nèi),然后對(duì)隊(duì)列中的client進(jìn)行檢索,如果不是發(fā)數(shù)據(jù)的client,則將數(shù)據(jù)寫入該client的buffer中,發(fā)送給該用戶。這里注意的是需要反復(fù)讀取buffer中的數(shù)據(jù),防止一個(gè)讀取并沒有讀取干凈,直到讀取不到數(shù)據(jù)為止。
buffer出錯(cuò)處理函數(shù)和上述函數(shù)差不多,功能就是出錯(cuò)后,結(jié)束掉保存的client結(jié)構(gòu),詳細(xì)就不說了。
程序源碼: chat-server.c Makefile
編譯的時(shí)候記得修改Makefile中Libevent文件夾的位置
3.2 編程實(shí)例之回顯服務(wù)器(純異步IO)
設(shè)計(jì)思想:所謂回顯服務(wù)器就是將客戶端發(fā)過來的數(shù)據(jù)再發(fā)回去,這里主要也就是說明libevent的純IO復(fù)用實(shí)現(xiàn)。實(shí)現(xiàn)方法和上面的差不多,甚至可以說更加簡(jiǎn)單。
程序和上面的聊天服務(wù)器差不多,只是在buffer可讀的事件函數(shù)中,不是將用戶的數(shù)據(jù)發(fā)送給其他用戶,而是直接發(fā)送給用戶本身。
程序源碼: libevent_echosrv_buffered.c Makefile
3.3 編程實(shí)例之回顯服務(wù)器(多線程--per connection per thread)
設(shè)計(jì)思想:上面的方法單純使用libevent的簡(jiǎn)單函數(shù)來實(shí)現(xiàn)服務(wù),但是這里,我們假設(shè)我們需要處理的客戶端很少,于是我們可以使用對(duì)于每個(gè)連接我們分配一個(gè)線程這樣的方式來實(shí)現(xiàn)對(duì)用戶的服務(wù)。這種方式簡(jiǎn)單有效,一對(duì)一服務(wù),就算業(yè)務(wù)邏輯出現(xiàn)阻塞也不怕。
程序分析
首先定義了一些數(shù)據(jù)結(jié)構(gòu),worker數(shù)據(jù)結(jié)構(gòu)定義的是一個(gè)工作者,它包含有一個(gè)工作線程,和結(jié)束標(biāo)志,需要獲取的工作隊(duì)列,和建立鏈表需要的指針。job數(shù)據(jù)結(jié)構(gòu)定義的是操作一個(gè)job的方法和對(duì)象,這回到程序中,實(shí)際上就是指的是事件發(fā)生后,封裝好的client結(jié)構(gòu)體和處理這個(gè)結(jié)構(gòu)體的方法。workqueue數(shù)據(jù)結(jié)構(gòu)指的是當(dāng)前的工作隊(duì)列中的工作者,以及工作隊(duì)列中的待完成的工作,以及互斥鎖和條件變量(因?yàn)槎鄠€(gè)工作進(jìn)程需要訪問這些資源)。
具體的流程就是,用一個(gè)主線程監(jiān)聽一個(gè)套接字,并將套接字接受到的連接accept,并創(chuàng)建一個(gè)client數(shù)據(jù)結(jié)構(gòu)保存該連接的信息,在這個(gè)client結(jié)構(gòu)中注冊(cè)一個(gè)bufferevent事件,注冊(cè)到client->evbase上(這時(shí)候這是向client中的evbase注冊(cè)了一個(gè)事件還沒有進(jìn)行循環(huán)這個(gè)事件集)。
接著,當(dāng)監(jiān)聽到某個(gè)client有bufferevent事件發(fā)生,主線程就把該client結(jié)構(gòu)體和需要進(jìn)行的工作方法包裝成一個(gè)job結(jié)構(gòu),然后把這個(gè)job扔到workqueue上去,并通知各個(gè)工作者。而后,各個(gè)工作者開著的線程就被激活了,瘋狂地去workqueue上去搶工作做,某個(gè)worker拿到工作后,就可以解包job,根據(jù)job的工作說明書(job_function)操作工作對(duì)象(client)了。這里,job的工作說明有是循環(huán)client中的client->evbase,于是這樣線程就會(huì)一直去監(jiān)視這個(gè)連接的狀態(tài),如果有數(shù)據(jù)就這會(huì)調(diào)用回調(diào)函數(shù)進(jìn)行處理。同時(shí),這個(gè)線程也就是阻塞在這里,這對(duì)這一個(gè)連接負(fù)責(zé)。
建立workqueue需要的結(jié)構(gòu)體和函數(shù)有:
typedef struct worker {pthread_t thread;int terminate;struct workqueue *workqueue;struct worker *prev;struct worker *next; } worker_t;typedef struct job {void (*job_function)(struct job *job);void *user_data;struct job *prev;struct job *next; } job_t;typedef struct workqueue {struct worker *workers;struct job *waiting_jobs;pthread_mutex_t jobs_mutex;pthread_cond_t jobs_cond; } workqueue_t;int workqueue_init(workqueue_t *workqueue, int numWorkers);void workqueue_shutdown(workqueue_t *workqueue);void workqueue_add_job(workqueue_t *workqueue, job_t *job);主線程的on_accept函數(shù)為:
void on_accept(evutil_socket_t fd, short ev, void *arg) {int client_fd;struct sockaddr_in client_addr;socklen_t client_len = sizeof(client_addr);workqueue_t *workqueue = (workqueue_t *)arg;client_t *client;job_t *job;client_fd = accept(fd, (struct sockaddr *)&client_addr, &client_len);if (client_fd < 0) {warn("accept failed");return;}/* Set the client socket to non-blocking mode. */if (evutil_make_socket_nonblocking(client_fd) < 0) {warn("failed to set client socket to non-blocking");close(client_fd);return;}/* Create a client object. */if ((client = malloc(sizeof(*client))) == NULL) {warn("failed to allocate memory for client state");close(client_fd);return;}memset(client, 0, sizeof(*client));client->fd = client_fd;/* Add any custom code anywhere from here to the end of this function* to initialize your application-specific attributes in the client struct.*/if ((client->output_buffer = evbuffer_new()) == NULL) {warn("client output buffer allocation failed");closeAndFreeClient(client);return;}if ((client->evbase = event_base_new()) == NULL) {warn("client event_base creation failed");closeAndFreeClient(client);return;}client->buf_ev = bufferevent_socket_new(client->evbase, client_fd, BEV_OPT_CLOSE_ON_FREE);if ((client->buf_ev) == NULL) {warn("client bufferevent creation failed");closeAndFreeClient(client);return;}bufferevent_setcb(client->buf_ev, buffered_on_read, buffered_on_write,buffered_on_error, client);/* We have to enable it before our callbacks will be* called. */bufferevent_enable(client->buf_ev, EV_READ);/* Create a job object and add it to the work queue. */if ((job = malloc(sizeof(*job))) == NULL) {warn("failed to allocate memory for job state");closeAndFreeClient(client);return;}job->job_function = server_job_function;job->user_data = client;workqueue_add_job(workqueue, job); }job中的工作指南為:
static void server_job_function(struct job *job) {client_t *client = (client_t *)job->user_data;//do my jobevent_base_dispatch(client->evbase);closeAndFreeClient(client);free(job); }程序源碼: echoserver_threaded.c workqueue.c workqueue.h Makefile
3.4 編程實(shí)例之回顯服務(wù)器(多線程--線程池+異步IO)
設(shè)計(jì)思想:假設(shè)我們的用戶很多,高并發(fā),長(zhǎng)連接,那么我們還是來用I/O復(fù)用和線程池實(shí)現(xiàn)吧,用一個(gè)控制線程通過I/O復(fù)用負(fù)責(zé)監(jiān)聽和分發(fā)事件,用一組線程池來進(jìn)行處理事件,這樣就可以靈活地將控制邏輯和業(yè)務(wù)邏輯分開了,見下述講解。
程序分析
具體的流程和上面的差不多,用一個(gè)主線程監(jiān)聽一個(gè)套接字,并將套接字接受到的連接accept,并創(chuàng)建一個(gè)client數(shù)據(jù)結(jié)構(gòu)保存該連接的信息,在這個(gè)client結(jié)構(gòu)中注冊(cè)一個(gè)bufferevent事件,但是這里,將事件注冊(cè)到accept_evbase中,仍然用主線程進(jìn)行監(jiān)聽。
而面對(duì)監(jiān)聽后出現(xiàn)的事件,將client和操作client的方法打包成一個(gè)job,放到上述的workqueue中去,讓工作進(jìn)程來完成。這樣的操作和上述的差別在于上述方法將bufferevent注冊(cè)到client中的evbase中,用工作線程監(jiān)聽,而本方法用主線程監(jiān)聽,工作線程負(fù)責(zé)處理監(jiān)聽產(chǎn)生的事件。
這要的差別在于兩個(gè)函數(shù) on_accept函數(shù):
void on_accept(evutil_socket_t fd, short ev, void *arg) {int client_fd;struct sockaddr_in client_addr;socklen_t client_len = sizeof(client_addr);client_t *client;client_fd = accept(fd, (struct sockaddr *)&client_addr, &client_len);if (client_fd < 0) {warn("accept failed");return;}/* Set the client socket to non-blocking mode. */if (evutil_make_socket_nonblocking(client_fd) < 0) {warn("failed to set client socket to non-blocking");close(client_fd);return;}/* Create a client object. */if ((client = malloc(sizeof(*client))) == NULL) {warn("failed to allocate memory for client state");close(client_fd);return;}memset(client, 0, sizeof(*client));client->fd = client_fd;/* Add any custom code anywhere from here to the end of this function* to initialize your application-specific attributes in the client struct.*/if ((client->output_buffer = evbuffer_new()) == NULL) {warn("client output buffer allocation failed");closeAndFreeClient(client);return;}//需要注意的是,這里注冊(cè)到evbase_acceptclient->buf_ev = bufferevent_socket_new(evbase_accept, client_fd,BEV_OPT_CLOSE_ON_FREE);if ((client->buf_ev) == NULL) {warn("client bufferevent creation failed");closeAndFreeClient(client);return;}bufferevent_setcb(client->buf_ev, buffered_on_read, buffered_on_write,buffered_on_error, client);/* We have to enable it before our callbacks will be* called. */bufferevent_enable(client->buf_ev, EV_READ); }在buffered_on_read中,提交job。
void buffered_on_read(struct bufferevent *bev, void *arg) {client_t *client = (client_t *)arg;job_t *job;/* Create a job object and add it to the work queue. */if ((job = malloc(sizeof(*job))) == NULL) {warn("failed to allocate memory for job state");closeAndFreeClient(client);return;}job->job_function = server_job_function;job->user_data = client;workqueue_add_job(&workqueue, job); }在job工作指南server_job_function中就可以做你工作該做的事兒了,根據(jù)發(fā)來的信息進(jìn)行數(shù)據(jù)庫處理,http返回等等。
程序源碼: echoserver_threaded.c workqueue.c workqueue.h Makefile
4 參考文章
[1] http://www.ibm.com/developerworks/cn/aix/library/au-libev/
[2] http://wenku.baidu.com/link?url=RmSm9M9mc4buqB_j6BGou5GxgyAn14lif18UUsQ8gr7pClAKGJr3civ8-DM0Xrpv4MdVIajykzbg34ZbGjGEizL8fOYE-EOKAATZIV06qwa
[3] http://blog.csdn.net/mafuli007/article/details/7476014
[4] http://blog.csdn.net/sparkliang/article/details/4957667
[5] http://bbs.chinaunix.net/thread-4118501-1-1.html
[6] http://bbs.chinaunix.net/thread-3776236-1-1.html
[7] http://www.zhihu.com/question/20114168
[8] http://www.zhihu.com/question/21516827
[9] http://www.wangafu.net/~nickm/libevent-2.0/doxygen/html/
[10] Libevent中文參考手冊(cè)
轉(zhuǎn)載于:https://www.cnblogs.com/nearmeng/p/4043548.html
總結(jié)
以上是生活随笔為你收集整理的libevent和基于libevent的网络编程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java NIO原理图文分析及代码实现
- 下一篇: 表驱动法之保险费率