再回首一些概念
- 阻塞IO
- 資料沒有準備好, 讀操作就會阻塞
- 資料不能立即被收時, 寫操作就會阻塞
- 打開檔案時阻塞, 直到某些條件發生
- 非阻塞IO
- 立即傳回, 并用錯誤值來表示目前的狀态
- 指定非阻塞方式
- 打開時指定O_NONBLOCK 标志
- 使用fcntl 打開或關閉非阻塞方式
- 網絡程式設計時, 可以使用非阻塞, 用輪詢方式發送
- 使用多線程可以避免使用非阻塞IO, 但是同步開銷較大
多路IO
- 當程式需要同時從多個檔案描述符讀資料時
- 使用多程序/多線程, 同步複雜, 程序線程開銷
- 使用非阻塞IO, 交替輪詢
- 通過信号使用異步IO, 無法判斷哪個IO完成
- 多路IO: 把關心的IO放入一個清單/檔案描述符集合, 調用多路函數
- 多路IO函數阻塞, 直到有一個IO資料準備好後傳回
- 傳回後告訴調用者哪個描述符準備好了
select實作說明
- 調用select時通過參數告訴核心使用者感興趣的IO描述符
- 關心的IO狀态: 輸入,輸出或錯誤三種狀态
- 調用者等待指定的時間
- 傳回之後核心告訴調用者哪些個描述符準備好了
- 哪些描述符發生了變化
- 調用傳回後對準備好的描述符調用讀寫操作
- 不關心的描述符集合傳NULL
函數詳解
- man手冊
select()
/* According to POSIX 1003.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
- 傳回值:如果成功,傳回所有sets中描述符的個數;如果逾時,傳回0;如果出錯,傳回-1。
- 監視readfds來檢視是否read的時候會被堵塞
注意,即便到了end-of-file,fd也是可讀的。
- 監視writefds看寫的時候會不會被堵塞。
- 監視exceptfd是否出現了異常。
主要用來讀取OOB資料,異常并不是指出錯。
- 注意當一個套接口出錯時,它會變得既可讀又可寫。
- 如果有了狀态改變,會将其他fd清零,隻有那些發生改變了的fd保持置位,以用來訓示set中的哪一個改變了狀态。
- 參數n是所有set裡所有fd裡,具有最大值的那個fd的值加1
與fd_set有關的函數
fd_set是一個位向量, 每位表示一個描述符
int FD_ISSET(int fd, fd_set *fdset);
//測試某個描述符是否在集合内用來訓示一個fd是不是一個set的一部分。他很有用,用來看select後哪一個fd可用了。
void FD_CLR(int fd, fd_set *fdset);
//從集合内把一個描述符移除
void FD_SET(int fd, fd_set *fdset);
//把一個描述符加入集合
void FD_ZERO(fd_set *fdset);
//清空描述符集合
關于time_out
- timeout是從調用開始到select傳回前,會經曆的最大等待時間。
- 兩種特殊情況:如果為值為0,會立刻傳回。如果timeout是NULL,會阻塞式等待。
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
- 一些調用使用3個空的set, n為zero, 一個非空的timeout來達到較為精确的sleep.
- 為了較好的可移植性,timeout在循環中需要被重新賦初值。
- timeout== NULL
- 無限等待
- 被信号打斷時傳回-1, errno 設定成 EINTR
- timeout->tv_sec == 0 && timeout ->tv_usec == 0
- 不等待立即傳回
- timeout->tv_sec != 0 || timeout ->tv_usec != 0
- 等待特定時間長度, 逾時傳回0
注意事項
- 可以把同一個描述符同時放入讀和寫集合
- 當讀和寫準備好時, 傳回值的計數分别加1次
- 普通檔案的三種狀态總是傳回準備好的狀态
- 是否阻塞式IO不會影響select的結果
- 如果一個描述符到了檔案結尾,select傳回的狀态是準備好
- 對一個準備好的描述符, 讀出長度是0表示到達結尾