Windows完成端口與Linux+epoll技術(shù)簡介_第1頁
Windows完成端口與Linux+epoll技術(shù)簡介_第2頁
Windows完成端口與Linux+epoll技術(shù)簡介_第3頁
Windows完成端口與Linux+epoll技術(shù)簡介_第4頁
Windows完成端口與Linux+epoll技術(shù)簡介_第5頁
已閱讀5頁,還剩1頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

1、WINDOWS 完成端口編程1、基本概念2、WINDO WS 完成端口的特點3、完成端口( C ompletion Ports )相關(guān)數(shù)據(jù)結(jié)構(gòu)和創(chuàng)建4、完成端口線程的工作原理5、Window s 完成端口的實例代碼Linux 的 EPoll 模型1、為什么 select 落后2、內(nèi)核中提高 I /O 性能的新方法 epoll 3、epoll 的優(yōu)點4、epoll 的工作模式5、epoll 的使用方法6、Linux 下 EPO ll 編程實例總結(jié)WINDOWS 完成端口編程摘要:開發(fā)網(wǎng)絡(luò)程序從來都不是一件容易的事情,盡管只需要遵守很少的一些規(guī)則;創(chuàng)建 socket, 發(fā)起連接,接受連接,發(fā)送和接

2、受數(shù)據(jù)。真正的困難在于: 讓你的程序可以適應(yīng)從單單一個連接到幾千個連接乃至于上萬個連接。利用Window s 平臺完成端口進(jìn)行重疊 I/O 的技術(shù)和 Linux 在 2.6 版本的內(nèi)核中 引入的 EPO ll 技術(shù),可以很方便在在在 Window s 和 Linux 平臺上開發(fā)出支持大量連接的網(wǎng)絡(luò)服務(wù)程序。本文介紹在 Window s 和 Linux 平臺 上使用的完成端口和 EPoll 模型開發(fā)的基本原理,同時給出實際的例子。本文主要關(guān)注C /S 結(jié)構(gòu)的服務(wù)器端程序,因為一般來說,開發(fā)一個大容量,具可擴(kuò)展性的 w insock 程序一般就是指服務(wù)程序。1、基本概念設(shè)備-w indow s 操

3、作系統(tǒng)上允許通信的任何東西,比如文件、目錄、串行口、并行口、郵件槽、命名管道、無名管道、套接字、控制臺、邏輯磁盤、物理 磁盤等。絕大多數(shù)與設(shè)備打交道的函數(shù)都是 C reateF ile/ReadF ile/WriteF ile 等。所以我們不能看到* *F ile 函數(shù)就只想到文件 設(shè)備。與設(shè)備通信有兩種方式,同步方式和異步方式。同步方式下,當(dāng)調(diào)用ReadF ile 函數(shù)時,函數(shù)會等待系統(tǒng)執(zhí)行完所要求的工作,然后才返回;異步方式 下, ReadF ile 這類函數(shù)會直接返回,系統(tǒng)自己去完成對設(shè)備的操作,然后以某種方式通知完成操作。重疊 I/O -顧名思義,當(dāng)你調(diào)用了某個函數(shù)(比如 ReadF

4、ile)就立刻返回做自己的其他動作的時候,同時系統(tǒng)也在對 I/0 設(shè)備進(jìn)行你要求的操 作,在這段時間內(nèi)你的程序和系統(tǒng)的內(nèi)部動作是重疊的,因此有更好的性能。所以,重疊 I/O 是用于異步方式下使用 I/O 設(shè)備的。 重疊I/O 需要使用的一個非常重要的數(shù)據(jù)結(jié)構(gòu) O VERLAPPED 。2、WINDOWS 完成端口的特點Win32 重疊 I/O (Overlapped I/O )機(jī)制允許發(fā)起一個操作,然后在操作完成之后接受到信息。對于那種需要很長時間才能完成的操作來說,重疊 IO 機(jī)制尤其有用,因為發(fā)起重疊操作的線程 在重疊請求發(fā)出后就可以自由的做別的事情了。在WinNT 和 Win2000 上

5、,提供的真正的可擴(kuò)展的 I/O 模型就是使用完成端口( C ompletion Port )的重疊 I/O. 完成端口-是一種 WINDO WS 內(nèi)核對象。完成端口用于異步方式的重疊 I/0 情況下,當(dāng)然重疊 I/O 不一定非使用完成端口不 可,還有設(shè)備內(nèi)核對象、事件對象、告警 I/0 等。但是完成端口內(nèi)部提供了線程池的管理,可以避免反復(fù)創(chuàng)建線程的開銷,同時可以根據(jù)C PU 的個數(shù)靈活的決定 線程個數(shù),而且可以讓減少線程調(diào)度的次數(shù)從而提高性能其實類似于 WSAA syncSelect 和 select 函數(shù)的機(jī)制更容易兼容 U nix,但是難以實現(xiàn) 我們想要的 “擴(kuò)展性”。而且 w indow

