1. 程式人生 > >c語言程式設計--網路程式設計之epoll

c語言程式設計--網路程式設計之epoll


1、為什麼select落後
    首先,在Linux核心中,select所用到的FD_SET是有限的,即核心中有個引數__FD_SETSIZE定義了每個FD_SET的控制代碼個數,在 我用的2.6.15-25-386核心中,該值是1024,搜尋核心原始碼得到:
include/linux/posix_types.h:
#define __FD_SETSIZE         1024
也就是說,如果想要同時檢測1025個控制代碼的可讀狀態是不可能用select實現的。或者 同時檢測1025個控制代碼的可寫狀態也是不可能的。其次,核心中實現 select是用輪詢方法,即每次檢測都會遍歷所有FD_SET中的控制代碼,顯然,select函式執行時間與FD_SET中的控制代碼個數有一個比例關係,即 select要檢測的控制代碼數越多就會越費時。當然,在前文中我並沒有提及poll方法,事實上用select的朋友一定也試過poll,我個人覺得 select和poll大同小異,個人偏好於用select而已。

2、核心中提高I/O效能的新方法epoll
    epoll是什麼?按照man手冊的說法:是為處理大批量控制代碼而作了改進的poll。要使用epoll只需要這三個系統調 用:epoll_create(2), epoll_ctl(2), epoll_wait(2)。
當然,這不是2.6核心才有的,它是在 2.5.44核心中被引進的(epoll(4) is a new API introduced in Linux kernel 2.5.44)

3、epoll的優點
<1>支援一個程序開啟大數 目的socket描述符(FD)
    select 最不能忍受的是一個程序所開啟的FD是有一定限制的,由FD_SETSIZE設定,預設值是2048。對於那些需要支援的上萬連線數目的IM伺服器來說顯 然太少了。這時候你一是可以選擇修改這個巨集然後重新編譯核心,不過資料也同時指出這樣會帶來網路效率的下降,二是可以選擇多程序的解決方案(傳統的 Apache方案),不過雖然linux上面建立程序的代價比較小,但仍舊是不可忽視的,加上程序間資料同步遠比不上執行緒間同步的高效,所以也不是一種完 美的方案。不過 epoll則沒有這個限制,它所支援的FD上限是最大可以開啟檔案的數目,這個數字一般遠大於2048,舉個例子,在1GB記憶體的機器上大約是10萬左 右,具體數目可以cat /proc/sys/fs/file-max察看,一般來說這個數目和系統記憶體關係很大。

<2>IO 效率不隨FD數目增加而線性下降
     傳統的select/poll另一個致命弱點就是當你擁有一個很大的socket集合,不過由於網路延時,任一時間只有部分的socket是"活躍"的, 但是select/poll每次呼叫都會線性掃描全部的集合,導致效率呈現線性下降。但是epoll不存在這個問題,它只會對"活躍"的socket進行 操作---這是因為在核心實現中epoll是根據每個fd上面的callback函式實現的。那麼,只有"活躍"的socket才會主動的去呼叫 callback函式,其他idle狀態socket則不會,在這點上,epoll實現了一個"偽"AIO,因為這時候推動力在os核心。在一些 benchmark中,如果所有的socket基本上都是活躍的---比如一個高速LAN環境,epoll並不比select/poll有什麼效率,相 反,如果過多使用epoll_ctl,效率相比還有稍微的下降。但是一旦使用idle connections模擬WAN環境,epoll的效率就遠在select/poll之上了。

<3>使用mmap加速核心 與使用者空間的訊息傳遞。
    這點實際上涉及到epoll的具體實現了。無論是select,poll還是epoll都需要核心把FD訊息通知給使用者空間,如何避免不必要的記憶體拷貝就 很重要,在這點上,epoll是通過核心於使用者空間mmap同一塊記憶體實現的。而如果你想我一樣從2.5核心就關注epoll的話,一定不會忘記手工 mmap這一步的。

<4>核心微調
    這一點其實不算epoll的優點了,而是整個linux平臺的優點。也許你可以懷疑 linux平臺,但是你無法迴避linux平臺賦予你微調核心的能力。比如,核心TCP/IP協議棧使用記憶體池管理sk_buff結構,那麼可以在執行時 期動態調整這個記憶體pool(skb_head_pool)的大小--- 通過echo XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函式的第2個引數(TCP完成3次握手 的資料包佇列長度),也可以根據你平臺記憶體大小動態調整。更甚至在一個數據包面數目巨大但同時每個資料包本身大小卻很小的特殊系統上嘗試最新的NAPI網 卡驅動架構。

