select---詳解

前言

傳統(tǒng)的服務(wù)器框架都是阻塞I/O類型的垦沉,為了提高并發(fā)性將迭代型(while)改為多進程亚情,但是多進程開銷較大歉闰,然后設(shè)計多線程的模型昙沦,節(jié)約系統(tǒng)開銷琢唾,但是考慮到傳統(tǒng)的accept函數(shù)是阻塞函數(shù),然后設(shè)計多個進程或是線程同時accept的方式提高并發(fā)處理能力盾饮。本質(zhì)沒有偏離阻塞I/O的基本很特性采桃,其原因就是我們的API接口均是阻塞型的,沒有請求就死等丐谋,沒有連接就耗著芍碧。思考有沒有一種非阻塞的方式可以實現(xiàn)同時監(jiān)聽多個socket?
默認方式下号俐,accept處于阻塞狀態(tài)泌豆,將套接字文件屬性設(shè)置為非阻塞時,accept處于非阻塞狀態(tài)(其實與該套接字相關(guān)的系統(tǒng)調(diào)用都是非阻塞的了)吏饿。

阻塞與非阻塞

  1. 阻塞與非阻塞是程序(線程)等待消息通知時的狀態(tài)角度來說的踪危。
  2. 阻塞模式:程序(線程)在執(zhí)行I/O操作完成前會一直等待蔬浙,不會把程序的控制權(quán)交給CPU。如(connect贞远、accept畴博、recv、recvfrom函數(shù))默認都是阻塞的蓝仲。

阻塞模式下俱病,進程或是線程執(zhí)行到這些函數(shù)時必須等待某個事件的發(fā)生,如果事件沒有發(fā)生袱结,進程或線程就被阻塞(死等在被阻塞的地方)亮隙,函數(shù)不會立即返回。

  1. 非阻塞模式:程序(線程)在執(zhí)行I/O操作時垢夹,進行系統(tǒng)調(diào)用時在不能立刻得到結(jié)果之前溢吻,該函數(shù)不會阻塞當(dāng)前線程,而會立刻返回果元。
  • 非阻塞的方式可以明顯的提高CPU的利用率促王,但是增加系統(tǒng)的線程切換增加。所以增加的CPU執(zhí)行時間能不能補償系統(tǒng)的切換成本需要好好評估
  • 非阻塞non-block模式下而晒,進程或線程執(zhí)行此函數(shù)時不必非要等待事件的發(fā)生蝇狼,一旦執(zhí)行肯定返回,以返回值的不同來反映函數(shù)的執(zhí)行情況倡怎,如果事件發(fā)生則與阻塞方式相同题翰,若事件沒有發(fā)生則返回一個代碼來告知事件未發(fā)生,而進程或線程繼續(xù)執(zhí)行诈胜,所以非阻塞模式效率較高。

select描述(同步IO復(fù)用)

  • select系統(tǒng)調(diào)用可以實現(xiàn)一個進程同時監(jiān)聽多個文件描述符(socket描述符)的狀態(tài)變化冯事,默認的當(dāng)調(diào)用select()函數(shù)焦匈,程序會阻塞到這里(除非設(shè)置timeout),直到被監(jiān)視的文件描述符有某一個或多個發(fā)生了狀態(tài)改變昵仅,則select函數(shù)返回缓熟。

select機制中提供了一個 fd_set數(shù)據(jù)結(jié)構(gòu),僅僅包含一個整形數(shù)組摔笤,數(shù)組的每一個元素的每一位(bit)標(biāo)記一個文件描述符够滑,某個文件描述的狀態(tài)改變時,設(shè)置相應(yīng)位為1吕世,表示就緒彰触。fd_set能容納的文件描述符的數(shù)量是由FD_SETSIZE決定,默認為1024命辖,除非修改宏定義况毅,并編譯內(nèi)核分蓖。

select函數(shù)原型

NAME
       select,  pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous
       I/O multiplexing

SYNOPSIS
       /* According to POSIX.1-2001 */
       #include <sys/select.h>

       /* According to earlier standards */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>

       int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

       void FD_CLR(int fd, fd_set *set); //用來清除描述詞組set中相關(guān)fd 的位
       int  FD_ISSET(int fd, fd_set *set);//用來測試描述詞組set中相關(guān)fd 的位是否為真
       void FD_SET(int fd, fd_set *set);//用來設(shè)置描述詞組set中相關(guān)fd的位
       void FD_ZERO(fd_set *set);  // 用來清除描述詞組set的全部位。

       #include <sys/select.h>

       int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask);

