歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux服務器 >> 如何有效地使用函數select()

如何有效地使用函數select()

日期:2017/3/2 16:38:57   编辑:Linux服務器

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。

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

  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;

  };

  }

Copyright © Linux教程網 All Rights Reserved