6、 s 的完成端口機(jī)制在操作系統(tǒng)內(nèi)部已經(jīng)作了優(yōu)化,提供了更高的效率。所以,我們選擇完成端口開始我們的服務(wù)器程序的開發(fā)。1、發(fā)起操作不一定完成,系統(tǒng)會在完成的時候通知你,通過用戶在完成端口上的等待,處理操作的結(jié)果。所以要有檢查完成端口,取操作結(jié)果的線程。在完成端口 上守候的線程系統(tǒng)有優(yōu)化,除非在執(zhí)行的線程阻塞,不會有新的線程被激活,以此來減少線程切換造成的性能代價。所以如果程序中沒有太多的阻塞操作,沒有必要啟動太多的線程, CPU 數(shù)量的兩倍,一般這樣來啟動線程。2、操作與相關(guān)數(shù)據(jù)的綁定方式:在提交數(shù)據(jù)的時候用戶對數(shù)據(jù)打相應(yīng)的標(biāo)記,記錄操作的類型,在用戶處理操作結(jié)果的時候,通過檢查自己打的標(biāo)記和系

7、統(tǒng)的操作結(jié)果進(jìn)行相應(yīng)的處理。3、操作返回的方式:一般操作完成后要通知程序進(jìn)行后續(xù)處理。但寫操作可以不通知用戶,此時如果用戶寫操作不能馬上完成,寫操作的相 關(guān)數(shù)據(jù)會被暫存到到非 交換緩沖區(qū)中,在操作完成的時候,系統(tǒng)會自動釋放緩沖區(qū)。此時發(fā)起完寫操作,使用的內(nèi)存就可以釋放了。此時如果占用非交換緩沖太多會使系統(tǒng)停止響應(yīng)。3、完成端口( Completion Por ts )相關(guān)數(shù)據(jù)結(jié)構(gòu)和創(chuàng)建其實可以把完成端口看成系統(tǒng)維護(hù)的一個隊列,操作系統(tǒng)把重疊IO 操作完成的事件通知放到該隊列里,由于是暴露“操作完成”的事件通知,所以命名為“完成端口” (CO mpletion Ports )。一個 socket

8、 被創(chuàng)建后,可以在任何時刻和一個完成端口聯(lián)系起來。完成端口相關(guān)最重要的是 OVERLAPPED 數(shù)據(jù)結(jié)構(gòu)ty pedef struct _OVERLAPPED U LONG_PTR Internal;/ 被系統(tǒng)內(nèi)部賦值,用來表示系統(tǒng)狀態(tài)U LONG_PTR InternalHigh;/ 被系統(tǒng)內(nèi)部賦值,傳輸?shù)淖止?jié)數(shù)union struct DW O RD O ffset;/和 O ffsetHigh 合成一個 64 位的整數(shù),用來表示從文件頭部的多少字節(jié)開始DW O RD O ffsetHigh;/操作,如果不是對文件 I/O 來操作,則必須設(shè)定為 0;PVOID Pointer;HANDLE

9、 hEv ent;/如果不使用,就務(wù)必設(shè)為 0,否則請賦一個有效的 Ev ent 句柄 OVERL APPED, *L POVERL APPED;下面是異步方式使用 ReadF ile 的一個例子OVERLA PPED Overlapped;Overlapped.O ffset=345;Overlapped.O ffsetHigh=0;Overlapped.hEv ent=0;/假定其他參數(shù)都已經(jīng)被初始化ReadF ile(hFile,buffer,sizeof(buffer),&dw NumBy tesRead,&Overlapped);這樣就完成了異步方式讀文件的操作,然后 ReadF i

10、le 函數(shù)返回,由操作系統(tǒng)做自己的事情,下面介紹幾個與OV ERLAPPED 結(jié)構(gòu)相關(guān)的函數(shù)等待重疊 I/0 操作完成的函數(shù)BOO L GetOv erlappedResult ( HANDLE hF ile,LPOVERLAP PED lpOv erlapped,/接受返回的重疊 I/0 結(jié)構(gòu)LPDWO RD lpcbTransfer,/ 成功傳輸了多少字節(jié)數(shù)BOO L fWait /TRU E 只有當(dāng)操作完成才返回, F A LSE 直接返回,如果操作沒有完成,通過調(diào) /用 GetLastError ( )函數(shù)會返回ERROR_IO _INCOMPLETE);宏 HasOverlapped