參數(shù)詳解

  • ndfs:select監(jiān)視的文件描述符數(shù)尔许,視進程中打開的文件數(shù)而定么鹤,一般設(shè)置為監(jiān)視各文件中的最大文件描述符值加1。
  • readfds:這個文件描述符集合監(jiān)視文件集中的任何文件是否有數(shù)據(jù)可讀味廊,當(dāng)select函數(shù)返回的時候蒸甜,readfds將清除其中不可讀的文件描述符,只留下可讀的文件描述符余佛。
  • writefds:這個文件描述符集合監(jiān)視文件集中的任何文件是否有數(shù)據(jù)可寫柠新,當(dāng)select函數(shù)返回的時候,writefds將清除其中不可寫的文件描述符衙熔,只留下可寫的文件描述符登颓。
  • exceptfds:這個文件集將監(jiān)視文件集中的任何文件是否發(fā)生錯誤。
  • timeout:本次select()的超時結(jié)束時間红氯,使得select處于三種不同的狀態(tài):
  1. 若將NULL以形參傳入框咙,即不傳入時間結(jié)構(gòu),就是將select置于阻塞狀態(tài)痢甘,一定等到監(jiān)視文件描述符集合中某個文件描述符發(fā)生變化為止喇嘱;
  1. 若將時間值設(shè)為0秒0毫秒,就變成一個純粹的非阻塞函數(shù)塞栅,不管文件描述符是否有變化者铜,都立刻返回繼續(xù)執(zhí)行,文件無變化返回0放椰,有變化返回一個正值作烟;
  2. timeout的值大于0,這就是等待的超時時間砾医,即select在timeout時間內(nèi)阻塞拿撩,超時時間之內(nèi)有事件到來就返回了,否則在超時后不管怎樣一定返回如蚜,返回值同上述压恒。

Linux Socket編程中select的常見用處

  • accept函數(shù)的非阻塞實現(xiàn){服務(wù)器端}


    select.png

如果將正在listen的socket設(shè)置到readfds中,調(diào)用select错邦,如果有客戶端connect探赫,select將返回正值,通過宏FD_ISSET可檢測到該socket可讀撬呢,此時再用accept接受新的socket伦吠,并通過FD_SET將accept返回的new socket描述符添加到readfds中,若是active connection 就調(diào)用recv()讀取數(shù)據(jù)。

  • connect函數(shù)的非阻塞實現(xiàn)(TCP){客戶端}

EINPROGRESS(man connect的描述)
The socket is nonblocking and the connection cannot be completed
immediately. It is possible to select(2) or poll(2) for comple‐
tion by selecting the socket for writing. After select(2) indi‐
cates writability, use getsockopt(2) to read the SO_ERROR option
at level SOL_SOCKET to determine whether connect() completed
successfully (SO_ERROR is zero) or unsuccessfully (SO_ERROR is
one of the usual error codes listed here, explaining the reason
for the failure).
描述了connect出錯時的一種值errno值:EINPROGRESS讨勤,這種錯誤發(fā)生在非阻塞的socket調(diào)用從connect箭跳,而連接又沒有立即建立時,在這種情況下潭千,可以調(diào)用select谱姓、poll等函數(shù)來監(jiān)聽這個連接的失敗的socket上的可寫事件,當(dāng)select刨晴、poll等函數(shù)返回時屉来,在利用getsockopt來讀取錯誤碼并清除該socket上的錯誤,如果錯誤碼是0狈癞,表示連接建立成功茄靠。

主動寫socket時對方突然關(guān)閉連接的處理,則可以簡單地捕捉信號SIGPIPE并作出相應(yīng)關(guān)閉本地socket等等的處理蝶桶。SIGPIPE的解釋是:寫入無讀者方的管道慨绳。

