select函數及fd_set介紹


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_SETFD_CLRFD_ISSETFD_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的操作來完成異步的網絡消息處理,具體的實現請看這里的例子


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM