• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

               C++ 技術中心

               :: 首頁 :: 聯系 ::  :: 管理
              160 Posts :: 0 Stories :: 87 Comments :: 0 Trackbacks

            公告

            鄭重聲明:本BLOG所發表的原創文章,作者保留一切權利。必須經過作者本人同意后方可轉載,并注名作者(天空)和出處(CppBlog.com)。作者Email:coder@luckcoder.com

            留言簿(27)

            搜索

            •  

            最新隨筆

            最新評論

            評論排行榜

            出處:Blog of Felix021
            時間:Sat, 25 Feb 2012 00:43:26 +0000
            作者:felix021
            地址:http://www.felix021.com/blog/read.php?2068
            內容:
            花了兩天的時間在libevent上,想總結下,就以寫簡單tutorial的方式吧,貌似沒有一篇簡單的說明,讓人馬上就能上手用的。
            首先給出官方文檔吧: http://libevent.org ,首頁有個Programming with Libevent,里面是一節一節的介紹libevent,但是感覺信息量太大了,而且還是英文的-。-(當然,如果想好好用libevent,看看還是很有必要的),還有個Reference,大致就是對各個版本的libevent使用doxgen生成的文檔,用來查函數原型和基本用法什么的。
            下面假定已經學習過基本的socket編程(socket,bind,listen,accept,connect,recv,send,close),并且對異步/callback有基本認識。
            基本的socket編程是阻塞/同步的,每個操作除非已經完成或者出錯才會返回,這樣對于每一個請求,要使用一個線程或者單獨的進程去處理,系統資源沒法支撐大量的請求(所謂c10k problem?),例如內存:默認情況下每個線程需要占用2~8M的棧空間。posix定義了可以使用異步的select系統調用,但是因為其采用了輪詢的方式來判斷某個fd是否變成active,效率不高[O(n)],連接數一多,也還是撐不住。于是各系統分別提出了基于異步/callback的系統調用,例如Linux的epoll,BSD的kqueue,Windows的IOCP。由于在內核層面做了支持,所以可以用O(1)的效率查找到active的fd。基本上,libevent就是對這些高效IO的封裝,提供統一的API,簡化開發。
            libevent大概是這樣的:
                默認情況下是單線程的(可以配置成多線程,如果有需要的話),每個線程有且只有一個event_base,對應一個struct event_base結構體(以及附于其上的事件管理器),用來schedule托管給它的一系列event,可以和操作系統的進程管理類比,當然,要更簡單一點。當一個事件發生后,event_base會在合適的時間(不一定是立即)去調用綁定在這個事件上的函數(傳入一些預定義的參數,以及在綁定時指定的一個參數),直到這個函數執行完,再返回schedule其他事件。//創建一個event_base
            struct event_base *base = event_base_new();
            assert(base != NULL);
                event_base內部有一個循環,循環阻塞在epoll/kqueue等系統調用上,直到有一個/一些事件發生,然后去處理這些事件。當然,這些事件要被綁定在這個event_base上。每個事件對應一個struct event,可以是監聽一個fd或者POSIX信號量之類(這里只講fd了,其他的看manual吧)。struct event使用event_new來創建和綁定,使用event_add來啟用://創建并綁定一個event
            struct event *listen_event;
            //參數:event_base, 監聽的fd,事件類型及屬性,綁定的回調函數,給回調函數的參數
            listen_event = event_new(base, listener, EV_READ|EV_PERSIST, callback_func, (void*)base);
            //參數:event,超時時間(struct timeval *類型的,NULL表示無超時設置)
            event_add(listen_event, NULL);
                注:libevent支持的事件及屬性包括(使用bitfield實現,所以要用 | 來讓它們合體)
                (a) EV_TIMEOUT: 超時
                (b) EV_READ: 只要網絡緩沖中還有數據,回調函數就會被觸發
                (c) EV_WRITE: 只要塞給網絡緩沖的數據被寫完,回調函數就會被觸發
                (d) EV_SIGNAL: POSIX信號量,參考manual吧
                (e) EV_PERSIST: 不指定這個屬性的話,回調函數被觸發后事件會被刪除
                (f) EV_ET: Edge-Trigger邊緣觸發,參考EPOLL_ET
                然后需要啟動event_base的循環,這樣才能開始處理發生的事件。循環的啟動使用event_base_dispatch,循環將一直持續,直到不再有需要關注的事件,或者是遇到event_loopbreak()/event_loopexit()函數。//啟動事件循環
            event_base_dispatch(base);
                接下來關注下綁定到event的回調函數callback_func:傳遞給它的是一個socket fd、一個event類型及屬性bit_field、以及傳遞給event_new的最后一個參數(去上面幾行回顧一下,把event_base給傳進來了,實際上更多地是分配一個結構體,把相關的數據都撂進去,然后丟給event_new,在這里就能取得到了)。其原型是:typedef void(* event_callback_fn)(evutil_socket_t sockfd, short event_type, void *arg)
                對于一個服務器而言,上面的流程大概是這樣組合的:
                1. listener = socket(),bind(),listen(),設置nonblocking(POSIX系統中可使用fcntl設置,windows不需要設置,實際上libevent提供了統一的包裝evutil_make_socket_nonblocking)
                2. 創建一個event_base
                3. 創建一個event,將該socket托管給event_base,指定要監聽的事件類型,并綁定上相應的回調函數(及需要給它的參數)。對于listener socket來說,只需要監聽EV_READ|EV_PERSIST
                4. 啟用該事件
                5. 進入事件循環
                ---------------
                6. (異步) 當有client發起請求的時候,調用該回調函數,進行處理。
                問題:為什么不在listen完馬上調用accept,獲得客戶端連接以后再丟給event_base呢?這個問題先想想噢。
                回調函數要做什么事情呢?當然是處理client的請求了。首先要accept,獲得一個可以與client通信的sockfd,然后……調用recv/send嗎?錯!大錯特錯!如果直接調用recv/send的話,這個線程就阻塞在這個地方了,如果這個客戶端非常的陰險(比如一直不發消息,或者網絡不好,老是丟包),libevent就只能等它,沒法處理其他的請求了——所以應該創建一個新的event來托管這個sockfd。
                在老版本libevent上的實現,比較羅嗦[如果不想詳細了解的話,看下一部分]。
                對于服務器希望先從client獲取數據的情況,大致流程是這樣的:
                1. 將這個sockfd設置為nonblocking
                2. 創建2個event:
                    event_read,綁上sockfd的EV_READ|EV_PERSIST,設置回調函數和參數(后面提到的struct)
                    event_write,綁上sockfd的EV_WRITE|EV_PERSIST,設置回調函數和參數(后面提到的struct)
                3. 啟用event_read事件
                ------
                4. (異步) 等待event_read事件的發生, 調用相應的回調函數。這里麻煩來了:回調函數用recv讀入的數據,不能直接用send丟給sockfd了事——因為sockfd是nonblocking的,丟給它的話,不能保證正確(為什么呢?)。所以需要一個自己管理的緩存用來保存讀入的數據中(在accept以后就創建一個struct,作為第2步回調函數的arg傳進來),在合適的時間(比如遇到換行符)啟用event_write事件【event_add(event_write, NULL)】,等待EV_WRITE事件的觸發
                ------
                5. (異步) 當event_write事件的回調函數被調用的時候,往sockfd寫入數據,然后刪除event_write事件【event_del(event_write)】,等待event_read事件的下一次執行。
                以上步驟比較晦澀,具體代碼可參考官方文檔里面的【Example: A low-level ROT13 server with Libevent】
                由于需要自己管理緩沖區,且過程晦澀難懂,并且不兼容于Windows的IOCP,所以libevent2開始,提供了bufferevent這個神器,用來提供更加優雅、易用的API。struct bufferevent內建了兩個event(read/write)和對應的緩沖區【struct evbuffer *input, *output】,并提供相應的函數用來操作緩沖區(或者直接操作bufferevent)。每當有數據被讀入input的時候,read_cb函數被調用;每當output被輸出完的時候,write_cb被調用;在網絡IO操作出現錯誤的情況(連接中斷、超時、其他錯誤),error_cb被調用。于是上一部分的步驟被簡化為:
                1. 設置sockfd為nonblocking
                2. 使用bufferevent_socket_new創建一個struct bufferevent *bev,關聯該sockfd,托管給event_base
                3. 使用bufferevent_setcb(bev, read_cb, write_cb, error_cb, (void *)arg)將EV_READ/EV_WRITE對應的函數
                4. 使用bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST)來啟用read/write事件
                ------
                5. (異步)
                    在read_cb里面從input讀取數據,處理完畢后塞到output里(會被自動寫入到sockfd)
                    在write_cb里面(需要做什么嗎?對于一個echo server來說,read_cb就足夠了)
                    在error_cb里面處理遇到的錯誤
                *. 可以使用bufferevent_set_timeouts(bev, struct timeval *READ, struct timeval *WRITE)來設置讀寫超時, 在error_cb里面處理超時。
                *. read_cb和write_cb的原型是
                     void read_or_write_callback(struct bufferevent *bev, void *arg)
                   error_cb的原型是
                     void error_cb(struct bufferevent *bev, short error, void *arg) //這個是event的標準回調函數原型
                   可以從bev中用libevent的API提取出event_base、sockfd、input/output等相關數據,詳情RTFM~
                
                于是代碼簡化到只需要幾行的read_cb和error_cb函數即可:
            void read_cb(struct bufferevent *bev, void *arg) {
                char line[256];
                int n;
                evutil_socket_t fd = bufferevent_getfd(bev);
                while (n = bufferevent_read(bev, line, 256), n > 0)
                    bufferevent_write(bev, line, n);
            }
            void error_cb(struct bufferevent *bev, short event, void *arg) {
                bufferevent_free(bev);
            }
                于是一個支持大并發量的echo server就成型了!下面附上無注釋的echo server源碼,110行,多抄幾遍,就能完全弄懂啦!更復雜的例子參見官方文檔里面的【Example: A simpler ROT13 server with Libevent】
            #include <stdio.h>
            #include <stdlib.h>
            #include <errno.h>
            #include <assert.h>
            #include <event2/event.h>
            #include <event2/bufferevent.h>
            #define LISTEN_PORT 9999
            #define LISTEN_BACKLOG 32
            void do_accept(evutil_socket_t listener, short event, void *arg);
            void read_cb(struct bufferevent *bev, void *arg);
            void error_cb(struct bufferevent *bev, short event, void *arg);
            void write_cb(struct bufferevent *bev, void *arg);
            int main(int argc, char *argv[])
            {
                int ret;
                evutil_socket_t listener;
                listener = socket(AF_INET, SOCK_STREAM, 0);
                assert(listener > 0);
                evutil_make_listen_socket_reuseable(listener);
                struct sockaddr_in sin;
                sin.sin_family = AF_INET;
                sin.sin_addr.s_addr = 0;
                sin.sin_port = htons(LISTEN_PORT);
                if (bind(listener, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
                    perror("bind");
                    return 1;
                }
                if (listen(listener, LISTEN_BACKLOG) < 0) {
                    perror("listen");
                    return 1;
                }
                printf ("Listening...\n");
                evutil_make_socket_nonblocking(listener);
                struct event_base *base = event_base_new();
                assert(base != NULL);
                struct event *listen_event;
                listen_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base);
                event_add(listen_event, NULL);
                event_base_dispatch(base);
                printf("The End.");
                return 0;
            }
            void do_accept(evutil_socket_t listener, short event, void *arg)
            {
                struct event_base *base = (struct event_base *)arg;
                evutil_socket_t fd;
                struct sockaddr_in sin;
                socklen_t slen = sizeof(sin);
                fd = accept(listener, (struct sockaddr *)&sin, &slen);
                if (fd < 0) {
                    perror("accept");
                    return;
                }
                if (fd > FD_SETSIZE) { //這個if是參考了那個ROT13的例子,貌似是官方的疏漏,從select-based例子里抄過來忘了改
                    perror("fd > FD_SETSIZE\n");
                    return;
                }
                printf("ACCEPT: fd = %u\n", fd);
                struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
                bufferevent_setcb(bev, read_cb, NULL, error_cb, arg);
                bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST);
            }
            void read_cb(struct bufferevent *bev, void *arg)
            {
            #define MAX_LINE    256
                char line[MAX_LINE+1];
                int n;
                evutil_socket_t fd = bufferevent_getfd(bev);
                while (n = bufferevent_read(bev, line, MAX_LINE), n > 0) {
                    line[n] = '\0';
                    printf("fd=%u, read line: %s\n", fd, line);
                    bufferevent_write(bev, line, n);
                }
            }
            void write_cb(struct bufferevent *bev, void *arg) {}
            void error_cb(struct bufferevent *bev, short event, void *arg)
            {
                evutil_socket_t fd = bufferevent_getfd(bev);
                printf("fd = %u, ", fd);
                if (event & BEV_EVENT_TIMEOUT) {
                    printf("Timed out\n"); //if bufferevent_set_timeouts() called
                }
                else if (event & BEV_EVENT_EOF) {
                    printf("connection closed\n");
                }
                else if (event & BEV_EVENT_ERROR) {
                    printf("some other error\n");
                }
                bufferevent_free(bev);
            }
            posted on 2015-05-03 20:28 C++技術中心 閱讀(678) 評論(0)  編輯 收藏 引用 所屬分類: Windows 網絡編程
            97精品国产91久久久久久| 人妻无码αv中文字幕久久琪琪布| 2021少妇久久久久久久久久| 国产精品一区二区久久精品| 精品久久人人做人人爽综合| 伊人伊成久久人综合网777| 久久亚洲私人国产精品vA| 国产精品美女久久久久AV福利| 久久久久国产精品人妻| 久久婷婷久久一区二区三区| 一本久久综合亚洲鲁鲁五月天亚洲欧美一区二区 | 国内精品久久国产大陆| 久久艹国产| 国产综合久久久久| 亚洲欧美国产精品专区久久| 韩国无遮挡三级久久| 狠狠色婷婷久久综合频道日韩 | 久久不见久久见免费影院www日本| 亚洲国产天堂久久久久久| 欧美777精品久久久久网| 久久天天婷婷五月俺也去| 成人a毛片久久免费播放| 日韩AV无码久久一区二区| 国产精品美女久久福利网站| 久久精品国产精品亚洲| 久久久青草青青亚洲国产免观| 久久久久亚洲Av无码专| 久久久久久精品久久久久| 久久99国产精品久久99小说| 久久久久国产视频电影| 91精品国产综合久久香蕉| 久久精品国产半推半就| 999久久久免费精品国产| 狠狠色丁香久久婷婷综合五月| 久久精品国产免费观看三人同眠| 久久精品国产只有精品66 | 伊人久久综合热线大杳蕉下载| 97久久精品无码一区二区天美| 久久无码人妻一区二区三区午夜| 亚洲精品无码久久一线| 亚洲AV成人无码久久精品老人|