1. 程式人生 > >fd_set 用法 socket

fd_set 用法 socket

一、winsock中

#include <winsock.h>

原型

int   select( 
int
   nfds ,
fd_set*
   ,
fd_set*
   ,
fd_set*
   ,
const struct timeval*
   
);

nfds:本引數忽略,僅起到相容作用。
    readfds:(可選)指標,指向一組等待可讀性檢查的套介面。
    writefds:(可選)指標,指向一組等待可寫性檢查的套介面。
    exceptfds:(可選)指標,指向一組等待錯誤檢查的套介面。
    timeout:select()最多等待時間,對阻塞操作則為NULL。

註釋:
    本函式用於確定一個或多個套介面的狀態。對每一個套介面,呼叫者可查詢它的可讀性、可寫性及錯誤狀態資訊。用fd_set結構來表示一組等待檢查的套介面。在呼叫返回時,這個結構存有滿足一定條件的套介面組的子集,並且select()返回滿足條件的套介面的數目。有一組巨集可用於對fd_set的操作,這些巨集與Berkeley Unix軟體中的相容,但內部的表達是完全不同的。
    readfds引數標識等待可讀性檢查的套介面。如果該套介面正處於監聽listen()狀態,則若有連線請求到達,該套介面便被標識為可讀,這樣一個accept()呼叫保證可以無阻塞完成。對其他套介面而言,可讀性意味著有排隊資料供讀取。或者對於SOCK_STREAM型別套介面來說,相對於該套介面的虛套介面已關閉,於是recv()或recvfrom()操作均能無阻塞完成。如果虛電路被“優雅地”中止,則recv()不讀取資料立即返回;如果虛電路被強制復位,則recv()將以WSAECONNRESET錯誤立即返回。如果SO_OOBINLINE選項被設定,則將檢查帶外資料是否存在(參見setsockopt())。
    writefds引數標識等待可寫性檢查的套介面。如果一個套介面正在connect()連線(非阻塞),可寫性意味著連線順利建立。如果套介面並未處於connect()呼叫中,可寫性意味著send()和sendto()呼叫將無阻塞完成。〔但並未指出這個保證在多長時間內有效,特別是在多執行緒環境中〕。
    exceptfds引數標識等待帶外資料存在性或意味錯誤條件檢查的套介面。請注意如果設定了SO_OOBINLINE選項為假FALSE,則只能用這種方法來檢查帶外資料的存在與否。對於SO_STREAM型別套介面,遠端造成的連線中止和KEEPALIVE錯誤都將被作為意味出錯。如果套介面正在進行連線connect()(非阻塞方式),則連線試圖的失敗將會表現在exceptfds引數中。
    如果對readfds、writefds或exceptfds中任一個組類不感興趣,可將它置為空NULL。
    在winsock.h標頭檔案中共定義了四個巨集來操作描述字集。FD_SETSIZE變數用於確定一個集合中最多有多少描述字(FD_SETSIZE預設值為64,可在包含winsock.h前用#define FD_SETSIZE來改變該值)。對於內部表示,fd_set被表示成一個套介面的佇列,最後一個有效元素的後續元素為INVAL_SOCKET。巨集為:
    FD_CLR(s,*set):從集合set中刪除描述字s。
    FD_ISSET(s,*set):若s為集合中一員,非零;否則為零。
    FD_SET(s,*set):向集合新增描述字s。
    FD_ZERO(*set):將set初始化為空集NULL。
    timeout引數控制select()完成的時間。若timeout引數為空指標,則select()將一直阻塞到有一個描述字滿足條件。否則的話,timeout指向一個timeval結構,其中指定了select()呼叫在返回前等待多長時間。如果timeval為{0,0},則select()立即返回,這可用於探詢所選套介面的狀態。如果處於這種狀態,則select()呼叫可認為是非阻塞的,且一切適用於非阻塞呼叫的假設都適用於它。舉例來說,阻塞鉤子函式不應被呼叫,且WINDOWS套介面實現不應yield。

返回值:
    select()呼叫返回處於就緒狀態並且已經包含在fd_set結構中的描述字總數;如果超時則返回0;否則的話,返回SOCKET_ERROR錯誤,應用程式可通過WSAGetLastError()獲取相應錯誤程式碼。

錯誤程式碼:
    WSANOTINITIALISED:在使用此API之前應首先成功地呼叫WSAStartup()。
    WSAENETDOWN:WINDOWS套介面實現檢測到網路子系統失效。
    WSAEINVAL:超時時間值非法。
    WSAEINTR:通過一個WSACancelBlockingCall()來取消一個(阻塞的)呼叫。
    WSAEINPROGRESS:一個阻塞的WINDOWS套介面呼叫正在執行中。
    WSAENOTSOCK:描述字集合中包含有非套介面的元素。

範例 :
sock= socket(AF_INET,SOCK_STREAM,0);

struct sockaddr_in addr;       //告訴sock 應該再什麼地方licence 
memset(&addr,0,sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_port=htons(11111);    //埠啦
addr.sin_addr.s_addr=htonl(INADDR_ANY);        //在本機的所有ip上開始監聽

bind (sock,(sockaddr *)&addr,sizeof(addr));//bind....

listen(sock,5);                   //最大5個佇列

SOCKET socka;                     //這個用來接受一個連線
fd_set rfd;                      // 描述符集 這個將用來測試有沒有一個可用的連線
struct timeval timeout;

FD_ZERO(&rfd);                      //總是這樣先清空一個描述符集

timeout.tv_sec=60;                 //等下select用到這個
timeout.tv_usec=0;

u_long ul=1;

ioctlsocket(sock,FIONBIO,&ul);     //用非阻塞的連線

//現在開始用select 
FD_SET(sock,&rfd);     //把sock放入要測試的描述符集 就是說把sock放入了rfd裡面 這樣下一步呼叫select對rfd進行測試的時候就會測試sock了(因為我們將sock放入的rdf) 一個描述符集可以包含多個被測試的描述符,
if(select(sock+1,&rfd,0,0, &timeout)==0)  {   //這個大括號接上面的,返回0那麼就超過了timeout預定的時間

//處理....

}

if(FD_ISSET(sock,&rfd))
{       //有一個描述符準備好了

socka=accept(sock,0,0);      //一個用來測試讀 一個用來測試寫

FD_ZERO(&rfd);

FD_ZERO(&wfd);

FD_SET(socka,&rfd);//把socka放入讀描述符集

FD_SET(sockb,&rfd);//把sockb放入讀描述符集

FD_SET(socka,&wfd);把socka放入寫描述符集

FD_SET(sockb,&wfd);把sockb放入寫描述符集

if(SOCKET_ERROR!=select(0,&rfd,&wfd,0,0))       //測試這兩個描述符集,永不超時 其中rfd只用來測試讀 wfd只用來測試寫

{       //沒有錯誤

if(FD_ISSET(socka,&rfd))     //socka可讀

{...}

if(FD_ISSET(sockb,&rfd)    //sockb可讀

{...}

if(FD_ISSET(socka,&wfd) //socka 可寫

{...}

if(FD_ISSET(sockb,&wfd) //sockb可寫

{...}

}

select()函式主要是建立在fd_set型別的基礎上的。fd_set(它比較重要所以先介紹一下)是一組檔案描述字(fd)的集合,它用一位來表示一個fd(下面會仔細介紹),對於fd_set型別通過下面四個巨集來操作: 

    fd_set set;

    FD_ZERO(&set);       /* 將set清零使集合中不含任何fd*/

    FD_SET(fd, &set);    /* 將fd加入set集合 */

    FD_CLR(fd, &set);    /* 將fd從set集合中清除 */

    FD_ISSET(fd, &set);  /* 測試fd是否在set集合中*/      

過去,一個fd_set通常只能包含<32的fd(檔案描述字),因為fd_set其 實只用了一個32位向量來表示fd;現在,UNIX系統通常會在標頭檔案<sys/select.h>中定義常量FD_SETSIZE,它是數 據型別fd_set的描述字數量,其值通常是1024,這樣就能表示<1024的fd。根據fd_set的位向量實現,我們可以重新理解操作 fd_set的四個巨集: 

    fd_set set;

FD_ZERO(&set);      /*將set的所有位置0,如set在記憶體中佔8位則將set置為

00000000*/

FD_SET(0, &set);    /* 將set的第0位置1,如set原來是00000000,則現在變為10000000,這樣fd==1的檔案描述字就被加進set中了 */

FD_CLR(4, &set);    /*將set的第4位置0,如set原來是10001000,則現在變為10000000,這樣fd==4的檔案描述字就被從set中清除了 */ 

FD_ISSET(5, &set);  /* 測試set的第5位是否為1,如果set原來是10000100,則返回非零,表明fd==5的檔案描述字在set中;否則返回0*/ 

―――――――――――――――――――――――――――――――――――――――

注意fd的最大值必須<FD_SETSIZE。

――――――――――――――――――――――――――――――――――――――― 

select函式的介面比較簡單:

    int select(int nfds, fd_set *readset, fd_set *writeset,

fd_set* exceptset, struct timeval *timeout); 

功能:

測試指定的fd可讀?可寫?有異常條件待處理?     

引數:

nfds    

需要檢查的檔案描述字個數(即檢查到fd_set的第幾位),數值應該比三組fd_set中 所含的最大fd值更大,一般設為三組fd_set中所含的最大fd值加1(如在readset,writeset,exceptset中所含最大的fd為 5,則nfds=6,因為fd是從0開始的)。設這個值是為提高效率,使函式不必檢查fd_set的所有1024位。

readset   

     用來檢查可讀性的一組檔案描述字。

writeset

     用來檢查可寫性的一組檔案描述字。

exceptset

     用來檢查是否有異常條件出現的檔案描述字。(注:錯誤不包括在異常條件之內)

timeout

有三種可能:

1.        timeout=NULL(阻塞:直到有一個fd位被置為1函式才返回)

2.        timeout所指向的結構設為非零時間(等待固定時間:有一個fd位被置為1或者時間耗盡,函式均返回)

3.        timeout所指向的結構,時間設為0(非阻塞:函式檢查完每個fd後立即返回) 

返回值:     

返回對應位仍然為1的fd的總數。 

Remarks:

三組fd_set均將某些fd位置0,只有那些可讀,可寫以及有異常條件待處理的fd位仍然為1。

使用select函式的過程一般是:

先呼叫巨集FD_ZERO將指定的fd_set清零,然後呼叫巨集FD_SET將需要測試的fd加入fd_set,接著呼叫函式select測試fd_set中的所有fd,最後用巨集FD_ISSET檢查某個fd在函式select呼叫後,相應位是否仍然為1。 

      在過去,一個fd_set通常只能包含少於等於32個檔案描述符,因為fd_set其實只用了一個int的位元向量來實現,在大多數情況下,檢查 fd_set能包括任意值的檔案描述符是系統的責任,但確定你的fd_set到底能放多少有時你應該檢查/修改巨集FD_SETSIZE的值。*這個值是系 統相關的*,同時檢查你的系統中的select() 的man手冊。有一些系統對多於1024個檔案描述符的支援有問題

#ifndef FD_SETSIZE
#define FD_SETSIZE       64
#endif /* FD_SETSIZE */

typedef struct fd_set {
         u_int fd_count;                /* how many are SET? */
         SOCKET   fd_array[FD_SETSIZE];    /* an array of SOCKETs */
} fd_set;

extern int PASCAL FAR __WSAFDIsSet(SOCKET, fd_set FAR *);

#define FD_CLR(fd, set) do { /
     u_int __i; /
     for (__i = 0; __i < ((fd_set FAR *)(set))->;fd_count ; __i++) { /
         if (((fd_set FAR *)(set))->;fd_array[__i] == fd) { /
             while (__i < ((fd_set FAR *)(set))->;fd_count-1) { /
                 ((fd_set FAR *)(set))->;fd_array[__i] = /
                     ((fd_set FAR *)(set))->;fd_array[__i+1]; /
                 __i++; /
             } /
             ((fd_set FAR *)(set))->;fd_count--; /
             break; /
         } /
     } /
} while(0)

#define FD_SET(fd, set) do { /
     u_int __i; /
     for (__i = 0; __i < ((fd_set FAR *)(set))->;fd_count; __i++) { /
         if (((fd_set FAR *)(set))->;fd_array[__i] == (fd)) { /
             break; /
         } /
     } /
     if (__i == ((fd_set FAR *)(set))->;fd_count) { /
         if (((fd_set FAR *)(set))->;fd_count < FD_SETSIZE) { /
             ((fd_set FAR *)(set))->;fd_array[__i] = (fd); /
             ((fd_set FAR *)(set))->;fd_count++; /
         } /
     } /
} while(0)

#define FD_ZERO(set) (((fd_set FAR *)(set))->;fd_count=0)

#define FD_ISSET(fd, set) __WSAFDIsSet((SOCKET)(fd), (fd_set FAR *)(set))
typedef int32_t __fd_mask;

#define _NFDBITS (sizeof(__fd_mask) * 8)      /* 8 bits per byte */

#define __howmany(x,y)   (((x)+((y)-1))/(y))

#ifndef _FD_SET

#   define _FD_SET
    typedef struct __fd_set {
      long fds_bits[__howmany(FD_SETSIZE, (sizeof(long) * 8))];
      } fd_set;

#   ifndef _KERNEL
#     ifdef __cplusplus
         extern "C" {
#     endif /* __cplusplus */

#ifdef _INCLUDE_HPUX_SOURCE
#     define FD_SET(n,p)   (((__fd_mask *)((p)->;fds_bits))[(n)/_NFDBITS] |= (1 <<
((n) % _NFDBITS)))
#     define FD_CLR(n,p) (((__fd_mask *)((p)->;fds_bits))[(n)/_NFDBITS] &= ~(1 <<
((n) % _NFDBITS)))

#     define FD_ISSET(n,p) (((__fd_mask *)((p)->;fds_bits))[(n)/_NFDBITS] & (1 <<
((n) % _NFDBITS)))
#     define FD_ZERO(p)      memset((void *)(p), (int) 0, sizeof(*(p)))
#else
#     define FD_SET(n,p) (__fd_set1(n, p))
#     define FD_CLR(n,p) (__fd_clr(n, p))
#     define FD_ISSET(n,p) (__fd_isset(n, p))
#     define FD_ZERO(p)      memset((void *)(p), (int) 0, sizeof(fd_set))