4、epoll的工作模式
    令人高興的是,2.6核心的epoll比其2.5開發版本的/dev/epoll簡潔了許多,所以,大部分情況下,強大的東西往往是簡單的。唯一有點麻煩 是epoll有2種工作方式:LT和ET。
LT(level triggered)是預設的工作方式,並且同時支援block和no-block socket.在這種做法中,核心告訴你一個檔案描述符是否就緒了,然後你可以對這個就緒的fd進行IO操作。如果你不作任何操作,核心還是會繼續通知你 的,所以,這種模式程式設計出錯誤可能性要小一點。傳統的select/poll都是這種模型的代表.
ET (edge-triggered)是高速工作方式,只支援no-block socket。在這種模式下,當描述符從未就緒變為就緒時,核心通過epoll告訴你。然後它會假設你知道檔案描述符已經就緒,並且不會再為那個檔案描述 符傳送更多的就緒通知,直到你做了某些操作導致那個檔案描述符不再為就緒狀態了(比如,你在傳送,接收或者接收請求,或者傳送接收的資料少於一定量時導致 了一個EWOULDBLOCK 錯誤)。但是請注意,如果一直不對這個fd作IO操作(從而導致它再次變成未就緒),核心不會發送更多的通知(only once),不過在TCP協議中,ET模式的加速效用仍需要更多的benchmark確認。
epoll只有epoll_create,epoll_ctl,epoll_wait 3個系統呼叫,具體用法請參考http://www.xmailserver.org/linux-patches/nio-improve.html ,在http://www.kegel.com/rn/也有一個完整的例子,大家一看就知道如何使用了
Leader/follower模式執行緒 pool實現,以及和epoll的配合。

5、 epoll的使用方法
    首先通過create_epoll(int maxfds)來建立一個epoll的控制代碼,其中maxfds為你epoll所支援的最大控制代碼數。這個函式會返回一個新的epoll控制代碼,之後的所有操作 將通過這個控制代碼來進行操作。在用完之後,記得用close()來關閉這個創建出來的epoll控制代碼。之後在你的網路主迴圈裡面,每一幀的呼叫 epoll_wait(int epfd, epoll_event events, int max events, int timeout)來查詢所有的網路介面,看哪一個可以讀,哪一個可以寫了。基本的語法為:
nfds = epoll_wait(kdpfd, events, maxevents, -1);
    其中kdpfd為用epoll_create建立之後的控制代碼,events是一個 epoll_event*的指標,當epoll_wait這個函式操作成功之後,epoll_events裡面將儲存所有的讀寫事件。 max_events是當前需要監聽的所有socket控制代碼數。最後一個timeout是 epoll_wait的超時,為0的時候表示馬上返回,為-1的時候表示一直等下去,直到有事件範圍,為任意正整數的時候表示等這麼長的時間,如果一直沒 有事件,則範圍。一般如果網路主迴圈是單獨的執行緒的話,可以用-1來等,這樣可以保證一些效率,如果是和主邏輯在同一個執行緒的話,則可以用0來保證主迴圈 的效率。


Epoll模型詳解 - 成 - 我的部落格
    Epoll模型主要負責對大量併發使用者的請求進行及時處理,完成伺服器與客戶端的資料互動。其具體的實現步驟如下:
(a) 使用epoll_create()函式建立檔案描述,設定將可管理的最大socket描述符數目。
(b) 建立與epoll關聯的接收執行緒,應用程式可以建立多個接收執行緒來處理epoll上的讀通知事件,執行緒的數量依賴於程式的具體需要。
(c) 建立一個偵聽socket描述符ListenSock;將該描述符設定為非阻塞模式,呼叫Listen()函式在套接字上偵聽有無新的連線請求,在 epoll_event結構中設定要處理的事件型別EPOLLIN,工作方式為 epoll_ET,以提高工作效率,同時使用epoll_ctl()註冊事件,最後啟動網路監視執行緒。
(d) 網路監視執行緒啟動迴圈,epoll_wait()等待epoll事件發生。
(e) 如果epoll事件表明有新的連線請求,則呼叫accept()函式,將使用者socket描述符新增到epoll_data聯合體,同時設定該描述符為非 阻塞,並在epoll_event結構中設定要處理的事件型別為讀和寫,工作方式為epoll_ET.
(f) 如果epoll事件表明socket描述符上有資料可讀,則將該socket描述符加入可讀佇列,通知接收執行緒讀入資料,並將接收到的資料放入到接收資料 的連結串列中,經邏輯處理後,將反饋的資料包放入到傳送資料鏈表中,等待由傳送執行緒傳送。

