最近在學習網路程式設計,覺得select這塊的知識點確實比較難以理解,在學習socket網路通訊機制時,只是習慣寫諸如connect、accept、recv或recvfrom這樣的阻塞程式,(所謂阻塞方式block,顧名思義,就是程序或是執行緒執行到這些函式時必須等待某個事件的發生,如果事件沒有發生,程序或執行緒就被阻塞,函式不能立即返回)。可是使用select就可以完成非阻塞方式工作的程式,所謂非阻塞方式non-block,就是程序或執行緒執行此函式時不必非要等待事件的發生,一旦執行肯定返回,以返回值的不同來反映函式的執**況,如果事件發生則與阻塞方式相同,若事件沒有發生則返回乙個**來告知事件未發生,而程序或執行緒繼續執行,所以效率較高)它能夠監視我們需要監視的檔案描述符的變化情況——讀寫或是異常。下面詳細介紹一下!
select在socket程式設計總自己認為還是很重要的。這種i/o復用技術,可以大大的提高效率。
i/o多路復用技術適用的場合:
(1)當客戶處理多個描述符時(一般是互動式輸入和網路套介面),必須使用i/o復用技術。
(2)如果乙個tcp伺服器既要處理監聽套介面,又要處理已連線套介面,要用到i/o復用。
(3)如果乙個伺服器既要處理tcp,又要處理udp,要使用i/o復用。
(4)如果乙個伺服器要處理多個服務或多個協議,要使用i/o復用。
但是以上的幾種要求也可以用多程序多執行緒來完成,那麼這裡用i/o復用技術的優點是,系統開銷小,系統不必建立多執行緒多程序,也不必維護多程序和多執行緒,而大大減小了系統的開銷。
select函式理解:
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
在網路程式中,乙個程序同時處理多個檔案描述符是很常見的情況
select()系統呼叫可以使程序檢測同時等待的多個i/o裝置,當沒有裝置準備好時,select()阻塞,其中任一裝置準備好時,select()就返回
select的返回值有如下情況:
1. 正常情況下返回就緒的檔案描述符個數;
2. 經過了timeout時長後仍無裝置準備好,返回值為0;
3. 如果select被某個訊號中斷,它將返回-1並設定errno為eintr;
4. 如果出錯,返回-1並設定相應的errno。
select中間的3個引數readfds, writefds, errorfds指定我們要讓核心測試讀,寫,異常條件的描述符,如果對某一條件不感興趣,可以把他設為空指標。
引數readfds指定了被讀監控的檔案描述符集;
引數writefds指定了被寫監控的檔案描述符集;
引數exceptfds指定了被例外條件監控的檔案描述符集;
fd_set可以理解為乙個集合。用來標識對應的描述符的狀態。初始化是將檔案描述符集合全設為0,如果哪個客戶端準備好了要連線,則將客戶端描述符對應的在描述符集合中的位置置為1.系統提供了4個巨集對描述符集進行操作:
void fd_set(int fd, fd_set *fdset); //設定檔案描述符集fdset中對應於檔案描述符fd的位(設定為1)描述符集現在通常用整數陣列中的位域表示,陣列元素的每一位對應乙個檔案描述符void fd_clr(int fd, fd_set *fdset); //清除檔案描述符集fdset中對應於檔案描述符fd的位(設定為 0
void fd_isset(int fd, fd_set *fdset); //判斷檔案描述符集fdset中有沒有描述符fd
void fd_zero(fd_set *fdset); //清除檔案描述符集fdset中的所有位(既把所有位都設定為0)
例如,乙個整數佔32位,那麼整數陣列的第乙個元素代表檔案描述符0到31,陣列的第二個元素代表檔案描述符32到63,以此類推
巨集fd_set設定整數陣列中對應於fd檔案描述符的位為1,
巨集fd_clr設定整數陣列中對應於fd檔案描述符的位為0,
巨集fd_zero把fdset指向的檔案描述符集合對應的位全部初始化為0。
假設執行如下程式後:
fd_set readset;
fd_zero(&readset);
fd_set(5, &readset);
fd_set(33, &readset);
則檔案描述符集readset中對應於檔案描述符5和33的相應位被置為1,如圖1所示:
再執行如下程式後:
fd_clr(5, &readset);
則檔案描述符集readset對應於檔案描述符5的相應位被置為0,如圖2所示:
通常,作業系統通過巨集fd_setsize來宣告在乙個程序中select所能操作的檔案描述符的最大數目。
例如:在linux的標頭檔案中我們可以看到:
#define __fd_setsize 1024既定義fd_setsize為1024,乙個整數佔4個位元組,既32位,那麼就是用包含32個元素的整數陣列來表示檔案描述符集#define fd_setsize __fd_setsize
我們可以在標頭檔案中修改這個值來改變select使用的檔案描述符集的大小,但是必須重新編譯核心才能使修改後的值有效。
上面是對select的總結,那麼,有下面的幾個問題:
(1)i/o復用可以做到既要處理監聽套介面,又要處理已連線套介面,那麼select是怎麼做的?
監聽套介面負責處理要連線伺服器的客戶端。
已連線套介面負責處理已經連線的客戶端的讀寫操作。
當伺服器阻塞在select中時,只要乙個客戶端準備好連線伺服器,則select立即返回,但是伺服器並不知道是哪個客戶端的描述符的狀態改變,因此要通過fd_isset()來判斷。如果發現描述符集合fd_set中有值為1,那麼這個位置的描述符fd就是第一次連線伺服器,因此伺服器進行accept()操作。但如果select立即返回,通過fd_isset()檢查發現在描述符集合中對應的描述符的值沒有變,因此伺服器要進行對已連線客戶端的讀寫操作。
(2)由於是i/o復用,也就是說伺服器要監測好多的socket套接字,那麼不同的客戶端給伺服器傳送的請求不同,伺服器是怎麼知道哪個客戶端給它發的訊息並且回符相應的訊息給客戶端?
用select將檔案描述符集合從使用者態拷貝到核心態,通過fd_isset()來遍歷描述符集合,看哪個位置上為1,即根據位置就能知道是哪個客戶端的描述符。
select
的幾個缺點:
(1)select支援的檔案描述符集的數量太少,預設是1024,如果要增大預設的描述符集要重新編譯核心,麻煩。
(2)每次呼叫select,都需要把fd集合從使用者態拷貝到核心態,如果fd集合比較多,那麼這個開銷很大。
(3)select返回後原有的fd集合被改變,所以每次返回要將原有資料重置。
(4)每次select返回代表有客戶端狀態改變,因此都要遍歷整個fd集合去檢視哪個客戶端發生改變。開銷很大。
select實現I O復用
select 系統提供select函式來實現多路復用輸入 輸出模型。select系統呼叫是用來讓我們的程式監視多個檔案控制代碼的狀態變化的。程式會停在select這裡等待,直到被監視的檔案控制代碼有乙個或 多個發生了狀態改變。關於檔案控制代碼,其實就是乙個整數,我們最熟悉的控制代碼是0 1 2三 個...
IO多路復用 select
select系統呼叫的目的是 在一段指定時間內,監聽使用者感興趣的檔案描述符上的可讀 可寫和異常事件。poll和select應該被歸類為這樣的系統 呼叫,它們可以阻塞地同時探測一組支援非阻塞的io裝置,直至某乙個裝置觸發了事件或者超過了指定的等待時間 也就是說它們的職責不是做io,而是幫助 呼叫者尋...
IO復用 select系統呼叫
1 select函式 此函式用於在一段時間內,監聽使用者感興趣的檔案描述符上的可讀 可寫和異常等事件。includeint select int nfds,fd set readfds,fd set writefds,fd set exceptfds,struct timeval timeout f...