11、IoC ompleted 可以幫助我們測試重疊 I/0 操作是否完成,該宏對 OV ERLAPPED 結(jié)構(gòu)的 Internal 成員進(jìn)行了測試,查看是否等于 STA TUS_PENDING 值。一般來說,一個應(yīng)用程序可以創(chuàng)建多個工作線程來處理完成端口上的通知事件。工作線程的數(shù)量依賴于程序的具體需要。但是在理想的情況下,應(yīng)該對應(yīng)一個 CPU 創(chuàng)建一個線程。因為在完成端口理想模型中,每個線程都可以從系統(tǒng)獲得一個“原子” 性的時間片,輪番運行并檢查完成端口,線程的切換是額外的開銷。在實際開發(fā)的時候,還要考慮這些線程是否牽涉到其他堵塞操作的情況。如果某線程進(jìn)行堵塞操作,系統(tǒng)則將其掛起,讓別的線程獲得運

12、行時間。因此,如果有這樣的情況,可以多創(chuàng)建幾個線程來盡量利用時間。應(yīng)用完成端口:創(chuàng)建完成端口:完成端口是一個內(nèi)核對象,使用時他總是要和至少一個有效的設(shè)備句柄進(jìn)行關(guān)聯(lián),完成端口是一個復(fù)雜的內(nèi)核對象,創(chuàng)建它的函數(shù)是:HANDLE C reateIoCompletionPort(IN HANDLE F ileHandle,IN HANDLE ExistingC ompletionPort, IN U LONG_PTR CompletionKey ,IN DW ORD Number O fConcurrentThreads);通常創(chuàng)建工作分兩步:第一步,創(chuàng)建一個新的完成端口內(nèi)核對象,可以使用下面的函數(shù)

13、:HANDLE C reateNewCompletionPort(DWORD dwNumber O fThreads)return C reateIoCompletionPort(INVA LID_HANDLE_VA LUE,NULL,NU LL,dwNumberO fThreads);第二步,將剛創(chuàng)建的完成端口和一個有效的設(shè)備句柄關(guān)聯(lián)起來,可以使用下面的函數(shù):bool A ssicoateDev iceWithC ompletionPort(HANDLE hC ompPort,HANDLE hDev ice,DWO RD dwCompKey )HANDLE h=C reateIoC ompl

14、etionPort(hDev ice,hC ompPort,dwC ompKey ,0); return h=hC ompPort;說明C reateIoCompletionPort 函數(shù)也可以一次性的既創(chuàng)建完成端口對象,又關(guān)聯(lián)到一個有效的設(shè)備句柄C ompletionKey 是一個可以自己定義的參數(shù),我們可以把一個結(jié)構(gòu)的地址賦給它,然后在合適的時候取出來使用,最好要保證結(jié)構(gòu)里面的內(nèi)存不是分配在棧上,除非你有十分的把握內(nèi)存會保留到你要使用的那一刻。NumberO fConcurrentThreads 通常用來指定要允許同時運行的的線程的最大個數(shù)。通常我們指定為0,這樣系統(tǒng)會根據(jù) C PU 的個

15、數(shù)來自動確定。創(chuàng)建和關(guān)聯(lián)的動作完成后,系統(tǒng)會將完成端口關(guān)聯(lián)的設(shè)備句柄、完成鍵作為一條紀(jì)錄加入到這個完成端口的設(shè)備列表中。如果你有多個完成端口,就會有多 個對應(yīng)的設(shè)備列表。如果設(shè)備句柄被關(guān)閉,則表中自動刪除該紀(jì)錄。4、完成端口線程的工作原理完成端口可以幫助我們管理線程池,但是線程池中的線程需要我們使用_beginthreadex 來創(chuàng)建,憑什么通知完成端口管理我們的新線程呢? 答案在函數(shù) GetQ ueuedC ompletionStatus 。該函數(shù)原型:BOO L GetQueuedC ompletionStatus(IN HANDLE CompletionPort,OU T LPDWO R

16、D lpNumber O fBytesTransferred, OU T PU LO NG_PTR lpC ompletionKey ,OU T LPOVERLAPPED *lpOverlapped, IN DW ORD dwMilliseconds);這個函數(shù)試圖從指定的完成端口的 I/0 完成隊列中抽取紀(jì)錄。只有當(dāng)重疊 I/O 動作完成的時候,完成隊列中才有紀(jì)錄。凡是調(diào)用這個函數(shù)的線程將被放入到完成 端口的等待線程隊列中,因此完成端口就可以在自己的線程池中幫助我們維護(hù)這個線程。完成端口的 I/0 完成隊列中存放了當(dāng)重疊 I/0 完成的結(jié)果 - 一條紀(jì)錄,該紀(jì)錄擁有四個字段,前三項就對應(yīng) G