01 //epoll_wait範圍之後應該是一個迴圈,遍利所有的事件:
02 for (n = 0; n < nfds;++n)
03 {
04         if (events[n].data.fd== listener)
05         {//如果是主socket的事件的話,則表示有新連線進入了,進行新連線的處理。
06                 client = accept (listener, (structsockaddr *)&local,&addrlen);
07                 if (client < 0)
08                 {
09                         perror ("accept");
10                         continue;
11                 }
12                 setnonblocking (client);        // 將新連線置於非阻塞模式
13                 ev.events= EPOLLIN |EPOLLET; // 並且將新連線也加入EPOLL的監聽佇列。
14                 //注意,這裡的引數EPOLLIN | EPOLLET並沒有設定對寫socket的監聽,
15                 //如果有寫操作的話,這個時候epoll是不會返回事件的,
16                 //如果要對寫操作也監聽的話,應該是EPOLLIN | EPOLLOUT | EPOLLET
17                 ev.data.fd= client;
18                 if (epoll_ctl (kdpfd,EPOLL_CTL_ADD,client, &ev) <0)
19                 {
20                         /*
21                                 設定好event之後,將這個新的event通過epoll_ctl加入到epoll的監聽佇列裡面,
22                                 這裡用EPOLL_CTL_ADD來加一個新的epoll事件,通過EPOLL_CTL_DEL來減少一個epoll事件,通
23                                 過EPOLL_CTL_MOD來改變一個事件的監聽方式.
24                         */
25                         fprintf (stderr,"epoll set insertion error: fd=%d",client);
26                         return -1;
27                 }
28         }
29         else                                                            // 如果不是主socket的事件的話,則代表是一個使用者socket的事件,
30                 do_use_fd (events[n].data.fd);//則來處理這個使用者socket的事情,比如說read(fd,xxx)之類的,或者一些其他的處理。
31 }
    對,epoll的操作就這麼簡單,總共不過4個 API:epoll_create, epoll_ctl, epoll_wait和close。
如果您對epoll的效率還不太瞭解,請參考我 之前關於網路遊戲的網路程式設計等相關的文章。

    以前公司的伺服器都是使用HTTP連線,但是這樣的話,在手機目前的網路情況下不但顯得速度較慢,而且不穩定。因此大家一致同意用 SOCKET來進行連線。雖然使用SOCKET之後,對於使用者的費用可能會增加(由於是用了CMNET而非CMWAP),但是,秉著使用者體驗至上的原則, 相信大家還是能夠接受的(希望那些玩家月末收到帳單不後能夠保持克制...)。
這次的伺服器設計中,最重要的一個突破,是使用了EPOLL模型, 雖然對之也是一知半解,但是既然在各大PC網遊中已經經過了如此嚴酷的考驗,相信他不會讓我們失望,使用後的結果,確實也是表現相當不錯。在這裡,我還是 主要大致介紹一下這個模型的結構。
6、Linux下EPOll程式設計例項
EPOLL模型似乎只有一種格式,所以大家只要參考我下面的程式碼, 就能夠對EPOLL有所瞭解了,程式碼的解釋都已經在註釋中:

