原型:
#include<sys/time.h> #include<unistd.h> int select(int maxfd, fd_set *rdset, fd_set *wrest, fd_set *exset, struct timeval *timeout);
參數:
- maxfd:描述需要監視最大文件描述符+1
- rdset:監視的可讀文件描述符的集合
- wrset:監視的可寫文件描述符的集合
- exset:監視的異常文件描述符的集合
- struct timeval:描述一段時間長度,如果在這個時間內,需要監視的描述符沒有事件發生,返回0
返回值:
- 超時返回0
- 失敗返回-1
- 成功返回大於0的整數,這個整數表示就緒描述符的數目
int FD_ZERO(fd_set *fdset):將指定的文件描述符集情況,在對文件描述符集合進行設置之前,必須對其進行初始化。如果不請空,由於在系統分配內存后,通常不做清空處理,所以結果時不可知的。 int FD_SET(int fd, fd_set *fdset); 用於在文件描述符集合中添加一個新的文件描述符 int FD_CLR(int fd, fd_set *fdset); 用於在文件描述符集合中刪除一個新的文件描述符 int FD_ISSET(int fd, fd_set *fdset); 用於測試指定的文件描述符是否在該集合中。
注意:fd_set通常是一個整數數組,其中每個整數中的每一位對應一個描述符(矢量),例如,使用一個32位整數,那么該數組的第一個元素對應於描述符0~31,第二個元素對應於描述符32~63,一次類推。
select 系統調用用途在於在一段指定的時間內,監聽用戶感興趣的文件描述符上可讀可寫和異常事件。
select使用范例:
當聲明了一個文件描述符集之后,必須用FD_ZERo將所有位置置0,然后再將我們所感興趣的描述符所對應的位置位:
- fd_set rset;
- int fd;
- FD_ZERO(&rset);
- FD_SET(fd,&rset);
- FD_SET(stdin,&rset);
然后調用select函數,阻塞等待文件描述符事件的到來如果超過設定的事件,則不再等待,繼續往下執行
select(fd+1,&rset,NULL,NULL,NULL);
select返回后,用FD_ISSET測試給定位是否置位。
if(FD_ISSET(fd, &rset)) { … //do something }
深入理解select模型:
理解selecr模型的關鍵是在於理解fd_set為了說明方便,取fd_set長度為1字節,fd_set的每一位bit可以對應一個文件描述符,則1字節長的fd_set最大可以對應8個fd。
- 執行fd_set set; FD_ZERO(&set); 則set用位表示為 0000,0000
- 若fd = 5,執行FD_SET(fd,&set); 后set變為0001,0000 (第5位置1)
- 若再加入fd = 2,fd = 1,則set變為0001,0011
- 執行select(6,&set,0,0,0)阻塞等待
- 若fd=1,fd=2上有事件發生,則select返回,此時set變為0000,0001。注意:沒有事件發生的fd=5被清空。
select模型特點:
- 可監控的文件描述符個數取決於sizeof(fd_set)的值。每個bit可以表示一個文件描述符。
- 將fd加入到select監控集的同事,還要再使用一個數據結構array保存放到select監控集中的fd,一是用於在select返回后,array作為元數據和fd_set進行FD_ISSET判斷。二是select返回后會把以前加入的但無事件發生的fd清空,則每次開始select前都要重新從array取得fd逐一加入人(FD_ZERO最先),掃描array的同時取得fd的最大值maxfd,用於select的第一個參數
可見select模型必須在select前循環加上fd,取maxfd,select 返回后在利用FD_ISSET判斷是否有事件發生。
select優勢:
用戶可以在一個線程內同時處理多個socket的IO請求,在網絡編程中,當涉及到多客戶訪問服務器的情況,除了使用fork多個進程來處理每個客戶的連接,還可以使用select來處理。
select缺點:
select本質是通過設置或者檢查存放fd標志位的數據結構來進行下一步處理,這樣帶來的缺點:
- l 單個進程可監視的fd數量被限制,即能監聽端口的大小有限。一般來說這個數目和系統內存關系很大,具體數目可以cat /proc/sys/fs/file-max查看。
- l 對socket進行掃描時是線性掃描,即采用輪詢的方法,效率較低,當套接字比較多的時候,每次select(0都要通過遍歷FD_SETSIZE個socket來完成調度,不管哪個socket是活躍的,都需要遍歷一遍。這就很浪費CPU事件,如果能給套接字注冊某個回調函數,當他們活躍時,自動完成相關操作,這就避免了輪詢,這正是epoll與kqueue做的
- l 需要維護一個用來存放大量fd的數據結構,這樣會使得用戶空間和內核空間在傳遞該結構體時賦值開銷大。