因?yàn)?a href="http://www.babyishan.com/tag/select">select可以使開(kāi)發(fā)者在同時(shí)等待多個(gè)文件緩沖區(qū),可減少I(mǎi)O等待的時(shí)間,能夠提高進(jìn)程的IO效率。select()函數(shù)是IO多路復(fù)用的函數(shù),允許程序監(jiān)視多個(gè)文件描述符,等待所監(jiān)視的一個(gè)或者多個(gè)文件描述符變?yōu)椤皽?zhǔn)備好”的狀態(tài);所謂的”準(zhǔn)備好“狀態(tài)是指:文件描述符不再是阻塞狀態(tài),可以用于某類(lèi)IO操作了,包括可讀,可寫(xiě),發(fā)生異常三種。
本教程操作環(huán)境:linux7.3系統(tǒng)、Dell G3電腦。
select是一個(gè)計(jì)算機(jī)函數(shù),位于頭文件#include
1. select函數(shù)介紹
select函數(shù)是IO多路復(fù)用的函數(shù),它主要的功能是用來(lái)等文件描述符中的事件是否就緒,select可以使我們?cè)谕瑫r(shí)等待多個(gè)文件緩沖區(qū) ,減少I(mǎi)O等待的時(shí)間,能夠提高進(jìn)程的IO效率。
select()函數(shù)允許程序監(jiān)視多個(gè)文件描述符,等待所監(jiān)視的一個(gè)或者多個(gè)文件描述符變?yōu)椤皽?zhǔn)備好”的狀態(tài)。所謂的”準(zhǔn)備好“狀態(tài)是指:文件描述符不再是阻塞狀態(tài),可以用于某類(lèi)IO操作了,包括可讀,可寫(xiě),發(fā)生異常三種
2. select函數(shù)參數(shù)的介紹
???????int?select(int?nfds,?fd_set?*readfds,?fd_set?*writefds, ??????????????????fd_set?*exceptfds,?Struct?timeval?*timeout);
ndfs
等待的文件描述符的最大值+1,例如:應(yīng)用進(jìn)程想要去等待文件描述符3,5,8的事件,則
nfds=max(3,5,8)+1;
fd_set類(lèi)型
readfds和writefds,exceptfds的類(lèi)型都是fd_set,那么fd_set類(lèi)型是什么呢?
- fd_set類(lèi)型本質(zhì)是一個(gè)位圖,位圖的位置 表示 相對(duì)應(yīng)的文件描述符,內(nèi)容表示該文件描述符是否有效,1代表該位置的文件描述符有效,0則表示該位置的文件描述符無(wú)效。
- 如果將文件描述符2,3設(shè)置位圖當(dāng)中,則位圖表示的是為1100。
- fd_set的上限是1024個(gè)文件描述符。
readfds
- readfds是 等待讀事件的文件描述符集合,.如果不關(guān)心讀事件(緩沖區(qū)有數(shù)據(jù)),則可以傳NULL值。
- 應(yīng)用進(jìn)程和內(nèi)核都可以設(shè)置readfds,應(yīng)用進(jìn)程設(shè)置readfds是為了通知內(nèi)核去等待readfds中的文件描述符的讀事件.而 內(nèi)核設(shè)置readfds是為了告訴應(yīng)用進(jìn)程哪些讀事件生效
writefds
與readfds類(lèi)似,writefds是等待寫(xiě)事件(緩沖區(qū)中是否有空間)的集合,如果不關(guān)心寫(xiě)事件,則可以傳值NULL。
exceptfds
如果內(nèi)核等待相應(yīng)的文件描述符發(fā)生異常,則將失敗的文件描述符設(shè)置進(jìn)exceptfds中,如果不關(guān)心錯(cuò)誤事件,可以傳值NULL。
timeout
設(shè)置select在內(nèi)核中阻塞的時(shí)間,如果想要設(shè)置為非阻塞,則設(shè)置為NULL。如果想讓select阻塞5秒,則將創(chuàng)建一個(gè)struct timeval time={5,0};
其中struct timeval的結(jié)構(gòu)體類(lèi)型是:
???????????struct?timeval?{ ???????????????long????tv_sec;?????????/*?seconds?*/ ???????????????long????tv_usec;????????/*?microseconds?*/ ???????????};
返回值
- 如果沒(méi)有文件描述符就緒就返回0;
- 如果調(diào)用失敗返回-1;
- 如果timeout中中readfds中有事件發(fā)生,則返回timeout剩下的時(shí)間。
3.select的工作流程
應(yīng)用進(jìn)程和內(nèi)核都需要從readfds和writefds獲取信息,其中,內(nèi)核需要從readfds和writefds知道哪些文件描述符需要等待,應(yīng)用進(jìn)程需要從readfds和writefds中知道哪些文件描述符的事件就緒.
如果我們要不斷輪詢(xún)等待文件描述符,則應(yīng)用進(jìn)程需要不斷的重新設(shè)置readfds和writefds,因?yàn)槊恳淮握{(diào)用select,內(nèi)核會(huì)修改readfds和writefds,所以我們需要在 應(yīng)用程序 中 設(shè)置一個(gè)數(shù)組 來(lái)保存程序需要等待的文件描述符,保證調(diào)用 select 的時(shí)候readfds 和 writefds中的將如下:
4.Select服務(wù)器
?如果是一個(gè)select服務(wù)器進(jìn)程,則服務(wù)器進(jìn)程會(huì)不斷的接收有新鏈接,每個(gè)鏈接對(duì)應(yīng)一個(gè)文件描述符,如果想要我們的服務(wù)器能夠同時(shí)等待多個(gè)鏈接的數(shù)據(jù)的到來(lái),我們監(jiān)聽(tīng)套接字listen_sock讀取新鏈接的時(shí)候,我們需要將新鏈接的文件描述符保存到read_arrys數(shù)組中,下次輪詢(xún)檢測(cè)的就會(huì)將新鏈接的文件描述符設(shè)置進(jìn)readfds中,如果有鏈接關(guān)閉,則將相對(duì)應(yīng)的文件描述符從read_arrys數(shù)組中拿走。
一張圖看懂select服務(wù)器:
簡(jiǎn)易版的select服務(wù)器:
server.hpp文件:
#pragma?once?? ??#include<iostream> ??#include<sys> ??#include<sys>???? ??#include<netinet>? ??#include<string.h> ??using?std::cout; ??using?std::endl; ??#define?BACKLOG?5?? ?????? ??namespace?sjp{???? ????class?server{???? ??????public:???? ??????static?int?Socket(){???? ????????int?sock=socket(AF_INET,SOCK_STREAM,0);???? ????????if(sock>0)???? ????????return?sock;???? ????????if(sock<p>?select_server.hpp文件</p> <pre class="brush:js;toolbar:false;">#pragma?once ??#include<vector> ??#include"server.hpp" ??#include<unistd.h> ??#include<time.h> ?? ??namespace?Select{ ????class?select_server{ ??????private: ????????int?listen_sock;//監(jiān)聽(tīng)套接字???? ????????int?port;???? ?????????? ??????public:???? ????????select_server(int?_port):port(_port){}???? ?????? ????????//初始化select_server服務(wù)器???? ????????void?InitServer(){???? ??????????listen_sock=sjp::server::Socket();???? ??????????sjp::server::Bind(listen_sock,port);???? ??????????sjp::server::Listen(listen_sock);???? ????????}???? ?????? ?????? ????????void?Run(){???? ??????????std::vector<int>?readfds_arry(1024,-1);//readfds_arry保存讀事件的文件描述符???? ??????????readfds_arry[0]=listen_sock;//將監(jiān)聽(tīng)套接字保存進(jìn)readfds_arry數(shù)組中???? ??????????fd_set?readfds;???? ??????????while(1){???? ??????????FD_ZERO(&readfds);???? ??????????int?nfds=0;???? ??????????//將read_arry數(shù)組中的文件描述符設(shè)置進(jìn)程readfds_arry位圖中???? ??????????for(int?i=0;i&?readfds_arry,fd_set?readfds){ ????????for(int?i=0;i<readfds_arry.size>&?fds_arry,int?fd){ ???????for(int?i=0;i<fds_arry.size><p>?select_server.cc文件</p> <pre class="brush:js;toolbar:false;">#include"select_server.hpp" int?main(int?argv,char*?argc[]){ ??if(argv!=2){ ????coutInitServer(); ??sl->Run(); }
測(cè)試:
5.Select的缺陷
- 由于fd_set的上限是1024,所以select能等待的讀事件的文件描述符和寫(xiě)事件的文件描述是有上限的,如果作為一個(gè)大型服務(wù)器,能夠同時(shí)鏈接的客戶(hù)端是遠(yuǎn)遠(yuǎn)不夠的。
- 每次應(yīng)用進(jìn)程調(diào)用一次select之前,都需要重新設(shè)定writefds和readfds,如果進(jìn)行輪詢(xún)調(diào)用select,這對(duì)影響cpu效率。
- 內(nèi)核每一次等待文件描述符 都會(huì)重新掃描所有readfds或者writefds中的所有文件描述符,如果有較多的文件描述符,則會(huì)影響效率。
推薦學(xué)習(xí):Linux視頻教程