01 while (TRUE)
02 {
03         int nfds =epoll_wait (m_epoll_fd,m_events, MAX_EVENTS, EPOLL_TIME_OUT);       //等待EPOLL時間的發生,相當於監聽,
04         //至於相關的埠,需要在初始化EPOLL的時候繫結。
05         if (nfds <=0)
06                 continue;
07         m_bOnTimeChecking= FALSE;
08         G_CurTime= time (NULL);
09         for (int i = 0; i <nfds; i++)
10         {
11                 try
12                 {
13                         if (m_events[i].data.fd== m_listen_http_fd)   //如果新監測到一個HTTP使用者連線到繫結的HTTP埠,
14                                 //建立新的連線。由於我們新採用了SOCKET連線,所以基本沒用。
15                         {
16                                 OnAcceptHttpEpoll ();
17                         }
18                         else if (m_events[i].data.fd== m_listen_sock_fd)      //如果新監測到一個SOCKET使用者連線到了繫結的SOCKET埠,
19                                 //建立新的連線。
20                         {
21                                 OnAcceptSockEpoll ();
22                         }
23                         else if (m_events[i].events& EPOLLIN) //如果是已經連線的使用者,並且收到資料,那麼進行讀入。
24                         {
25                                 OnReadEpoll (i);
26                         }
27
28                         OnWriteEpoll (i);       //檢視當前的活動連線是否有需要寫出的資料。
29                 }
30                 catch (int)
31                 {
32                         PRINTF ("CATCH捕獲錯誤\n");
33                         continue;
34                 }
35         }
36         m_bOnTimeChecking= TRUE;
37         OnTimer ();                                    //進行一些定時的操作,主要就是刪除一些短線使用者等。
38 }

相關推薦

c語言程式設計--網路程式設計epoll

1、為什麼select落後     首先,在Linux核心中,select所用到的FD_SET是有限的,即核心中有個引數__FD_SETSIZE定義了每個FD_SET的控制代碼個數,在 我用的2.6.15-25-386核心中,該值是1024,搜尋核心原始碼得到: inclu

C語言嵌入式系統程式設計修煉

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Linux C高階程式設計——網路程式設計包裹函式

                                    Linux網路程式設計(六)——包裹函式                          宗旨:技術的學習是有限的,分享的精神是無限的。          系統呼叫不能保證每次都成功,必須進行出

Linux C高階程式設計——網路程式設計UDP(4)

Linux網路程式設計——UDP 宗旨:技術的學習是有限的,分享的精神是無限的。 下面分析一幀基於UDP的TFTP協議幀。 乙太網首部 0000: 00 05 5d 67 d0 b1 00 05 5d 61 58 a8 08 00 IP首部0000: 45 00 00

Linux C高階程式設計——網路程式設計乙太網(2)

Linux網路程式設計——乙太網 宗旨:技術的學習是有限的,分享的精神是無限的。 1、乙太網幀格式         源地址和目的地址是指網絡卡的硬體地址(也叫MAC地址),長度是48位,是在網絡卡出廠時固化的。用ifconfig命令檢視,“ 硬體地址 00:0c:29

Linux C高階程式設計——網路程式設計API(5)

Linux C網路程式設計——API 宗旨:技術的學習是有限的,分享的精神的無限的。 一、基本socket函式         Linux系統是通過提供套接字(socket)來進行網路程式設計的。網路的socket資料傳輸是一種特殊的I/O,socket也是一種檔案描述

Linux C高階程式設計——網路程式設計TCP(3)

Linux網路程式設計(三)——TCP 宗旨:技術的學習是有限的,分享的精神是無限的。 1、TCP段格式         和UDP協議一樣也有源埠號和目的埠號,通訊的雙方由IP地址和埠號標識。32位序號、32位確認序號、視窗大小。4位首部長度和IP協議頭類似,表示TCP協議頭的長度,以4位元組為單位,

C語言嵌入式系統程式設計修煉三:記憶體操作

資料指標  在嵌入式系統的程式設計中,常常要求在特定的記憶體單元讀寫內容,彙編有對應的MOV指令,而除C/C++以外的其它程式語言基本沒有直接訪問絕對地址的能力。在嵌入式系統的實際除錯中,多借助C語言指標所具有的對絕對地址單元內容的讀寫能力。以指標直接操作記憶體多發生在如下

c語言libcurl網路http操作程式設計庫使用

http://www.cnblogs.com/moodlxs/archive/2012/10/15/2724318.html 目錄索引: 一、LibCurl基本程式設計框架 二、一些基本的函式 三、curl_easy_setopt函式部分選項介紹 四、curl_e

