1. select函數
1. 用途
在編程的過程中,經常會遇到許多阻塞的函數,好像read和網絡編程時使用的recv, recvfrom函數都是阻塞的函數,當函數不能成功執行的時候,程序就會一直阻塞在這里,無法執行下面的代碼。這時就需要用到非阻塞的編程方式,使用select函數就可以實現非阻塞編程。
select函數是一個輪循函數,循環詢問文件節點,可設置超時時間,超時時間到了就跳過代碼繼續往下執行。
2. 大致原理
select需要驅動程序的支持,驅動程序實現fops內的poll函數。select通過每個設備文件對應的poll函數提供的信息判斷當前是否有資源可用(如可讀或寫),如果有的話則返回可用資源的文件描述符個數,沒有的話則睡眠,等待有資源變為可用時再被喚醒繼續執行。詳細的原理請看這里
3. 函數定義
該函數聲明如下
int select(int nfds, fd_set* readset, fd_set* writeset, fe_set* exceptset, struct timeval* timeout);
參數:
nfds 需要檢查的文件描述字個數
readset 用來檢查可讀性的一組文件描述字。
writeset 用來檢查可寫性的一組文件描述字。
exceptset 用來檢查是否有異常條件出現的文件描述字。(注:錯誤不包括在異常條件之內)
timeout 超時,填NULL為阻塞,填0為非阻塞,其他為一段超時時間
返回值:
返回fd的總數,錯誤時返回SOCKET_ERROR
2. fd_set結構體
上面select函數中需要用到兩個fd_set形參,這個結構體到底做什么用的呢
fd_set其實這是一個數組的宏定義,實際上是一long類型的數組,每一個數組元素都能與一打開的文件句柄(socket、文件、管道、設備等)建立聯系,建立聯系的工作由程序員完成,當調用select()時,由內核根據IO狀態修改fd_set的內容,由此來通知執行了select()的進程哪個句柄可讀。
系統提供了FD_SET, FD_CLR, FD_ISSET, FD_ZERO進行操作,聲明如下:
FD_SET(int fd, fd_set *fdset); //將fd加入set集合 FD_CLR(int fd, fd_set *fdset); //將fd從set集合中清除 FD_ISSET(int fd, fd_set *fdset); //檢測fd是否在set集合中,不在則返回0 FD_ZERO(fd_set *fdset); //將set清零使集合中不含任何fd
下面寫一段程序探究一下這幾個宏的工作:
#include <WINSOCK2.H>
int main()
{
fd_set fdset;
FD_ZERO(&fdset);
FD_SET(1, &fdset);
FD_SET(2, &fdset);
FD_SET(3, &fdset);
FD_SET(7, &fdset);
int isset = FD_ISSET(3, &fdset);
printf("isset = %d\n", isset);
FD_CLR(3, &fdset);
isset = FD_ISSET(3, &fdset);
printf("isset = %d\n", isset);
return 0;
}
當使用FD_SET添加完1、2、3、7后,fdset的值如下:

然后經過FD_CLR以后,fd_array[2]就被清除了,數組后面的數據依次往前提,即7被放到了fd_array[2]
所以isset前后兩次打印的值分別為1和0
3. 小結
select的結果會對fd_set造成影響。例如,對於一個監聽的socket:
#include <WinSock2.h>
#include <stdio.h>
#pragma comment(lib,"WS2_32.lib")
int main()
{
FD_SET ReadSet;
FD_ZERO(&ReadSet);
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData); //初始化
SOCKET ListenSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED); //定義一個監聽套接字
//bind等操作這里省略.... //.....
FD_SET(ListenSocket, &ReadSet); //將套接字加入ReadSet集合中
int isset = FD_ISSET(ListenSocket, &ReadSet); //這里並沒有通過select對fd_set進行篩選
printf("Before select, isset = %d\n", isset); //所以這里打印結果為1
struct timeval tTime;
tTime.tv_sec = 10;
tTime.tv_usec = 0;
select(0, &ReadSet, NULL, NULL, &tTime); //通過select篩選處於就緒狀態的fd
//這時,剛才的ListenSocket並不在就緒狀態(沒有連接連入),那么就從ReadSet中去除它
isset = FD_ISSET(ListenSocket, &ReadSet);
printf("After select, isset = %d\n", isset); //所以這里打印的結果為0
system("pause");
return 0;
}
所以可以使用select以及fd的操作來完成異步的網絡消息處理,具體的實現請看這里的例子