17、etQ ueuedC ompletionStatus 函數(shù)的 2 、3、4 參數(shù),最后一個字段是錯誤信息 dw Error。我們也可以通過調(diào)用 PostQ ueudC ompletionStatus 模擬完成了一個重疊 I/0 操作。當(dāng) I/0 完成隊列中出現(xiàn)了紀(jì)錄,完成端口將會檢查等待線程隊列,該隊列中的線程都是通過調(diào)用 GetQ ueuedC ompletionStatus 函數(shù)使自 己加入隊列的。等待線程隊列很簡單,只是保存了這些線程的 ID 。完成端口會按照后進(jìn)先出的原則將一 個線程隊列的 ID 放入到釋放線程列表中,同時該線程將從 等待 GetQ ueuedC ompletionSt

18、atus 函數(shù)返回的睡眠狀態(tài)中變?yōu)榭烧{(diào)度狀態(tài)等待 C PU 的調(diào)度。所以我們的線程要想成為完成端口管理的線 程,就必須要調(diào)用 GetQ ueuedC ompletionStatus 函數(shù)。出于性能的優(yōu)化,實際上完成端口還維護(hù)了一個暫停線程列表,具體細(xì)節(jié)可以參考 Window s 高級編程指南,我們現(xiàn)在知道的知識,已經(jīng)足夠了。完成端口線程間數(shù)據(jù)傳遞線程間傳遞數(shù)據(jù)最常用的辦法是在_beginthreade x 函數(shù)中將參數(shù)傳遞給線程函數(shù),或者使用全局變量。但是完成端口還有自己的傳遞數(shù)據(jù)的方法,答案就在于C ompletionKey 和OVERLAPPED 參數(shù)。C ompletionKey 被保存

19、在完成端口的設(shè)備表中,是和設(shè)備句柄一一對應(yīng)的,我們可以將與設(shè)備句柄相關(guān)的數(shù)據(jù)保存到C ompletionKey 中, 或者將C ompletionKey 表示為結(jié)構(gòu)指針,這樣就可以傳遞更加豐富的內(nèi)容。這些內(nèi)容只能在一開始關(guān)聯(lián)完成端口和設(shè)備句柄的時候做,因此不能在以后 動態(tài)改變。OVERLA PPED 參數(shù)是在每次調(diào)用 ReadF ile 這樣的支持重疊 I/0 的函數(shù)時傳遞給完成端口的。我們可以看到,如果我們不是對文件設(shè)備做操作,該 結(jié)構(gòu)的成員變量就對我們幾乎毫無作用。我們需要附加信息,可以創(chuàng)建自己的結(jié)構(gòu),然后將 OVERL APPED 結(jié)構(gòu)變量作為我們結(jié)構(gòu)變量的第一個成員,然后傳 遞第一個成

20、員變量的地址給 ReadF ile 函數(shù)。因為類型匹配,當(dāng)然可以通過編譯。當(dāng) GetQ ueuedC ompletionStatus 函數(shù)返回時,我 們可以獲取到第一個成員變量的地址,然后一個簡單的強制轉(zhuǎn)換,我們就可以把它當(dāng)作完整的自定義結(jié)構(gòu)的指針使用,這樣就可以傳遞很多附加的數(shù)據(jù)了。太好了! 只有一點要注意,如果跨線程傳遞,請注意將數(shù)據(jù)分配到堆上,并且接收端應(yīng)該將數(shù)據(jù)用完后釋放。我們通常需要將 ReadF ile 這樣的異步函數(shù)的所需要的緩 沖區(qū)放到我們自定義的結(jié)構(gòu)中,這樣當(dāng) GetQ ueuedC ompletionStatus 被返回時,我們的自定義結(jié)構(gòu)的緩沖區(qū)變量中就存放了 I/0 操

21、作的 數(shù)據(jù)。C ompletionKey 和 OVERLA PPED 參數(shù),都可以通過 GetQ ueuedC ompletionStatus 函數(shù)獲得。線程的安全退出很多線程為了不止一次的執(zhí)行異步數(shù)據(jù)處理,需要使用如下語句w hile (true).GetQ ueuedCompletionStatus(.);.那么如何退出呢,答案就在于上面曾提到的 PostQ ueudC ompletionStatus 函數(shù),我們可以用它發(fā)送一個自定義的包含了 OVERL APPED 成員變量的結(jié)構(gòu)地址,里面包含一個狀態(tài)變量,當(dāng)狀態(tài)變量為退出標(biāo)志時,線程就執(zhí)行清除動作然后退出。5、Windows 完成端口的

22、實例代碼:DWO RD WINAPI WorkerThread(LPVO ID lpParam)U LO NG_PTR *PerHandleKey ; OVERLA PPED *Ov erlap;OVERLA PPEDPLUS *Ov erlapPlus,* newolp;DWO RD dw By tesXfered; w hile (1)ret = GetQ ueuedC ompletionStatus( hIocp,&dw By tesXfered,(PU LONG_PTR)&PerHandleKey , &Overlap,INFINITE);if (ret = 0)/ O peratio

23、n failed continue;OverlapPlus = CONTAINING_RECORD( Overlap, OVERL APPEDPLUS, ol); sw itch (Ov erlapPlus-O pCode)case OP_ACCEPT:/ C lient socket is contained in Ov erlapPlus.sclient/ A dd client to completion port C reateIoC ompletionPort( (HA NDLE)Ov erlapPlus-sclient, hIocp,(ULONG_PTR)0, 0);/ Need

24、a new OVERLA PPEDPLUS structure/ for the new ly accepted socket. Perhaps/ keep a look aside list of free structures. new olp = A llocateOverlappedPlus();if (!new olp)/ Errornew olp-s = Ov erlapPlus-sclient; new olp-O pCode = OP_READ;/ This function div pares the data to be sent PrepareSendBuffer(&ne

25、w olp-w buf);ret = WSASend( new olp-s, &new olp-w buf, 1,&new olp-dwBy tes, 0,&new olp.ol,NU LL);if (ret = SOCKET_ERROR)if (WSAGetLastError() != WSA _IO _PENDING)/ Error/ Put structure in look aside list for late r use F reeOverlappedPlus(OverlapPlus);/ Signal accept thread to issue another A cceptE

26、x SetEv ent(hA cceptThread);break;case OP_READ:/ Process the data read/ Repost the read if necessa ry , reusing the same/ receiv e buffer as beforememset(&Ov erlapPlus-ol, 0, sizeof(OVERL A PPED); ret = WSA Recv (OverlapPlus-s, &OverlapPlus-w buf, 1,&OverlapPlus-dwBy tes, &OverlapPlus-dwF lags, &Ove

27、rlapPlus-ol, NU LL);if (ret = SOCKET_ERROR)if (WSAGetLastError() != WSA _IO _PENDING)/ Errorbreak;case OP_WRITE:/ Process the data se nt, etc. break; / sw itch / w hile / WorkerThread查看以上代碼,注意如果 Ov erlapped 操作立刻失?。ū热纾祷?SOCKET_ERROR 或其他非 WSA _IO _PENDING 的錯誤),則 沒有任何完成通知時間會被放到完成端口隊列里。反之,則一定有相應(yīng)的通知時間被放

28、到完成端口隊列。更完善的關(guān)于Winsock 的完成端口機(jī)制,可以參考 MSDN 的 Microsoft PlatF ormSDK ,那里有完成端口的例子。訪問 HYPERLINK / /library /techart/msdn_serv rapp.htm 可以獲得更多信息。Linux 的 EPoll 模型Linux 2.6 內(nèi)核中提高網(wǎng)絡(luò) I/O 性能的新方法-epoll I/O 多路復(fù)用技術(shù)在比較多的 TCP 網(wǎng)絡(luò)服務(wù)器中有使用,即比較多的用到 sele ct 函數(shù)。1、為什么 select 落后首先,在 Linux 內(nèi)核中, select 所用到的 F D_SET 是有限的,即內(nèi)核中有個

29、參數(shù) F D_SETSIZE 定義了每個F D_SET 的句柄個數(shù),在我用的2.6.15-25-386 內(nèi)核中,該值是 1024 ,搜索內(nèi)核源代碼得到: include /linux/posix_ty pes.h:#define F D_SETSIZE1024也就是說,如果想要同時檢測 1025 個句柄的可讀狀態(tài)是不可能用 select 實現(xiàn)的?;蛘咄瑫r檢測 10 25 個句柄的可寫狀態(tài)也是不可能的。其次,內(nèi)核中實 現(xiàn) select 是用輪詢方法,即每次檢測都會遍歷所有 F D_SET 中的句柄,顯然, select 函數(shù)執(zhí)行時間與 F D_SET 中的句柄個數(shù)有一個比例關(guān)系, 即 selec

30、t 要檢測的句柄數(shù)越多就會越費時。當(dāng)然,在前文中我并沒有提及poll 方法,事實上用 select 的朋友一定也試過poll,我個人覺得 select 和 poll 大同小異,個人偏好于用 select 而已。2、內(nèi)核中提高 I/O 性能的新方法 epollepoll 是什么?按照 man 手冊的說法:是為處理大批量句柄而作了改進(jìn)的poll 。要使用 e poll 只需要這三個系統(tǒng)調(diào)用: epoll_ create(2 ), epoll_ctl(2) , epoll_w ait(2)。當(dāng)然,這不是 2.6 內(nèi)核才有的,它是在 2.5.44 內(nèi)核中被引進(jìn)的 (epoll(4) is a new

31、API introduced in Linux kernel 2.5.44 )Linux2.6 內(nèi)核 epoll 介紹先介紹 2 本書The Linux Netw orking A rchitecture-Design and Implementation of Netw ork Protocols in the Linux Kernel ,以 2.4 內(nèi)核講解 Linux TC P/IP 實現(xiàn),相當(dāng)不錯.作為一個現(xiàn)實世界中的實現(xiàn),很多時候你必須作很多權(quán)衡,這時候參考一個久經(jīng)考驗的系統(tǒng)更有實際意義。舉個例子,linux 內(nèi) 核中 sk_buff 結(jié)構(gòu)為了追求速度和安全,犧牲了部分內(nèi)存,所以在發(fā)

32、送 TCP 包的時候,無論應(yīng)用層數(shù)據(jù)多大 ,sk_buff 最小也有 272 的字節(jié).其實 對于 socket 應(yīng)用層程序來說,另外一本書 U NIX Netw ork Programming V olume 1 意義更大一點.2003 年的時候,這本書出了最新的第 3 版本,不過主要還是修訂第 2 版本。其中第 6 章I/O Multiplexing 是最重要的。 Stev ens 給出了網(wǎng)絡(luò) IO 的基本模型。在這里最重要的莫過于 select 模型和 A sy nchronous I/O 模型.從理論上說, A IO 似乎是最高效的,你的 IO 操作可以立即返回,然后等待 os 告訴你

33、IO 操作完成。但是一直以來,如何實現(xiàn)就沒有一個完美的方 案。最著名的 w indow s 完成端口實現(xiàn)的 A IO,實際上也是內(nèi)部用線程池實現(xiàn)的罷了,最后的結(jié)果是 IO 有個線程池,你應(yīng)用也需要一個線程池 . 很多文檔其實已經(jīng)指出了這帶來的線程 context-sw itch 帶來的代價。在linux 平臺上,關(guān)于網(wǎng)絡(luò) A IO 一直是改動最多的地方, 2.4 的年代就有很多 A IO 內(nèi)核 patch,最著名的應(yīng)該算是 SGI 那個。但是一直到 2.6 內(nèi)核發(fā)布,網(wǎng)絡(luò) 模塊的 A IO 一直沒有進(jìn)入穩(wěn)定內(nèi)核版本(大部分都是使用用戶線程模擬方法,在使用了 NPTL 的 linux 上面其實和

34、 w indow s 的完成端口基本上差不多 了)。2.6 內(nèi)核所支持的 A IO 特指磁盤的A IO -支持 io_submit(),io_getev ents()以及對 Direct IO 的支持(就是繞過 V FS 系統(tǒng)buffer 直接寫硬盤,對于流服務(wù)器在內(nèi)存平穩(wěn)性上有相當(dāng)幫助 )。所以,剩下的 select 模型基本上就是我們在 linux 上面的唯一選擇,其實,如果加上 no-block socket 的配置,可以完成一個 偽A IO 的實現(xiàn), 只不過推動力在于你而不是 os 而已。不過傳統(tǒng)的 select/poll 函數(shù)有著一些無法忍受的缺 點,所以改進(jìn)一直是 2.4-2.5

35、開發(fā)版本內(nèi)核的任務(wù), 包括/dev /poll, realtime signal 等等。最終, Dav ide Libenzi 開發(fā)的 epoll 進(jìn)入 2.6 內(nèi)核成為正式的解決方案3、epoll 的優(yōu)點 支持一個進(jìn)程打開大數(shù)目的 so cket 描述符(F D)select 最不能忍受的是一個進(jìn)程所打開的 F D 是有一定限制的,由 F D_SETSIZE 設(shè)置,默認(rèn)值是 2048。對于那些需要支持的上萬連接數(shù)目的IM 服務(wù)器來說顯 然太少了。這時候你一是可以選擇修改這個宏然后重新編譯內(nèi)核,不過資料也同時指出這樣會帶來網(wǎng)絡(luò)效率的下降,二是可以選擇多進(jìn)程的解決方案 (傳統(tǒng)的 A pache

36、方案),不過雖然 linux 上面創(chuàng)建進(jìn)程的代價比較小,但仍舊是不可忽視的,加上進(jìn)程間數(shù)據(jù)同步遠(yuǎn)比不上線程間同步的高效,所以也不是一種完美的方案。不過 epoll 則沒有這個限制,它所支持的 F D 上限是最大可以打開文件的數(shù)目,這個數(shù)字一般遠(yuǎn)大于 2048,舉個例子, 在 1GB 內(nèi)存的機(jī)器上大約是 10 萬左 右,具體數(shù)目可以 cat /proc/sy s/fs/file-ma x 察看,一般來說這個數(shù)目和 系統(tǒng)內(nèi)存關(guān)系很大。IO 效率不隨F D 數(shù)目增加而線性下降傳統(tǒng)的 select/poll 另一個致命弱點就是當(dāng)你擁有一個很大的socket 集合,不過由于網(wǎng)絡(luò)延時,任一時間只有部分的s

37、o cket 是活躍的, 但是select/poll 每次調(diào)用都會線性掃描全部的集合,導(dǎo)致效率呈現(xiàn)線性下降。但是epoll 不存在這個問題,它只會對 活躍的 so cket 進(jìn)行 操作-這是因為在內(nèi)核實現(xiàn)中 epoll 是根據(jù)每個 fd 上面的 callba ck 函數(shù)實現(xiàn)的。那么,只有 活躍 的 socket 才會主動的去調(diào)用 callba ck 函數(shù),其他idle 狀態(tài) socket 則不會,在這點上, epoll 實現(xiàn)了一個偽A IO ,因為這時候推動力在 os 內(nèi)核。在一些 benchmark 中,如果所有的 socket 基本上都是活躍的 -比如一個高速 LAN 環(huán)境,epoll 并

38、不比 se lect/poll 有什么效率,相 反,如果過多使用 epo ll_ctl, 效率相比還有稍微的下降。但是一旦使用 idle conne ctions 模擬 WA N 環(huán)境,epoll 的效率就遠(yuǎn)在 sele ct/poll 之上了。 使用 mmap 加速內(nèi)核與用戶空間的消息傳遞。這點實際上涉及到 epoll 的具體實現(xiàn)了。無論是 sele ct,poll 還是 epoll 都需要內(nèi)核把 F D 消息通知給用戶空間,如何避免不必要的內(nèi)存拷貝就很重要,在這點上, epoll 是通過內(nèi)核于用戶空間 mmap 同一塊內(nèi)存實現(xiàn)的。而如果你想我一樣從2.5 內(nèi)核就關(guān)注 epoll 的話,一定

39、不會忘記手工 mmap 這一步的。 內(nèi)核微調(diào)這一點其實不算 epoll 的優(yōu)點了,而是整個 linux 平臺的優(yōu)點。也許你可以懷疑 linux 平臺,但是你無法回避 linux 平臺賦予你微調(diào)內(nèi)核的能力。 比如,內(nèi)核 TCP/IP 協(xié)議棧使用內(nèi)存池管理 sk_buff 結(jié)構(gòu),那么可以在運行時期動態(tài)調(diào)整這個內(nèi)存po ol(skb_ head_pool) 的大小- - 通過echo XXXX/proc/sy s/net/core /hot_list_length 完成。再比如 liste n 函數(shù)的第 2 個參數(shù)(TCP 完成 3 次握手 的數(shù)據(jù)包隊列長度 ),也可以根據(jù)你平臺內(nèi)存大小動態(tài)調(diào)整。更

40、甚至在一個數(shù)據(jù)包面數(shù)目巨大但同時每個數(shù)據(jù)包本身大小卻很小的特殊系統(tǒng)上嘗試最新的 NA PI 網(wǎng) 卡驅(qū)動架構(gòu)。4、epoll 的工作模式令人高興的是, 2.6 內(nèi)核的 epoll 比其 2.5 開發(fā)版本的 /dev /epoll 簡潔了許多,所以,大部分情況下,強大的東西往往是簡單的。唯一有點麻煩是 epoll 有 2 種工作方式 LT 和 ET 。LT(lev el triggered) 是缺省的工作方式,并且同時支持 block 和 no-block socket. 在這種做法中,內(nèi)核告訴你一個文件描述符是否就緒了,然后你可以對這個就緒的 f d 進(jìn)行 IO 操作。如果你不作任何操作,內(nèi)核還

41、是會繼續(xù)通知你的,所以,這種模式編程出錯誤可能性要小一點。傳統(tǒng)的 select/poll 都是這種模型的代表ET (edge-triggered) 是高速工作方式,只支持 no-block socket 。在這種模式下,當(dāng)描述符從未就緒變?yōu)榫途w時,內(nèi)核通過epoll 告訴你。然后它會假設(shè)你知道文件描述符已經(jīng)就緒,并且不會再為那個文件描述符發(fā)送更多的就緒通知,直到你做了某些操作導(dǎo)致那個文件描述符不再為 就緒狀態(tài)了(比如,你在發(fā)送,接收或者接收請求,或者發(fā)送接收的數(shù)據(jù)少于一定量時導(dǎo)致了一個 EWOU LDBLOCK 錯誤)。但是請注意,如果一直不對這個 fd 作 IO 操作(從而導(dǎo)致它再次變成未就

42、緒 ),內(nèi)核不會發(fā)送更多的通知 (only once),不過在 TC P 協(xié)議中,ET 模式的加速效用仍需要更多的 benchmark 確認(rèn)。epoll 只有e poll_create,epoll_ctl,epo ll_w ait 3 個系統(tǒng)調(diào)用,具體用法請參考 HYPERLINK http:/w/ http:/w ww.xmailserv /linux-patches/nio-improv e.html ,在 HYPERLINK http:/w/ http:/w /rn/ 也有一個完整的例子,大家一看就知道如何使用了Leader/follow er 模式線程 pool 實現(xiàn),以及和 epol