缺點

  1. 單個進程可以監(jiān)聽的描述符的個數(shù)限制。
  2. 開銷大(內(nèi)存拷貝):包含大量文件描述符的數(shù)組被整體復(fù)制于用戶態(tài)和內(nèi)核的地址空間之間真竖,每次在select調(diào)用之前脐雪,需要將監(jiān)聽的描述符從用戶態(tài)空間拷貝到內(nèi)核的地址空間,在select調(diào)用都返回整個用戶注冊的事件集合(包括據(jù)就緒的和為就緒的)恢共,它的開銷隨著文件描述符數(shù)量的增加而線性增大战秋。
  3. 效率問題:內(nèi)核在幫助應(yīng)用程序監(jiān)聽多個描述符的時候,是一種輪循檢測就緒事件的方式讨韭,掃描判斷哪個socket描述符的位是就緒的脂信,這是耗時的,時間復(fù)雜度為O(n)透硝。
  • poll的改進:只是描述fd集合的方式不同狰闪,

  • epoll的改進

  1. 沒有最大并發(fā)連接的限制,上限是最大可以打開文件的數(shù)目濒生。cat /proc/sys/fs/file-max察看尝哆。
  2. 共享內(nèi)存:在epoll_ctl函數(shù)中,每次注冊新的事件到epoll句柄中時(在epoll_ctl中指定EPOLL_CTL_ADD)甜攀,會把所有的fd拷貝進內(nèi)核,而不是在epoll_wait的時候重復(fù)拷貝琐馆。epoll保證了每個fd在整個過程中只會拷貝一次规阀。利用mmap()文件映射內(nèi)存加速與內(nèi)核空間的消息傳遞,即epoll使用mmap減少復(fù)制開銷瘦麸。
  3. 效率提升:只有活躍可用的FD才會調(diào)用callback函數(shù)谁撼;即Epoll最大的優(yōu)點就在于它只管你“活躍”的連接,而跟連接總數(shù)無關(guān)。
  1. select厉碟,poll每次調(diào)用都要把fd集合從用戶態(tài)往內(nèi)核態(tài)拷貝一次喊巍,并且要把current往設(shè)備等待隊列中掛一次,而epoll只要一次拷貝箍鼓,而且把current往等待隊列上掛也只掛一次(在epoll_wait的開始崭参,注意這里的等待隊列并不是設(shè)備等待隊列,只是一個epoll內(nèi)部定義的等待隊列)款咖。這也能節(jié)省不少的開銷何暮。
  2. select,poll實現(xiàn)需要自己不斷輪詢所有fd集合铐殃,直到設(shè)備就緒海洼,期間可能要睡眠和喚醒多次交替。而epoll其實也需要調(diào)用epoll_wait不斷輪詢就緒鏈表富腊,期間也可能多次睡眠和喚醒交替坏逢,但是它是設(shè)備就緒時,調(diào)用回調(diào)函數(shù)赘被,把就緒fd放入就緒鏈表中是整,并喚醒在epoll_wait中進入睡眠的進程。雖然都要睡眠和交替帘腹,但是select和poll在“醒著”的時候要遍歷整個fd集合贰盗,而epoll在“醒著”的時候只要判斷一下就緒鏈表是否為空就行了,這節(jié)省了大量的CPU時間阳欲。這就是回調(diào)機制帶來的性能提升舵盈。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市球化,隨后出現(xiàn)的幾起案子秽晚,更是在濱河造成了極大的恐慌,老刑警劉巖筒愚,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赴蝇,死亡現(xiàn)場離奇詭異,居然都是意外死亡巢掺,警方通過查閱死者的電腦和手機句伶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來陆淀,“玉大人考余,你說我怎么就攤上這事≡唬” “怎么了楚堤?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我身冬,道長衅胀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任酥筝,我火速辦了婚禮滚躯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘樱哼。我一直安慰自己哀九,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布搅幅。 她就那樣靜靜地躺著阅束,像睡著了一般。 火紅的嫁衣襯著肌膚如雪茄唐。 梳的紋絲不亂的頭發(fā)上息裸,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天,我揣著相機與錄音沪编,去河邊找鬼呼盆。 笑死,一個胖子當(dāng)著我的面吹牛蚁廓,可吹牛的內(nèi)容都是我干的访圃。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼相嵌,長吁一口氣:“原來是場噩夢啊……” “哼腿时!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起饭宾,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤批糟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后看铆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體徽鼎,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年弹惦,在試婚紗的時候發(fā)現(xiàn)自己被綠了否淤。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡棠隐,死狀恐怖石抡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情宵荒,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站报咳,受9級特大地震影響侠讯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜暑刃,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一厢漩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧岩臣,春花似錦溜嗜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至谷扣,卻和暖如春土全,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背会涎。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工裹匙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人末秃。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓概页,卻偏偏與公主長得像,于是被迫代替她去往敵國和親练慕。 傳聞我的和親對象是個殘疾皇子惰匙,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354