select函數
select()函數允許進程指示內核等待多個事件中的任何一個發生,並只在有一個或多個事件發生或經歷一段指定時間后才喚醒它
#include <sys/select.h> #include <sys/time.h>
// 返回值:若有就緒描述符,則返回就緒描述符數目;若超時則返回0,出錯返回-1 int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
select的參數:
maxfdp1:
指定待測試的描述符個數,它的值是待測試的最大描述符加1
指定待測試的描述符個數,它的值是待測試的最大描述符加1
readset、writeset、exceptset:
指定讓內核測試讀、寫、異常條件的描述符
指定讓內核測試讀、寫、異常條件的描述符
異常條件:
某個套接字的帶外數據到達
某個已置為分組模式的偽終端存在可以從其主端讀取的控制狀態信息
fd_set *: select使用的描述符集
系統提供了4個宏對描述符集進行操作:
系統提供了4個宏對描述符集進行操作:
#include <sys/select.h> #include <sys/time.h> void FD_SET(int fd, fd_set *fdset); // 設置文件描述符集fdset中對應於文件描述符fd的位(設置為1) void FD_CLR(int fd, fd_set *fdset); // 清除文件描述符集fdset中對應於文件描述符fd的位(設置為0) void FD_ISSET(int fd, fd_set *fdset); // 檢測文件描述符集fdset中對應於文件描述符fd的位是否被設置 void FD_ZERO(fd_set *fdset); // 清除文件描述符集fdset中的所有位(既把所有位都設置為0
注意:
在使用FD_ISSET測試fd_set數據類型中的描述符后,描述符集內任何與未就緒描述符對應
的位返回時均被清0,因此,每次重新調用select函數時,都需要將描述符集內所關心的位置為1
描述符必須被初始化,因為作為自動變量分配的一個描述符如果沒有被初始化,那么發生的后果
不可預期
readset、writeset、exceptset三個參數中,若對其中任何參數條件不感興趣,則可將其設為
NULL
timeout:
告知內核等待指定描述符中的任何一個就緒的時間限制
timeval結構:
a.設為空指針:永遠等待下去,僅在有描述符就緒時才返回
b.正常設置timeout,在不超過timeout設置的時間內,在有描述符就緒時返回
c.將timeout.tv_sec和timeout.tv_usec都設為0:檢查描述符后立即返回(輪詢)
timeval結構:
struct timeval{ long tv_sec; // 秒 long tv_usec; // 微秒 }
timeout參數的三種可能:
a.設為空指針:永遠等待下去,僅在有描述符就緒時才返回
b.正常設置timeout,在不超過timeout設置的時間內,在有描述符就緒時返回
c.將timeout.tv_sec和timeout.tv_usec都設為0:檢查描述符后立即返回(輪詢)
a b 兩種情形的等待通常會被進程在 等待期間捕獲的信號中斷,並 從信號處理函數返回,因此在
考慮可移植性的情況下,若我們在捕獲信號,那必須做好 select返回EINTR錯誤的准備
描述符的就緒條件:
可讀條件:
1.該套接字接收緩沖區中的數據字節數大於等於套接字接收緩沖區低水位標記的當前大小
2.該連接的讀半部關閉(即接收了FIN的TCP連接)
3.該套接字是一個監聽套接字且已完成的連接數不為0
4.該套接字上有一個套接字錯誤待處理
可讀條件:
1.該套接字接收緩沖區中的數據字節數大於等於套接字接收緩沖區低水位標記的當前大小
2.該連接的讀半部關閉(即接收了FIN的TCP連接)
3.該套接字是一個監聽套接字且已完成的連接數不為0
4.該套接字上有一個套接字錯誤待處理
可寫條件:
1.該套接字發送緩沖區中的可用空間字節數大於等於套接字發送緩沖區低水位標記的當前大小
2.該連接的寫半部關閉
3.使用非阻塞式connect的套接字已建立連接,或者connect已經以失敗告終
4.該套接字上有一個套接字錯誤待處理
1.該套接字發送緩沖區中的可用空間字節數大於等於套接字發送緩沖區低水位標記的當前大小
2.該連接的寫半部關閉
3.使用非阻塞式connect的套接字已建立連接,或者connect已經以失敗告終
4.該套接字上有一個套接字錯誤待處理
異常條件:
a.該套接字存在帶外數據或者仍處於帶外標記
a.該套接字存在帶外數據或者仍處於帶外標記
接受緩沖區低水位標記(讀):讓select返回套接字接收緩沖區所需數據量
發送緩沖區低水位標記(寫):讓select返回套接字發送緩沖區可用空間
發送緩沖區低水位標記(寫):讓select返回套接字發送緩沖區可用空間
示例:
91 // 使用select的cli_io函數,使得在服務器進程終止后客戶可以馬上獲取通知 92 void cli_io_select(int sockfd, char *mark, FILE *fp) 93 { 94 int maxfdp1, n; 95 fd_set rset; 96 char sendline[MAXLINE], recvline[MAXLINE]; 97 98 FD_ZERO(&rset); 99 100 for ( ; ; ) 101 { 102 FD_SET(fileno(fp), &rset); 103 FD_SET(sockfd, &rset); 104 105 // fileno() 函數,將文件流指針轉換為文件描述符· 106 maxfdp1 = max(fileno(fp), sockfd) + 1; 107 108 if (select(maxfdp1, &rset, NULL, NULL, NULL) < 0) 109 { 110 printf("Error select!\n"); 111 exit(1); 112 } 113 114 if (FD_ISSET(sockfd, &rset)) 115 { 116 if ( (n = read(sockfd, recvline, MAXLINE)) > 0 ) 117 { 118 recvline[n] = '\0'; 119 fputs(recvline, stdout); 120 } 121 } 122 123 if (FD_ISSET(fileno(fp), &rset)) 124 { 125 if (fgets(sendline, MAXLINE, fp) == NULL) 126 { 127 return; 128 } 129 130 if (write(sockfd, sendline, strlen(sendline)) < 0) 131 { 132 printf("Error write!\n"); 133 exit(1); 134 } 135 } 136 } 137 }