43、l 的配合。5、 epoll 的使用方法首先通過 create_epoll(int ma xfds)來創(chuàng)建一個 epoll 的句柄,其中 ma xfds 為你 epo ll 所支持的最大句柄數(shù)。這個函數(shù)會返回一個新的epoll 句柄,之后的所有操作 將通過這個句柄來進(jìn)行操作。在用完之后,記得用close() 來關(guān)閉這個創(chuàng)建出來的 epoll 句柄。 之后在你的網(wǎng)絡(luò)主循環(huán)里面,每一幀的調(diào)用 epoll_w ait(int epfd, epoll_ev ent ev ents, int max ev ents, int timeout) 來查詢所有的網(wǎng)絡(luò)接口,看哪一個可以讀,哪一個可以寫了?;镜?/p>

44、語法為:nfds = epoll_w ait(kdpfd, ev ents, maxev ents, -1);其中 kdpfd 為用 epoll_create 創(chuàng)建之后的句柄, ev ents 是一個 epoll_ev ent* 的指針,當(dāng) epoll_w ait 這個函數(shù)操作成 功之后, epoll_ev ents 里 面將儲存所有的讀寫事件。 max_ev ents 是當(dāng)前需要監(jiān)聽的所有 socket 句柄數(shù)。最后一個 timeout 是 epoll_w ait 的超時,為 0 的時候表示馬上返回,為-1 的時候表示一直等下去,直到有事件范圍,為任意正整數(shù)的時候表示等這么長的時間,如果一直

