select的機制中提供了一個fd_set的資料結構,實際上是long類型的數組, 每一個數組元素都能與一開啟的檔案控制代碼(不管是Socket控制代碼,還是其他 檔案或命名管道或裝置控制代碼)建立聯絡,建立聯絡的工作由程式員完成,
當調用select()時,由核心根據IO狀態修改fd_set的內容,由此來通知執行了select()的進程哪一Socket或檔案可讀,下面具體解釋:
int select(nfds, readfds, writefds, exceptfds, timeout)
fd_set *readfds, *writefds, *exceptfds;
struct timeval *timeout;
ndfs:需要監視的檔案控制代碼
readfds:select監視的可讀檔案控制代碼集合。
writefds: select監視的可寫檔案控制代碼集合。
exceptfds:select監視的異常檔案控制代碼集合。
timeout:本次select()的逾時結束時間。(見/usr/sys/select.h, 可精確至百萬分之一秒!)
當readfds或writefds中映象的檔案可讀或可寫或逾時,本次select() 就結束返回。程式員利用一組系統提供的宏在select()結束時便可判 斷哪一檔案可讀或可寫。對Socket編程特別有用的就是readfds。 幾隻相關的宏解釋如下:
FD_ZERO(fd_set *fdset):清空fdset與所有檔案控制代碼的聯絡。
FD_SET(int fd, fd_set *fdset):建立檔案控制代碼fd與fdset的聯絡。
FD_CLR(int fd, fd_set *fdset):清除檔案控制代碼fd與fdset的聯絡。
FD_ISSET(int fd, fdset *fdset):檢查fdset聯絡的檔案控制代碼fd是否可讀寫,>0表示可讀寫。
(關於fd_set及相關宏的定義見/usr/include/sys/types.h)
select 傳回值
負值:select錯誤
正值:某些檔案可讀寫或出錯
0:等待逾時,沒有可讀寫或錯誤的檔案
具體解釋select的參數:
int maxfdp:是一個整數值,是指集合中所有檔案描述符的範圍,即所有檔案描述符的最大值加1,不能錯!在Windows中這個參數的值無所謂,可以設定不正確。
fd_set* readfds:是指向fd_set結構的指標,這個集合中應該包括檔案描述符,可以從這些檔案中讀取資料,可以傳入NULL值,表示不關心任何檔案的讀變化。
fd_set* writefds:是指向fd_set結構的指標,這個集合中應該包括檔案描述符,檔案描述符的寫變化,可以傳入NULL值,表示不關心任何檔案的寫變化。
fd_set * errorfds:同上面兩個參數的意圖,用來監視檔案錯誤異常
struct timeval* timeout:是select的逾時時間,這個參數至關重要,它可以使select處於三種狀態:
第一:若將NULL以形參傳入,就是將select置於阻塞狀態,一定等到監視檔案描述符集合中某個檔案描述符發生變化為止
第二:若將時間值設為0秒0毫秒,就變成一個純粹的非阻塞函數,不管檔案描述符是否有變化,都立刻返回繼續執行,檔案無變化返回0,有變化返回一個正值
第三:timeout的值大於0,這就是等待的逾時時間,即select在timeout時間內阻塞,有事件到來就返回了,
FD_ACCEPT的實現?依然如上,因為客戶方socket請求串連時,會發送串連請求報文,此時select()當然會結束,FD_ISSET(sockfd)當然大於零,因為有報文可讀嘛!至於這方面的應用,主要在於服務方的父Socket,你若不喜歡主動accept(),可改為如上機制來accept()。
討論關於利用select()檢測對方Socket關閉的問題:
仍然是本地Socket有東東可讀,因為對方Socket關閉時,會發一個關閉串連通知報文,會馬上被select()檢測到的。關於TCP的串連(三向交握)和關閉(二次握手)機制,敬請參考有關TCP/IP的書籍。
不知是什麼原因,UNIX好象沒有提供通知進程關於Socket或Pipe對方關閉的訊號,也可能是cpu所知有限。總之,當對方關閉,一執行recv()或read(),馬上回返回-1,此時全域變數errno的值是115,相應的sys_errlist[errno] 為"Connect refused"(請參考/usr/include/sys/errno.h)。所以,在上篇的for(;;)...select()程式塊中,當有東西可讀時,一定要檢查recv()或
read()的傳回值,返回-1時要作出關斷本地Socket的處理(select有讀是 因為發來的是結束報文嗎?而read卻讀不到這個報文?),否則select()會一直認為有東西讀,其結果曾幾令cpu傷心欲斷針腳。不信你可以試試:不檢查recv()返回結果,且將收到的東東(實際沒收到)寫至標準輸出... 在有名管道的編程中也有類似問題出現。具體處理詳見拙作:發布一個有用的Socket客戶方原碼。
至於主動寫Socket時對方突然關閉的處理則可以簡單地捕捉訊號SIGPIPE並作出相應關斷本地Socket等等的處理。SIGPIPE的解釋是:寫入無讀者方的管道。 在此不作贅述,請詳man signal。
以上是cpu在作tcp/ip資料轉送實驗積累的經驗,若有錯漏,請狂炮擊之。
補充關於select在非同步(非阻塞)connect中的應用,剛開始搞socket編程的時候 我一直都用阻塞式的connect,
用select可以很好地解決這一問題.大致過程是這樣的:
1.將開啟的socket設為非阻塞的,可以用fcntl(socket, F_SETFL, O_NDELAY)完成(有的系統用FNEDLAY也可).
2.發connect調用,這時返回-1,但是errno被設為EINPROGRESS,意即connect仍舊在進行還沒有完成.
3.將開啟的socket設進被監視的可寫(注意不是可讀)檔案集合用select進行監視,如果可寫,用
getsockopt(socket, SOL_SOCKET, SO_ERROR, &error, sizeof(int));
來得到error的值,如果為零,則connect成功.
在有了select後可以寫出像樣的網路程式來!舉個簡單的例子,就是從網路上接受資料寫入一個檔案中。
main()
{
int sock;
FILE *fp;
struct fd_set fds;
struct timeval timeout={3,0}; //select等待3秒,3秒輪詢,要非阻塞就置0
........
while(1)
{
FD_ZERO(&fds); //每次迴圈都要清空集合,否則不能檢測描述符變化
FD_SET(sock,&fds);
FD_SET(fp,&fds);
maxfdp=sock>fp?sock+1:fp+1; //描述符最大值加1
switch(select(maxfdp,&fds,&fds,NULL,&timeout)) //select使用
{
case -1: exit(-1);break;
case 0:break;
default:
if(FD_ISSET(sock,&fds)) //測試sock是否可讀,即是否網路上有資料
{
recvfrom(sock,buffer,256,.....);//接受網路資料
if(FD_ISSET(fp,&fds)) //測試檔案是否可寫
fwrite(fp,buffer...);//寫入檔案
buffer清空;
}
}
}
}