I/O 多路複用的設計初衷就是解決這樣的場景。我們可以把标準輸入、套接字等都看做 I/O 的一路,多路複用的意思,就是在任何一路 I/O 有“事件”發生的情況下,通知應用程式去處理相應的 I/O 事件,這樣我們的程式就變成了“多面手”,在同一時刻仿佛可以處理多個 I/O 事件。
select 函數就是這樣一種常見的 I/O 多路複用技術,我們将在後面繼續講解其他的多路複用技術。使用 select 函數,通知核心挂起程序,當一個或多個 I/O 事件發生後,控制權返還給應用程式,由應用程式進行 I/O 事件的處理。
select 函數的聲明:
int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
傳回:若有就緒描述符則為其數目,若逾時則為0,若出錯則為-1
在這個函數中,maxfd 表示的是待測試的描述符基數,它的值是待測試的最大描述符加 1。比如現在的 select 待測試的描述符集合是{0,1,5},那麼 maxfd 就是 6,為啥是 6,而不是 5呢? 我會在下面進行解釋。
緊接着的是三個描述符集合,分别是讀描述符集合 readset、寫描述符集合 writeset 和異常描述符集合 exceptset,這三個分别通知核心,在哪些描述符上檢測資料可以讀,可以寫和有異常發生。
以下宏具有重要的作用:
void FD_ZERO(fd_set *fdset);
void FD_SET(int fd, fd_set *fdset);
void FD_CLR(int fd, fd_set *fdset);
int FD_ISSET(int fd, fd_set *fdset);
FD_ZERO 用來将這個向量的所有元素都設定成 0;
FD_SET 用來把對應套接字 fd 的元素,a[fd]設定成 1;
FD_CLR 用來把對應套接字 fd 的元素,a[fd]設定成 0;
FD_ISSET 對這個向量進行檢測,判斷出對應套接字的元素 a[fd]是 0 還是 1。
具體的可以參考bitmap算法處理,就是拿位來代表具體的資料。大神們都這樣操作。
最後一個參數是 timeval 結構體時間:
struct timeval {
long tv_sec;
long tv_usec;
};
第一個可能是 倆都 設定成空 (NULL),表示如果沒有 I/O 事件發生,則 select 一直等待下去。
第二個可能是兩個中設定一個非零的值,這個表示等待固定的一段時間後從 select 阻塞調用中傳回,
第三個可能是将 tv_sec 和 tv_usec 都設定成 0,表示根本不等待,檢測完畢立即傳回。這種情況使用得比較少。
直接上示例代碼+注釋
int main(int argc, char **argv) {
if (argc != 2) {
error(1, 0, "usage: select01 <IPaddress>");
}
int socket_fd = tcp_client(argv[1], SERV_PORT);
char recv_line[MAXLINE], send_line[MAXLINE];
int n;
fd_set readmask;
fd_set allreads;
FD_ZERO(&allreads);
//設定第0位需要檢測
FD_SET(0, &allreads);
//設定第socket_fd位需要檢測
FD_SET(socket_fd, &allreads);
for (;;) {
//為readmask指派 莫非是傳說中的 傳值和傳引用 ----?
readmask = allreads;
// 開始檢測
int rc = select(socket_fd + 1, &readmask, NULL, NULL, NULL);
//如果傳回的結果小于等于0 列印錯誤資訊
if (rc <= 0) {
error(1, errno, "select failed");
}
//檢測socket_fd位是否有響應的事件發生
if (FD_ISSET(socket_fd, &readmask)) {
//處理讀事件
n = read(socket_fd, recv_line, MAXLINE);
if (n < 0) {
error(1, errno, "read error");
} else if (n == 0) {
error(1, 0, "server terminated \n");
}
recv_line[n] = 0;
fputs(recv_line, stdout);
fputs("\n", stdout);
}
if (FD_ISSET(STDIN_FILENO, &readmask)) {
if (fgets(send_line, MAXLINE, stdin) != NULL) {
int i = strlen(send_line);
if (send_line[i - 1] == '\n') {
send_line[i - 1] = 0;
}
printf("now sending %s\n", send_line);
size_t rt = write(socket_fd, send_line, strlen(send_line));
if (rt < 0) {
error(1, errno, "write failed ");
}
printf("send bytes: %zu \n", rt);
}
}
}
}
注意事項:
循環中://為readmask指派
readmask = allreads;
每次循環完畢後都要重新指派,因為集合會根據具體的事件來改變。
參考資料《網絡程式設計實戰》-盛嚴敏