45、沒有事件,則范圍。一般如果網(wǎng)絡(luò)主循環(huán)是單獨的線程的話,可以用 -1 來等,這樣可以保證一些效率,如果是和主邏輯在同一個線程的話,則可以用0 來保證主循環(huán) 的效 率。epoll_w ait 范圍之后應(yīng)該是一個循環(huán),遍利所有的事件:for(n = 0; n nfds; +n) if(ev entsn.data.fd = listener) / 如果是主 socket 的事件的話,則表示有新連接進(jìn)入了,進(jìn)行新連接的處理。client = accept(listener, (struct sockaddr *) &local, &addrlen);if(client 0) perror(accept

46、); continue;setnonblocking(client); / 將新連接置于非阻塞模式ev.events = EPO LLIN | EPO LLET; / 并且將新連接也加入 EPO LL 的監(jiān)聽隊列。注意,這里的參數(shù) EPO LLIN | EPO LLET 并沒有設(shè)置對寫 so cket 的監(jiān)聽,如果有寫操作的話,這個時候 epoll 是不會返回事件的,如果要對寫操作也監(jiān)聽的話,應(yīng)該是 EPO LLIN | EPO LLOU T | EPO LLETev.data.fd = client;if (epoll_ctl(kdpfd, EPO LL_CTL_A DD, client, &ev ) 0) / 設(shè)置好 ev ent 之后,將這個新的 ev ent 通過 epo ll_ctl 加入到 epoll 的監(jiān)聽隊列里面,這里用 EPO LL_CTL_ADD 來加一個新的 epoll 事件,通過 EPO LL_CTL_DEL 來減少一個 epoll 事件,通過 EPO LL_CTL_MOD 來改變一個事件的監(jiān)聽方式。fprintf(stderr, epoll set insertion error: fd=%d0, cli

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論