以下是一個測試單個檔案描述字可讀性的例子:

     int isready(int fd)

     {

         int rc;

         fd_set fds;

         struct timeval tv;    

         FD_ZERO(&fds);

         FD_SET(fd,&fds);

         tv.tv_sec = tv.tv_usec = 0;    

      rc = select(fd+1, &fds, NULL, NULL, &tv);

         if (rc < 0)   //error

           return -1;    

         return FD_ISSET(fd,&fds) ? 1 : 0;

     }

下面還有一個複雜一些的應用:

//這段程式碼將指定測試Socket的描述字的可讀可寫性,因為Socket使用的也是fd

uint32 SocketWait(TSocket *s,bool rd,bool wr,uint32 timems)    

{

     fd_set rfds,wfds;

#ifdef _WIN32

     TIMEVAL tv;

#else

     struct timeval tv;

#endif   /* _WIN32 */ 

     FD_ZERO(&rfds);

     FD_ZERO(&wfds); 

     if (rd)     //TRUE

          FD_SET(*s,&rfds);   //新增要測試的描述字 

     if (wr)     //FALSE

          FD_SET(*s,&wfds); 

     tv.tv_sec=timems/1000;     //second

     tv.tv_usec=timems%1000;     //ms 

     for (;;) //如果errno==EINTR,反覆測試緩衝區的可讀性

          switch(select((*s)+1,&rfds,&wfds,NULL,

              (timems==TIME_INFINITE?NULL:&tv)))  //測試在規定的時間內套介面接收緩衝區中是否有資料可讀

         {                                              //0--超時,-1--出錯

         case 0:     /* time out */

              return 0; 

         case (-1):    /* socket error */

              if (SocketError()==EINTR)

                   break;              

              return 0; //有錯但不是EINTR 

          default:

              if (FD_ISSET(*s,&rfds)) //如果s是fds中的一員返回非0,否則返回0

                   return 1;

              if (FD_ISSET(*s,&wfds))

                   return 2;

              return 0;

         };

}