I/O多路復(fù)用是在多線程或多進(jìn)程編程中常用技術(shù)。主要是通過(guò)select/epoll/poll三個(gè)函數(shù)支持的。在此主要對(duì)select和epoll函數(shù)詳細(xì)介紹。
該函數(shù)運(yùn)行進(jìn)程指示內(nèi)核等待多個(gè)事件中的任何一個(gè)發(fā)生,并只有一個(gè)或多個(gè)事件發(fā)生或經(jīng)歷一段指定的時(shí)間后才喚醒它。
調(diào)用select告知內(nèi)核對(duì)哪些描述符(就讀、寫(xiě)或異常條件)感興趣以及等待多長(zhǎng)時(shí)間。我們感興趣的描述符不局限于套接字,任何描述符都可以使用select來(lái)測(cè)試。
函數(shù)原型:
#include<sys/select.h>#include<sys/time.h>int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout); 返回:若有就緒描述符則為其數(shù)目,若超時(shí)則為0,若出錯(cuò)則為-1
永遠(yuǎn)等待下去:僅在有一個(gè)描述符準(zhǔn)備好I/O時(shí)才返回,將其設(shè)為空指針
等待一段固定時(shí)間:在有一個(gè)描述符準(zhǔn)備好I/O時(shí)返回,但是不超過(guò)由該參數(shù)所指向的timeval結(jié)構(gòu)中指定的秒數(shù)和微秒數(shù)。
根本不等待:檢查描述符后立即返回,這就是輪詢。為此,該參數(shù)必須指向一個(gè)timeval結(jié)構(gòu),但是其中的值必須設(shè)置為0
最后一個(gè)參數(shù)timeout,它告知內(nèi)核等待所指定描述符中的任何一個(gè)就緒可花多長(zhǎng)時(shí)間。該參數(shù)有三種可能:
三個(gè)參數(shù)readset,writeset,exceptset指定我們要讓內(nèi)核測(cè)試讀、寫(xiě)和異常條件的描述符。
如何給這三個(gè)參數(shù)的每一個(gè)參數(shù)指定一個(gè)或多個(gè)描述符值是一個(gè)設(shè)計(jì)上的問(wèn)題。select使用描述符集,通常是一個(gè)整數(shù)數(shù)組,其中每個(gè)整數(shù)中的每一位對(duì)應(yīng)一個(gè)描述符。舉例來(lái)說(shuō),假設(shè)使用32位整數(shù),那么該數(shù)組的第一個(gè)元素對(duì)應(yīng)于描述符0~31,第二個(gè)元素對(duì)應(yīng)于描述符32~63,以此類推。所有這些實(shí)現(xiàn)細(xì)節(jié)都與應(yīng)用程序無(wú)關(guān),它們隱藏在名為fd_set的數(shù)據(jù)類型和以下四個(gè)宏中:
void FD_ZERO(fd_set *fdset); //clear all bits in fdsetvoid FD_SET(int fd, fd_set *fdset); //turn on the bit for fd in fdsetvoid FD_CLR(int fd, fd_set *fdset); //turn off the bit for fd in fdsetint FD_ISSET(int fd, fd_set *fdset); //is the bit for fd on in fdset?
我們分配一個(gè)fd_set數(shù)據(jù)類型的描述符集,并用這些宏設(shè)置或測(cè)試該集合中的每一位,也可以用C語(yǔ)言中的賦值語(yǔ)句把它賦值成另外一個(gè)描述符集。
注意:前面所討論的每個(gè)描述符占用整數(shù)數(shù)組中的一位的方法僅僅是select函數(shù)的可能實(shí)現(xiàn)之一。
maxfdp1參數(shù)指定待測(cè)試的描述符個(gè)數(shù),它的值是待測(cè)試的大描述符加1。描述符0,1,2,...,直到maxfdp1 - 1均被測(cè)試。
select函數(shù)修改由指針readset,writeset和exceptset所指向的描述符集,因而這三個(gè)參數(shù)都是值-結(jié)果參數(shù)。該函數(shù)返回后,我們使用FD_ISSET宏來(lái)測(cè)試fd_set數(shù)據(jù)類型中的描述符。描述符集內(nèi)任何與未就緒描述符所對(duì)應(yīng)的位返回時(shí)均清成0.為此,每次重新調(diào)用select函數(shù)時(shí),我們都得再次把所有描述符集內(nèi)所關(guān)心的位均置為1
滿足下列四個(gè)條件之一的任何一個(gè)時(shí),一個(gè)套接字準(zhǔn)備好讀:
該套接字接收緩沖區(qū)中的數(shù)據(jù)字節(jié)數(shù)大于等于套接字接收緩沖區(qū)低水位標(biāo)記的當(dāng)前大小。對(duì)于這樣的套接字執(zhí)行讀操作不會(huì)阻塞并將返回一個(gè)大于0的值(也就是返回準(zhǔn)備好讀入的數(shù)據(jù))。我們使用SO_RECVLOWAT套接字選項(xiàng)設(shè)置套接字的低水位標(biāo)記。對(duì)于TCP和UDP套接字而言,其默認(rèn)值為1
該連接的讀半部關(guān)閉(也就是接收了FIN的TCP連接)。對(duì)這樣的套接字的讀操作將不阻塞并返回0(也就是返回EOF)
該套接字時(shí)一個(gè)監(jiān)聽(tīng)套接字且已完成的連接數(shù)不為0。
其上有一個(gè)套接字錯(cuò)誤待處理。對(duì)這樣的套接字的讀操作將不阻塞并返回-1(也就是返回一個(gè)錯(cuò)誤),同時(shí)把errno設(shè)置為確切的錯(cuò)誤條件。這些待處理錯(cuò)誤也可以通過(guò)SO_ERROR套接字選項(xiàng)調(diào)用getsockopt獲取并清除。
下列四個(gè)條件的任何一個(gè)滿足時(shí),一個(gè)套接字準(zhǔn)備好寫(xiě):
該套接字發(fā)送緩沖區(qū)中的可用字節(jié)數(shù)大于等于套接字發(fā)送緩沖區(qū)低水位標(biāo)記的當(dāng)前大小,并且或該套接字已連接,或者該套接字不需要連接(如UDP套接字)。這意味著如果我們把這樣的套接字設(shè)置成非阻塞的,寫(xiě)操作將不阻塞并返回一個(gè)正值(如由傳輸層接收的字節(jié)數(shù))。我們使用SO_SNDLOWAT套接字選項(xiàng)來(lái)設(shè)置該套接字的低水位標(biāo)記。對(duì)于TCP和UDP而言,默認(rèn)值為2048
該連接的寫(xiě)半部關(guān)閉。對(duì)這樣的套接字的寫(xiě)操作將產(chǎn)生SIGPIPE信號(hào)
使用非阻塞式connect套接字已建立連接,或者connect已經(jīng)已失敗告終
其上有一個(gè)套接字錯(cuò)誤待處理。對(duì)這樣的套接字的寫(xiě)操作將不阻塞并返回-1(也就是返回一個(gè)錯(cuò)誤),同時(shí)把errno設(shè)置為確切的錯(cuò)誤條件。這些待處理錯(cuò)誤也可以通過(guò)SO_ERROR套接字選項(xiàng)調(diào)用getsockopt獲取并清除。
如果一個(gè)套接字存在帶外數(shù)據(jù)或者仍處于帶外標(biāo)記,那么它有異常條件待處理。
注意:當(dāng)某個(gè)套接字上發(fā)生錯(cuò)誤時(shí),它將由select標(biāo)記為既可讀又可寫(xiě)
接收低水位標(biāo)記和發(fā)送低水位標(biāo)記的目的在于:允許應(yīng)用進(jìn)程控制在select可讀或可寫(xiě)條件之前有多少數(shù)據(jù)可讀或有多大空間可用于寫(xiě)。
任何UDP套接字只要其發(fā)送低水位標(biāo)記小于等于發(fā)送緩沖區(qū)大小(默認(rèn)應(yīng)該總是這種關(guān)系)就總是可寫(xiě)的,這是因?yàn)閁DP套接字不需要連接。
函數(shù)原型:
#include<poll.h>int poll(struct pollfd *fdarray, unsigned long nfds, int timeout); 返回:若有就緒描述符則為數(shù)目,若超時(shí)則為0,若出錯(cuò)則為-1
第一個(gè)參數(shù)是指向一個(gè)結(jié)構(gòu)數(shù)組第一個(gè)元素的指針。每個(gè)數(shù)組元素都是一個(gè)pollfd結(jié)構(gòu),用于指定測(cè)試某個(gè)給定描述符fd的條件。
struct pollfd{ int fd; //descriptor to check short event; //events of interest on fd short revents; //events that occurred on fd};
要測(cè)試的條件由events成員指定,函數(shù)在相應(yīng)的revents成員中返回該描述符的狀態(tài)。(每個(gè)描述符都有兩個(gè)變量,一個(gè)為調(diào)用值,另一個(gè)為返回結(jié)果,從而避免使用值-結(jié)果參數(shù)。)
poll事件
epoll是Linux特有的I/O復(fù)用函數(shù)。它在實(shí)現(xiàn)和使用上與select、poll有很大的差異。
首先,epoll使用一組函數(shù)來(lái)完成任務(wù),而不是單個(gè)函數(shù)。
其次,epoll把用戶關(guān)心的文件描述符上的事件放在內(nèi)核里的一個(gè)事件表中,從而無(wú)須像select和poll那樣每次調(diào)用都要重復(fù)傳入文件描述符集或事件集。
但epoll需要使用一個(gè)額外的文件描述符,來(lái)唯一標(biāo)識(shí)內(nèi)核中的這個(gè)事件表
epoll文件描述符使用如下方式創(chuàng)建:
#include<sys/epoll.h>int epoll_create(int size);
size參數(shù)完全不起作用,只是給內(nèi)核一個(gè)提示,告訴它事件表需要多大。該函數(shù)返回的文件描述符將用作其他所有epoll系統(tǒng)調(diào)用的第一個(gè)參數(shù),以指定要訪問(wèn)的內(nèi)核事件表。
下面的函數(shù)用來(lái)操作epoll的內(nèi)核事件表:
#include<sys/epoll.h>int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 返回:若成功返回0,失敗返回-1,并置errno
fd參數(shù)是要操作的文件描述符,op參數(shù)則指定操作類型。操作類型有以下三類:
EPOLL_CTL_ADD, 往事件表中注冊(cè)fd上的事件
EPOLL_CTL_MOD, 修改fd上的注冊(cè)事件
EPOLL_CTL_DEL, 刪除fd上的注冊(cè)事件
event指定事件,它是epoll_event結(jié)構(gòu)指針類型,epoll_event的定義如下:
strcut epoll_event{ __uint32_t events; //epoll事件 epoll_data_t data; //用戶數(shù)據(jù)};
其中,events成員描述事件類型。epoll支持的事件類型同poll基本相同。表示epoll事件類型的宏在poll對(duì)應(yīng)的宏前加上"E",比如epoll的數(shù)據(jù)可讀事件是EPOLLIN。
epoll有兩個(gè)額外的事件類型——EPOLLET和EPOLLONESHOT。它們對(duì)于epoll的高效運(yùn)作非常關(guān)鍵。
data成員用于存儲(chǔ)用戶數(shù)據(jù),是一個(gè)聯(lián)合體:
typedef union epoll_data{ void *ptr; int fd; uint32_t u32; uint64_t u64; }epoll_data_t;
其中4個(gè)成員用得最多的是fd,它指定事件所從屬的目標(biāo)文件描述符。
epoll系列系統(tǒng)調(diào)用的主要接口是epoll_wait函數(shù),它在一段超時(shí)時(shí)間內(nèi)等待一組文件描述符上的事件,其原型如下:
#include<sys/epoll.h>int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 返回:若成功返回就緒的文件描述符個(gè)數(shù),失敗時(shí)返回-1,并置errnoo
maxevents參數(shù)指定最多監(jiān)聽(tīng)多少個(gè)事件,它必須大于0
event_wait函數(shù)如果檢測(cè)到事件,就將所有就緒事件從內(nèi)核事件表(由epfd參數(shù)指定)中復(fù)制到它的第二個(gè)參數(shù)events指向的數(shù)組中。這個(gè)數(shù)組只用于輸出epoll_wait檢測(cè)到的就緒事件,而不像select和poll的數(shù)組參數(shù)那樣既用于傳入用戶注冊(cè)的事件,又用于輸出內(nèi)核檢測(cè)到的就緒事件。這就極大地提高了應(yīng)用程序索引就緒文件描述符的效率。
下面代碼給出 poll和epoll在使用上的差別:
//如何索引poll返回的就緒文件描述符int ret = poll(fds, MAX_EVENT_NUMBER, -1);//必須遍歷所有已注冊(cè)文件描述符并找到其中的就緒者for(int i = 0; i < MAX_EVENT_NUMBER; ++i){ if(fds[i].revents & POLLIN) //判斷第 i 個(gè)文件描述符是否就緒 { int sockfd = fds[i].fd; //處理sockfd } }//如何索引epoll返回的文件描述符int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);//僅遍歷就緒的ret個(gè)文件描述符for(int i = 0; i < ret; ++i){ int sockfd = events[i].data.fd; //sockfd肯定就緒,直接處理}
LT和ET模式
LT(Level Trigger,電平觸發(fā))模式:是默認(rèn)工作模式,在這種模式下的epoll相當(dāng)于一個(gè)效率較高的poll。當(dāng)epoll_wait檢測(cè)到其上有事件發(fā)生并將此事件通知應(yīng)用程序后,應(yīng)用程序可以不立即處理該事件。這樣,當(dāng)應(yīng)用程序下一次調(diào)用epoll_wait時(shí),epoll_wait還會(huì)再次向應(yīng)用程序通告此事件。
ET(Edge Trigger,邊沿觸發(fā))模式。對(duì)于ET工作模式下的文件描述符,當(dāng)epoll_wait檢測(cè)到其上有事件發(fā)生并將此事件通知應(yīng)用程序后,應(yīng)用程序必須立即處理該事件,因?yàn)楹罄m(xù)的epoll_wait調(diào)用將不再向應(yīng)用程序通知這一事件。
ET模式在很大程度上降低了同一個(gè)epoll事件被重復(fù)觸發(fā)的次數(shù)。因此效率要比LT模式高。
每個(gè)使用ET模式的文件描述符都應(yīng)該是非阻塞的。如果文件描述符是阻塞的,那么讀或?qū)懖僮鲗?huì)因?yàn)闆](méi)有后續(xù)的時(shí)間而一直處于阻塞狀態(tài)(饑渴狀態(tài))
EPOLLONESHOT事件
即使使用ET模式,一個(gè)socket上的某個(gè)事件還是可能被觸發(fā)多次。這在并發(fā)程序中引起一個(gè)問(wèn)題。比如一個(gè)線程(或進(jìn)程)在讀取完某個(gè)socket上的數(shù)據(jù)后開(kāi)始處理這些數(shù)據(jù),而在數(shù)據(jù)的處理過(guò)程中該socket上又有新數(shù)據(jù)可讀(EPOLLIN再次被觸發(fā)),此時(shí)另外一個(gè)線程被喚醒來(lái)讀取這些新的數(shù)據(jù)。于是出現(xiàn)了兩個(gè)線程同時(shí)操作一個(gè)socket的場(chǎng)面。這當(dāng)然不是我們期望的。我們期望的是一個(gè)socket連接在任一時(shí)刻都只被一個(gè)線程處理。
對(duì)于注冊(cè)了EPOLLONESHOT事件的文件描述符,操作系統(tǒng)最多觸發(fā)其上注冊(cè)的一個(gè)可讀、可寫(xiě)或異常事件,且只觸發(fā)一次,除非我們使用epoll_ctl函數(shù)重置該文件描述符上的EPOLLONESHOT事件。這樣,當(dāng)一個(gè)線程在處理某個(gè)socket時(shí),其他線程時(shí)不可能有機(jī)會(huì)操作該socket的。但反過(guò)來(lái)思考,注冊(cè)了EPOLLONESHOT事件的socket一旦被某個(gè)線程處理完畢,該線程就應(yīng)該立即重置這個(gè)socket上的EPOLLONESHOT事件,以確保這個(gè)socket下一次可讀時(shí),其EPOLLIN事件能被觸發(fā),進(jìn)而讓其他工作線程有機(jī)會(huì)繼續(xù)處理這個(gè)socket.
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無(wú)理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場(chǎng)景需求。
當(dāng)前文章:網(wǎng)絡(luò)編程:I/O復(fù)用-創(chuàng)新互聯(lián)
鏈接URL:http://www.ekvhdxd.cn/article4/dojioe.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供標(biāo)簽優(yōu)化、用戶體驗(yàn)、軟件開(kāi)發(fā)、網(wǎng)站導(dǎo)航、定制開(kāi)發(fā)、定制網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容