C語言可變引數程式設計Avg、Myprinf…

 我們要訪問未命名的可變引數,首先必須在可變引數函式中宣告va_list型別的變數。呼叫va_start並傳入兩個引數:第一個引數為va_list型別的變數,第二個為省略號前最後一個有名字的引數的名稱,接著每一呼叫va_arg就會返回下一個引數,va_arg的第一個引數為va_list,第二個引數為返回的型

C語言嵌入式系統程式設計修煉軟體架構篇

轉載來自下面的網頁:http://dev.yesky.com/131/2017631_2.shtml 模組劃分  模組劃分的"劃"是規劃的意思,意指怎樣合理的將一個很大的軟體劃分為一系列功能獨立的部分合作完成系統的需求。C語言作為一種結構化的程式設計語言,在模組的劃分上主要

學習筆記-C語言1(程式設計入門)

C語言和C++是作為一名程式設計師必備技能,非科班出身的我對這些語言一直是一知半解,後來更是直接使用簡單易上手的python,matlab語言。今天終於開始系統的學習了C了,記錄一些學習筆記,方便後面檢視,如有不妥,還請幫忙指正。 1. 檔案開頭 檔案開頭要加入:# include<

C語言筆記18--C語言面向物件程式設計

C語言是一門面向過程的程式語言,裡面沒有類的說法,沒有類的繼承、封裝、多型。Cpp是有類的概念的,Cpp本身就來源C語言,Cpp的類就是一個經過高度封裝的C語言結構體。在學習Cpp之前,瞭解C語言的設計模式非常重要,今天就用C語言結構體進行簡單的繼承、封裝、多型。 1.封裝 面向物件程式設計

[讀書筆記2]《C語言嵌入式系統程式設計修煉》

第3章 螢幕操作   3.1 漢字處理   現在要解決的問題是,嵌入式系統中經常要使用的並非是完整的漢字型檔,往往只是需要提供數量有限的漢字供必要的顯示功能。例如,一個微波爐的LCD上沒有必要提供顯示"電子郵件"的功能;一個提供漢字顯示功能的空調的LCD上不需要顯示一條"短訊息",諸如此類。但

[讀書筆記3]《C語言嵌入式系統程式設計修煉》

第五章 效能優化   5.1 使用巨集定義   在C語言中,巨集是產生內嵌程式碼的唯一方法。對於嵌入式系統而言,為了能達到效能要求,巨集是一種很好的代替函式的方法。     寫一個"標準"巨集MIN ,這個巨集輸入兩個引數並返回較小的一個:   錯誤做法: #define MIN(

C語言面向物件程式設計:配置檔案解析(6)

在實際專案中,經常會把軟體的某些選項寫入配置檔案。 Windows 平臺上的 INI 檔案格式簡單易用,本篇文章利用《C語言面向物件程式設計(五):單鏈表實現》中實現的單鏈表,設計了一個“類” ini_parser 來讀寫 INI 格式的配置檔案。     

C語言面向物件程式設計:單鏈表實現(5)

 前面我們介紹瞭如何在 C 語言中引入面嚮物件語言的一些特性來進行面向物件程式設計,從本篇開始,我們使用前面提到的技巧,陸續實現幾個例子,最後呢,會提供一個基本的 http server 實現(使用 libevent )。在這篇文章裡,我們實現一個通用的資料結構:單鏈表。  

C語言面向物件程式設計:面向介面程式設計(4)

 Java 中有 interface 關鍵字,C++ 中有抽象類或純虛類可以與 interface 比擬,C 語言中也可以實現類似的特性。     在面試 Java 程式設計師時我經常問的一個問題是:介面和抽象類有什麼區別。  &n

C語言面向物件程式設計:虛擬函式與多型(3)

 在《 C++ 程式設計思想》一書中對虛擬函式的實現機制有詳細的描述,一般的編譯器通過虛擬函式表,在編譯時插入一段隱藏的程式碼,儲存型別資訊和虛擬函式地址,而在呼叫時,這段隱藏的程式碼可以找到和實際物件一致的虛擬函式實現。     我們在這裡提供

C語言面向物件程式設計:繼承詳解(2)

在 C 語言面向物件程式設計(一)裡說到繼承,這裡再詳細說一下。     C++ 中的繼承,從派生類與基類的關係來看(出於對比 C 與 C++,只說公有繼承): 派生類內部可以直接使用基類的 public 、protected 成員(包括變數