http://www.blogbus.com/eastsun-logs/7762285.html
摘自《UNIX NETWORK PROGRAMMING》chapter 6
p144
對於常見的input操作,一般分爲兩個步驟:
1. wait to be ready
2. copy data from kernel buffer to user buffer
常見的I/O模型:(參見以上步驟)
1. 阻塞I/O 用戶進程執行1、2
2. 非阻塞I/O 用戶輪巡1,然後執行2
3. 多路複用select、poll 用戶調用select等待kernel返回,然後執行2
4. 信號驅動I/O(SIGIO) 用戶設置信號處理函數(sigio)後,正常繼續其他函數,當kernel返回SIGIO後,執行2
5. 異步信號I/O kernel執行1、2後通知用戶進程(不常用)
# include <sys/select.h>
# include <sys/time.h>
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
作用:
1. 內核掃描maxfdp1個描述符(常規情況下,系統最大值FD_SETSIZE=1024,可修改)
2. 內核查看readset, writeset, exceptset集中的描述符是否準備好
3. 等待超過timeout時間而沒有描述符準備好,select返回
struct timeval {
long tv_sec;
long tv_usec;
}
注:timeval == 0 馬上返回
timeval == NULL 永久阻塞
void FD_ZERO(fd_set *fdset);
void FD_SET(int fd, fd_set *fdset);
void FD_CLR(int fd, fd_set *fdset);
void FD_ISSET(int fd, fd_set *fdset);
注意,如循環調用select,多次檢查同一描述符,必須在調用select之前重新設定初始值。select函數會在每次返回時,將沒有ready的描述符所在的位清0
任何信號將使select()出錯返回。而且BSD系統的select將不可能自動再啓動
在標準select()中,返回後系統不會改變timeval的值,而linux系統例外。
exception condition:
當前只支持 out-of-band和the presence of control status information to be read from the master side of a pseudo terminal that has been put into packet mode.(???)
一般的系統實現中,將fd_set設置爲整數隊列,每個整數元素中的一位表示爲一個描述符
FD_SETSIZE定義在<sys/select.h>中,如果要更改的話,必須重新編譯內核
select()的常見錯誤:
1. maxfdp1必須指定爲最大描述符值+1
2. 每次select返回後,都會將fd_set中的初值清0,除非該描述符已經準備好。因此,如果要重新檢查描述符,必須再次賦初值
在select返回的描述符個數中,如果同一描述符同時爲讀、寫準備好,則記數2次
早期的SVR4版本只記錄1次。(bug)
select中準備好的意義:
爲讀準備好:
1. 在socket接受緩存中的數據 >= SO_RCVLOWAT,默認情況下,SO_RCVLOWAT=1
2. 對端寫關閉,read返回0
3. 在listenfd中的complete queue 中,有entry
4. socket出錯。read返回-1
爲寫準備好:
1. 在socket發送緩存中的數據 >= SO_SNDLOWAT,默認情況下,SO_SNDLOWAT=2048
2. 對端讀關閉,kernel返回SIGPIPE
3. socket出錯,write返回-1
注意,當socket出錯時,將在readset/writeset分別賦值
使用select應該注意:
由於同時處理單個描述符的讀寫,可能出現此描述符的寫(或讀)操作已全部完成,而相對的另一個讀(或寫)操作還沒有完成。爲了獲得這些數據,進程必須調用shutdown()以進行半關閉
# include <sys/socket.h>
int shutdown(int sockfd, int howto);
其中,howto可以置爲SHUT_RD/SHUT_WR/SHUTRDWR
區別於close():
1. shutdown不查看描述符計數器。直接進行半關閉
2. close只能進行全關閉,shutdown可以選擇一端或兩端
服務器處理客戶機請求的原則:永遠不要將服務器阻塞在一個客戶連接中。(可能被dos攻擊)
# include <sys/select.h>
# include <signal.h>
# include <time.h>
int pselect(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *except_set, const struct timespec *timeout, const sigset_t sigmask);