linux select operation

select系统调用是用来让我们的程序监视多个文件描述符(file descrīptor)的状态变化的。程序会停在select这里等待,直到被监视的文件描述符有某一个或多个发生了状态改变。select()的机制中提供一fd_set的数据结构,实际上是一long类型的数组, 每一个数组元素都能与一打开的文件描述符(不管是Socket描述符,还是其他 文件或命名管道或设备描述符)建立联系,建立联系的工作由程序员完成, 当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执 行了select()的进程哪一Socket或文件可读,

select函数原型如下:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

函数的最后一个参数timeout显然是一个超时时间值,其类型是struct timeval *,即一个struct timeval结构的变量的指针,所以我们在程序里要申明一个struct timeval tv;然后把变量tv的地址&tv传递给select函数。struct timeval结构如下:

struct timeval {
long  
tv_sec;     /* seconds */
long   tv_usec;    /* microseconds */
};

第2、3、4三个参数的类型是一样的: fd_set *,即我们在程序里要申明几个fd_set类型的变量,比如定义了rfds, wfds, efds。

另外关于fd_set类型的变量,还有一组标准的宏定义来处理此类变量:

FD_ZERO(fd_set *fdset):清空fdset与所有文件描述符的联系。

FD_SET(int fd, fd_set *fdset):建立文件描述符fd与fdset的联系。

FD_CLR(int fd, fd_set *fdset):清除文件描述符fd与fdset的联系。

FD_ISSET(int fd, fd_set *fdset):检查fd_set联系的文件描述符fd是否可读写,>0表示可读写。

(关于fd_set及相关宏的定义见/usr/include/sys/types.h)定义的这三个参数都是描述符的集合,第一个rfds是用来保存这样的描述符的:当描述符的状态变成可读的时系统就会告诉select函数返回,第二个wfds是指有描述符状态变成可写的时系统就会告诉select函数返回,第三个参数efds是特殊情况,即描述符上有特殊情况发生时系统会告诉select函数返回。下面以一个输入为例来说明:

int fd1, fd2;         /* 在定义两个描述符*/

fd1 = socket(...);    /* 创建socket连接*/

fd2 = open(“/dev/tyS0”,O_RDWR); /* 打开一个串口*/

FD_ZERO(&rfds);       /* 用select函数之前先把集合清零 */

FD_SET(fd1, &rfds);   /* 分别把2个描述符加入读监视集合里去 */

FD_SET(fd2, &rfds);

int maxfd = 0;

maxfd = (fd1>fd2)?(fd1+1):(fd2+1);           /* 注意是最大值还要加1 */

ret = select(maxfd, &rfds, NULL, NULL, &tv); /*然后调用select函数*/

这样就可以使用一个开关语句(switch语句)来判断到底是哪一个输入源在输入数据。具体判断如下:

switch(ret){

case -1:perror("select");/* 这说明select函数出错 */

case 0:printf("超时\n"); /* 说明在设定的时间内,socket的状态没有发生变化 */

default

if(FD_ISSET(fd1, &rfds))处理函数1();/*socket有数据来*/

if(FD_ISSET(fd2, &rfds))处理函数2();/*ttyS0有数据来*/

}

 

 

以下来自网络搜索:

Linux下select调用的过程:

1.用户层应用程序调用select(),底层调用poll())

2.核心层调用sys_select() ------> do_select()

最终调用文件描述符fd对应的struct file类型变量的struct file_operations *f_op的poll函数。

poll指向的函数返回当前可否读写的信息。

1)如果当前可读写,返回读写信息。

2)如果当前不可读写,则阻塞进程,并等待驱动程序唤醒,重新调用poll函数,或超时返回。

3.驱动需要实现poll函数。

当驱动发现有数据可以读写时,通知核心层,核心层重新调用poll指向的函数查询信息。

poll_wait(filp,&wait_q,wait) // 此处将当前进程加入到等待队列中,但并不阻塞

在中断中使用wake_up_interruptible(&wait_q)唤醒等待队列

 

http://www.lupaworld.com/151392/viewspace-45283.html

===================================================================================

 

非阻塞式socket编程(select() )

http://eastsun.blogbus.com/logs/7873846.html

Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect、accept、recv或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。可是使用Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。下面详细介绍一下!

Select的函数格式(我所说的是Unix系统下的伯克利socket编程,和windows下的有区别,一会儿说明):

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);

先说明两个结构体:

第一,struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,比如清空集合FD_ZERO(fd_set *),将一个给定的文件描述符加入集合之中FD_SET(int ,fd_set *),将一个给定的文件描述符从集合中删除FD_CLR(int ,fd_set*),检查集合中指定的文件描述符是否可以读写FD_ISSET(int ,fd_set* )。一会儿举例说明。

第二,struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。

具体解释select的参数:

int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。

fd_set *readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。

fd_set *writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。

fd_set *errorfds同上面两个参数的意图,用来监视文件错误异常。

struct timeval* timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。

返回值:

负值:select错误 正值:某些文件可读写或出错 0:等待超时,没有可读写或错误的文件

在有了select后可以写出像样的网络程序来!举个简单的例子,就是从网络上接受数据写入一个文件中。

例子:

main()

{

int sock;

FILE *fp;

struct fd_set fds;

struct timeval timeout={3,0}; //select等待3秒,3秒轮询,要非阻塞就置0

char buffer[256]={0}; //256字节的接收缓冲区

/* 假定已经建立UDP连接,具体过程不写,简单,当然TCP也同理,主机ip和port都已经给定,要写的文件已经打开

sock=socket(...);

bind(...);

fp=fopen(...); */

while(1)

{

FD_ZERO(&fds); //每次循环都要清空集合,否则不能检测描述符变化

FD_SET(sock,&fds); //添加描述符

FD_SET(fp,&fds); //同上

maxfdp=sock>fp?sock+1:fp+1; //描述符最大值加1

switch(select(maxfdp,&fds,&fds,NULL,&timeout)) //select使用

{

case -1: exit(-1);break; //select错误,退出程序

case 0:break; //再次轮询

default:

if(FD_ISSET(sock,&fds)) //测试sock是否可读,即是否网络上有数据

{

recvfrom(sock,buffer,256,.....);//接受网络数据

if(FD_ISSET(fp,&fds)) //测试文件是否可写

fwrite(fp,buffer...);//写入文件

buffer清空;

}// end if break;

}// end switch

}//end while

}//end main


==========================================================================

Linux下用select查询串口数据

http://www.wangchao.net.cn/bbsdetail_68116.html

 

  Linux下直接用read读串口可能会造成堵塞,或数据读出错误。然而用select先查询com口,再用read去读就可以避免,并且当com口延时时,程序可以退出,这样就不至于由于com口堵塞,程序就死了。我的代码如下:
  bool ReadDevice( int hComm, unsigned long uLen, char* pData )
  {
   int nread = 0;
   char inbuf[uLen];
   char buff[uLen];
   memset( inbuff, '\0', uLen );
   memset( buff, '\0', uLen );
   fd_set readset;
   struct timeval tv;
   int MaxFd = 0;
   int c = 0;
   int z;
   do
   {
   FD_ZERO( &readset );
   if( hComm >= 0 )
   FD_SET( hComm, &readset );
   MaxFd = hComm + 1;
   tv.tv_sec = 0;
   tv.tv_usec = 500000;
   do
   {
   z = select( MaxFd, &readset, 0, 0, &tv);
   }while( z==-1 && errno==EINTR );
   if( z == -1 )
   printf("select(2)\n");
   if( z == 0 )
   {
   hComm = -1;
   }
  
   if( hComm>=0 && FD_ISSET(hComm, &readset) )
   {
   z = read( hComm, buff, uLen - c );
   c += z;
   if( z == -1 )
   {
   hComm = -1;
   }
   if( z > 0 )
   {
   buff[ z + 1 ] = '\0';
   strcat( inbuff, buff );
   memset( buff, 0x00, uLen );
   }
   else
   {
   hComm = -1;
   }
   }
   }while( hComm >= 0 );
   memcpy( pData, inbuff, c );
   return true;
  }

=========================================================================

 

关于fd_set说明

http://eastsun.blogbus.com/logs/7762079.html

'fd_set'是一组文件描述符(fd)的集合。由于fd_set类型的长度在不同平台上不同,因此应该用一组标准的宏定义来处理此类变量:fd_set set;   

FD_ZERO(&set);       /* 将set清零 */

FD_SET(fd, &set);    /* 将fd加入set */   

FD_CLR(fd, &set);    /* 将fd从set中清除 */   

FD_ISSET(fd, &set);  /* 如果fd在set中则真 */     

在过去,一个fd_set通常只能包含少于等于32个文件描述符,因为fd_set其实只用了一个int的比特矢量来实现,在大多数情况下,检查fd_set能包括任意值的文件描述符是系统的责任,但确定你的fd_set到底能放多少有时你应该检查/修改宏FD_SETSIZE的值。*这个值是系统相关的*,同时检查你的系统中的select() 的man手册。有一些系统对多于1024个文件描述符的支持有问题。

我们可以用select()对fd_set进行扫描,以判断设备是后可读写或是处于某种状态,它会返回一个掩码为那些可用和不可用得坐下不同的标记

# include <sys/select.h>
# include <sys/time.h>
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);

然后可以用FD_ISSET宏来查找你所要求的fd是否在其中,如(FD_ISSET(fd,&fdset))。


====================================================================

 

unix下的多路复用(select

http://eastsun.blogbus.com/logs/7762285.html

摘自《UNIX NETWORK PROGRAMMINGchapter 6

p144

 

 

对于常见的input操作,一般分为两个步骤:

1  wait to be ready

2  copy data from kernel buffer to user buffer

 

 

常见的I/O模型:(参见以上步骤)

1  阻塞I/O                               用户进程执行12

2  非阻塞I/O                           用户轮巡1,然后执行2

3  多路复用selectpoll            用户调用select等待kernel返回,然后执行2

4  信号驱动I/OSIGIO              用户设置信号处理函数(sigio)后,正常继续其他函数,当kernel返回SIGIO后,执行2

5  异步信号I/O                        kernel执行12后通知用户进程(不常用)

 

 

# include <sys/select.h>

# include <sys/time.h>

int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);

 

 

作用:

1  内核扫描maxfdp1个描述符(常规情况下,系统最大值FD_SETSIZE=1024,可修改)

2  内核查看readset, writeset, exceptset集中的描述符是否准备好

3  等待超过timeout时间而没有描述符准备好,select返回

 

 

struct timeval {

       long tv_sec;

       long tv_usec;

}

注:timeval == 0   马上返回

       timeval == NULL   永久阻塞

 

 

void FD_ZERO(fd_set *fdset);

void FD_SET(int fd, fd_set *fdset);

void FD_CLR(int fd, fd_set *fdset);

void FD_ISSET(int fd, fd_set *fdset);

 

 

注意,如循环调用select,多次检查同一描述符,必须在调用select之前重新设定初始值。select函数会在每次返回时,将没有ready的描述符所在的位清0

 

 

任何信号将使select()出错返回。而且BSD系统的select将不可能自动再启动

在标准select()中,返回后系统不会改变timeval的值,而linux系统例外。

 

 

exception condition

当前只支持  out-of-bandthe presence of control status information to be read from the master side of a pseudo terminal that has been put into packet mode.(???)

 

 

一般的系统实现中,将fd_set设置为整数队列,每个整数元素中的一位表示为一个描述符

 

 

FD_SETSIZE定义在<sys/select.h>中,如果要更改的话,必须重新编译内核

 

 

select()的常见错误:

1  maxfdp1必须指定为最大描述符值+1

2  每次select返回后,都会将fd_set中的初值清0,除非该描述符已经准备好。因此,如果要重新检查描述符,必须再次赋初值

 

 

select返回的描述符个数中,如果同一描述符同时为读、写准备好,则记数2

早期的SVR4版本只记录1次。(bug

 

 

 

 

select准备好的意义:

准备好:

1  socket接受缓存中的数据 >= SO_RCVLOWAT,默认情况下,SO_RCVLOWAT=1

2  对端写关闭,read返回0

3  listenfd中的complete queue 中,有entry

4  socket出错。read返回-1

 

 

准备好:

1  socket发送缓存中的数据 >= SO_SNDLOWAT,默认情况下,SO_SNDLOWAT=2048

2  对端读关闭,kernel返回SIGPIPE

3  socket出错,write返回-1

 

 

注意,当socket出错时,将在readset/writeset分别赋值

 

 

使用select应该注意:

由于同时处理单个描述符的读写,可能出现此描述符的写(或读)操作已全部完成,而相对的另一个读(或写)操作还没有完成。为了获得这些数据,进程必须调用shutdown()以进行半关闭

 

 

# include <sys/socket.h>

int shutdown(int sockfd, int howto);

其中,howto可以置为SHUT_RD/SHUT_WR/SHUTRDWR

 

 

区别于close()

1  shutdown不查看描述符计数器。直接进行半关闭

2  close只能进行全关闭,shutdown可以选择一端或两端

 

 

服务器处理客户机请求的原则:永远不要将服务器阻塞在一个客户连接中。(可能被dos攻击)

 

 

# include <sys/select.h>

# include <signal.h>

# include <time.h>

int pselect(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *except_set, const struct timespec *timeout, const sigset_t sigmask);

===============================================================================

 

socket编程中用select实现并发处理

http://bbs.linuxpk.com/thread-33452-1-2.html

环境:Linux C
看了几天用select函数处理并发的程序,有些地方还是百思不得其解,非常急,可能是基础薄弱,希望能得到基础的讲解。
    我目前有一种处理并发的方式,是采用accept阻塞,然后fork子进程来process,父进程继续阻塞,流程如下:
一.第一种处理方式:
client端:
socket->connect->连接成功->write/read|recv/send->结束

server端:
sockfd = socket()
bind()
listen()

while(1)
{
   newfd = accept()
   if ( 0 == fork() ) {
       close(sockfd)
       process( )  /* write/read|recv/send   */
       close(newfd)
       exit(0)   /* 子进程处理事件完成,退出 */
   }
   
   close(newfd)  /* 主进程重新去accept,等待新连接 */
}

close(sockfd)

二。第二处理方式
这也是我要请教的重点:对以上的流程我还是稍稍熟悉些,主要是用select处理并发就不知道怎么用了,不明白select的作用体现在了哪里?相对于单一使用accept的优点好在哪里?
  我从网上找到了一段代码,很多问题想请教下:
  #include   <stdlib.h>   
  #include   <stdio.h>   
  #include   <unistd.h>   
  #include   <sys/time.h>   
  #include   <sys/types.h>   
  #include   <string.h>   
  #include   <signal.h>   
  #include   <sys/socket.h>   
  #include   <netinet/in.h>   
  #include   <arpa/inet.h>   
  #include   <errno.h>   
   
  #define   MAXCLIENT   10   
  #define   PORT   4672   
  #define   BUF_SIZE   1024   
  #undef   max   
  #define   max(x,y)   ((x)   >   (y)   ?   (x)   :   (y))   
   
  int   main(int   argc,char   *argv[])   
  {   
         
        int   listen_id,accep_id;                     //监听socket,传输socket   
        int   n;   
        static   int   currentid   =   0;   
        int   fd[MAXCLIENT];   
        int   i,j;   
        char   buffer[BUF_SIZE];     
        struct   sockaddr_in   serveraddr,clientaddr;       //客户端地址   
         
        fd_set   rd;                                                 //在这儿定义一个保存可读的fd_set   
         
        for(i   =   0;i   <   MAXCLIENT;i++)   
        {   
              fd   =   -1;   
        }   
         
        //创建监听socket   
        listen_id   =   socket(AF_INET,SOCK_STREAM,0);   
        if(listen_id   <   0)   
        {   
              fprintf(stderr,"Create   listen   socket   failure.\n");   
              exit(1);   
        }   
   
        //设置监听socket   
        memset(&serveraddr,0,sizeof(serveraddr));   
        serveraddr.sin_family   =   AF_INET;                                       
        serveraddr.sin_addr.s_addr   =   htonl(INADDR_ANY);         
        serveraddr.sin_port   =   htons(PORT);     
   
        //绑定socket   
        if(bind(listen_id,(struct   sockaddr   *)&serveraddr,sizeof(serveraddr))<0)   
        {   
              fprintf(stderr,"bind   listen   socket   failure.\n");   
              exit(1);   
        }   
   
        //监听socket   
        if(listen(listen_id,MAXCLIENT)<0)   
        {   
              fprintf(stderr,"listen   the   socket   failure.\n");   
        }   
         
        //并发处理连接   
        FD_ZERO(&rd);   
   
        int   nfds   =   0;     //将该值放到循环外来定义   
        while(1)   
        {   
              int   ret;   
              nfds   =   max(nfds,listen_id);   
              FD_SET(listen_id,&rd);   
               
              ret   =   select(nfds+1,&rd,   NULL,   NULL,   NULL);    /* ???问题1  ------------*/
               
              //有中断,继续探测select   
              if   (   ret   <   0   )   
              {   
                      if   (   EINTR   ==   errno   )   
                      {   
                              continue;   
                      }   
                      else   
                      {   
                              fprintf(stderr,"begin   select   failure.\n");   
                      }   
              }   
               
              //假如信号来自监听socket   
              if(FD_ISSET(listen_id,&rd))     /* ???问题2 ----------------------*/
              {   
                    memset(&clientaddr,0,sizeof(clientaddr));   
                    fd[currentid]   =   accept(listen_id,(struct   sockaddr   *)&clientaddr,(unsigned   int   *)sizeof(clientaddr));   
                    if(fd[currentid]   <   0)   
                    {   
                            fprintf(stderr,"accept   a   new   connect   failure.\n");   
                            exit(1);   
                    }   
                    else   
                    {   
                            FD_SET(fd[currentid],&rd);   
                            currentid++;   
                              
                            if   (fd[currentid]   >   nfds)   
                            {   
                                    nfds   =   fd[currentid];   
                            }   
                              
                            if   (   --ret   <=   0   )     //当没有可读的描述字时   
                            {   
                                    break;   
                            }   
                    }   
              }   
              else   
              {   
                  for(j   =   0;   j   <   currentid;   j++)   
                  {   
                      //刚才这儿没有加   
                      if   (fd[j]   <   0   )   
                      {   
                              continue;   
                      }   
                        
                      if(FD_ISSET(fd[j],&rd))   
                      {   
                          int   rec;   
                          memset(&buffer,0,sizeof(buffer));   
                          rec   =   recv(fd[j],buffer,BUF_SIZE,0);   
                          if(rec   <   0)   
                          {   
                                  fprintf(stderr,"received   from   client:   %s   failure.\n",inet_ntoa(clientaddr.sin_addr));   
                                  exit(1);   
                          }   
                          else   if   (rec   ==   0)           //如果客户端终止   
                          {     
                                  close(fd[j]);                     //关闭该套接口                       
                                  FD_CLR(fd[j],   &rd);         //将该套接口描述字从rd中清除   
                                  fd[j]   =   -1;                         //将fd数组中相应的描述字置为-1   
                          }   
                          else   
                          {   
                              fprintf(stdout,"success   received   from   client:   %s   ,the   word   is:   %s.\n",inet_ntoa(clientaddr.sin_addr),buffer);   
                              if(send(fd[j],   buffer,   rec,   0)   !=   rec)   
                              {   
                                          fprintf(stderr,"sento   client:   %s   ,failure.   \n",inet_ntoa(clientaddr.sin_addr));   
                                          exit(1);   
                                  }   
                          }   
                           
                          //将fd[j]这个套接口,重新加入rd中   
                          FD_SET(fd[j],   &rd);   
                           
                          if   (   --ret   <=   0   )   //当没有描述字可读时   
                          {   
                                  break;   
                          }   
                      }   
                  }   
              }   
        }         
  }   
  
  问题1: 在" ret   =   select(nfds+1,&rd,   NULL,   NULL,   NULL);  ",在没有错误的情况下,ret应该是什么样的呢?是不是listen队列中请求的个数呢?
问题2:在上面程序中,select是如何收集描述符的呢?while大循环中,第一次循环应该执行:if(FD_ISSET(listen_id,&rd)) ,执行之后通过accept获得一个客户端的连接,并将描述符放入rd中,那第二次循环是不是应该执行else了呢?
  如果是的话,那岂不是每次只能处理一个描述符,因为accept每次只能linsten队列中取一个连接。
  但如果不是话,也就是第二次循环还会执行if(FD_ISSET(listen_id,&rd)),第三次也有可能执行if(FD_ISSET(listen_id,&rd)),那么什么时候终止呢?我觉得程序中if   (   --ret   <=   0   )  为终止条件好像不对吧。因为每一次循环都会执行ret   =   select(nfds+1,&rd,   NULL,   NULL,   NULL); ,ret值就会发生变化啊。
  总之问题归结于两点:select 是如何收集客户端连接描述符的呢,比如说有5个客户请求连接,而select在又是如何并发处理的呢?
  问题3:不明白select的作用体现在了哪里?相对于单一使用accept的优点好在哪里?

请求各位老师帮帮忙,我很急,如果可能话,就从while那里开始讲下程序。。

------------------------------

问题1答案:
select返回的是准备就绪的描述符个数,若超时则为0,若出错则为- 1。(参见《UNIX环境高级编程12.5.1节》)
(以下参见man select)
... ...
RETURN VALUE
       On success, select and pselect return the number of descriptors contained in the descriptor sets, which may be zero if the
       timeout  expires  before anything interesting happens.  On error, -1 is returned, and errno is set appropriately; the sets
       and timeout become undefined, so do not rely on their contents after an error.

问题1答案:
select()允许进程指示内核等待多个事件中的任一个发生,并仅在一个或多个事件发生或经过某指定的时间后才唤醒进程。也就是说通知内核我们对哪些描述字感兴趣,我们关心的描述字不受限于套接口,任何描述字都可以用select()来测试。select收集描述符,怎么实现的我不知道,有兴趣看看内核代码吧。
while大循环中,第一次循环应该执行:if(FD_ISSET(listen_id,&rd)) ,执行之后通过accept获得一个客户端的连接,并将描述符放入rd中,那第二次循环是不是应该执行else了呢?
不是!只有当有新的连接连上监听端口的时候FD_ISSET(listen_id,&rd)才会返回TRUE,否则,就去挨个检查那些已经连上来的客户端连接看有没有消息可以收(注意那个for循环,表明不是每次只能处理一个描述符)。
逻辑是这样的,帮你理顺:
把监听描述字设置到rd;
select()返回大于0的话
{
      if有新的连接连上来
    {
        添加新的连接到rd;
    }
    else
        {
            for  所有已有的连接
            {
                  收消息
                  发消息
            }
        }
}

问题3:
select的作用体现在了不需要你自己频繁的去测试哪些文件描述符可读可写或异常,select一次性把你关心的描述字的状态展现在你面前。
假如没有select函数,你就得自己这样写:
for (int i=0; i<maxfd; i++)
{
    if accept(...){...};
    if recv(...){...};
    if send(...){...};
}
这样的频繁的系统调用显然影响效率。
在应用进程中需要对多个I/O设备进行监听,当某个设备可读或可写时,进程能马上得知,并进行相关处理。这时若采用阻塞方式操作I/O,则进程会阻塞在某个设备的I/O读写操作上而不能适用于这种情况;若采用非阻塞方式,则往往需要定时或循环地探测所有设备,才作相应处理,这种作法相当耗费系统中央处理器的执行周期。可见,上述的两个I/O模型都不能满足这类应用,故此需要引入一种特别的I/O处理机制,即I/O多路转接,就是select()。

 

我来说一下第二点把:

  总之问题归结于两点:select 是如何收集客户端连接描述符的呢,比如说有5个客户请求连接,而select在又是如何并发处理的呢?

 

因为不知道内核实如何实现select的,我就说一下我的想法。

1。首先在单处理器里是没有真正的并行处理的,所以就有了并发的概念。

2。就算是真正的同时有5个客户同时请求连接,那处理器也必须一个一个的来处理。

3。实际上select的真正优势是在于可以等待多个文件描述符(当然包括多个套接口)

4。我认为内核可能是让这多个客户连接请求进入队列,然后再一个一个通知select来处理。

 

=====================================================================================

 

非阻塞式socket编程select()

http://eastsun.blogbus.com/logs/7873846.html

 

SelectSocket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如 connectacceptrecvrecvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。可是使用Select就可以完成非阻塞(所谓非阻塞方式non- block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况读写或是异常。下面详细介绍一下!

 

Select的函数格式(我所说的是Unix系统下的伯克利socket编程,和windows下的有区别,一会儿说明)

 

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);

 

先说明两个结构体:

 

第一,struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,比如清空集合 FD_ZERO(fd_set *),将一个给定的文件描述符加入集合之中FD_SET(int ,fd_set *),将一个给定的文件描述符从集合中删除FD_CLR(int ,fd_set*),检查集合中指定的文件描述符是否可以读写FD_ISSET(int ,fd_set* )。一会儿举例说明。

 

第二,struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。

 

具体解释select的参数:

 

int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。

 

fd_set *readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。

 

fd_set *writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。

 

fd_set *errorfds同上面两个参数的意图,用来监视文件错误异常。

 

struct timeval* timeoutselect的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为00毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即 selecttimeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。

 

返回值:

 

负值:select错误正值:某些文件可读写或出错 0:等待超时,没有可读写或错误的文件

 

在有了select后可以写出像样的网络程序来!举个简单的例子,就是从网络上接受数据写入一个文件中。

 

例子:

 

main()

 

{

 

int sock;

 

FILE *fp;

 

struct fd_set fds;

 

struct timeval timeout={3,0}; //select等待3秒,3秒轮询,要非阻塞就置0

 

char buffer[256]={0}; //256字节的接收缓冲区

 

/* 假定已经建立UDP连接,具体过程不写,简单,当然TCP也同理,主机ipport都已经给定,要写的文件已经打开

 

sock=socket(...);

 

bind(...);

 

fp=fopen(...); */

 

while(1)

 

{

 

FD_ZERO(&fds); //每次循环都要清空集合,否则不能检测描述符变化

 

FD_SET(sock,&fds); //添加描述符

 

FD_SET(fp,&fds); //同上

 

maxfdp=sock>fp?sock+1:fp+1; //描述符最大值加1

 

switch(select(maxfdp,&fds,&fds,NULL,&timeout)) //select使用

 

{

 

case -1: exit(-1);break; //select错误,退出程序

 

case 0:break; //再次轮询

 

default:

 

if(FD_ISSET(sock,&fds)) //测试sock是否可读,即是否网络上有数据

 

{

 

recvfrom(sock,buffer,256,.....);//接受网络数据

 

if(FD_ISSET(fp,&fds)) //测试文件是否可写

 

fwrite(fp,buffer...);//写入文件

 

buffer清空;

 

}// end if break;

 

}// end switch

 

}//end while

 

}//end main

 

----------------------------------------------------------

Linux select()详解

 

 

Linux select()详解       select系统调用是用来让我们的程序监视多个文件句柄(file descriptor)的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有某一个或多个发生了状态改变。

 

    文件在句柄在Linux里很多,如果你man某个函数,在函数返回值部分说到成功后有一个文件句柄被创建的都是的,如man socket可以看到“On success, a file descriptor for the new socket is returned.”而man 2 open可以看到“open() and creat() return the new file descriptor”,其实文件句柄就是一个整数,看socket函数的声明就明白了:

    int socket(int domain, int type, int protocol);

当然,我们最熟悉的句柄是012三个,0是标准输入,1是标准输出,2是标准错误输出。012是整数表示的,对应的FILE *结构的表示就是stdinstdoutstderr0就是stdin1就是stdout2就是stderr

比如下面这两段代码都是从标准输入读入9个字节字符:

 

 

#include <stdio.h>

#include <unistd.h>

#include <string.h>

int main(int argc, char ** argv)

{

        char buf[10] = "";

        read(0, buf, 9); /* 从标准输入 0 读入字符 */

        fprintf(stdout, "%s\n", buf); /* 向标准输出 stdout 写字符 */

        return 0;

}

/* **上面和下面的代码都可以用来从标准输入读用户输入的9个字符** */

#include <stdio.h>

#include <unistd.h>

#include <string.h>

int main(int argc, char ** argv)

{

        char buf[10] = "";

        fread(buf, 9, 1, stdin); /* 从标准输入 stdin 读入字符 */

        write(1, buf, strlen(buf));

        return 0;

}

   继续上面说的select,就是用来监视某个或某些句柄的状态变化的。select函数原型如下:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

函数的最后一个参数timeout显然是一个超时时间值,其类型是struct timeval *,即一个struct timeval结构的变量的指针,所以我们在程序里要申明一个struct timeval tv;然后把变量tv的地址&tv传递给select函数。struct timeval结构如下:

 

 

struct timeval {

             long    tv_sec;         /* seconds */

             long    tv_usec;        /* microseconds */

         };

   234三个参数是一样的类型: fd_set *,即我们在程序里要申明几个fd_set类型的变量,比如rdfds, wtfds, exfds,然后把这个变量的地址&rdfds, &wtfds, &exfds 传递给select函数。这三个参数都是一个句柄的集合,第一个rdfds是用来保存这样的句柄的:当句柄的状态变成可读的时系统就会告诉select函数返回,同理第二个wtfds是指有句柄状态变成可写的时系统就会告诉select函数返回,同理第三个参数exfds是特殊情况,即句柄上有特殊情况发生时系统会告诉select函数返回。特殊情况比如对方通过一个socket句柄发来了紧急数据。如果我们程序里只想检测某个socket是否有数据可读,我们可以这样:

 

fd_set rdfds; /* 先申明一个 fd_set 集合来保存我们要检测的 socket句柄 */

struct timeval tv; /* 申明一个时间变量来保存时间 */

int ret; /* 保存返回值 */

FD_ZERO(&rdfds); /* select函数之前先把集合清零 */

FD_SET(socket, &rdfds); /* 把要检测的句柄socket加入到集合里 */

tv.tv_sec = 1;

tv.tv_usec = 500; /* 设置select等待的最大时间为1秒加500毫秒 */

ret = select(socket + 1, &rdfds, NULL, NULL, &tv); /* 检测我们上面设置到集合rdfds里的句柄是否有可读信息 */

if(ret < 0) perror("select");/* 这说明select函数出错 */

else if(ret == 0) printf("超时\n"); /* 说明在我们设定的时间值1秒加500毫秒的时间内,socket的状态没有发生变化 */

else { /* 说明等待时间还未到1秒加500毫秒,socket的状态发生了变化 */

    printf("ret=%d\n", ret); /* ret这个返回值记录了发生状态变化的句柄的数目,由于我们只监视了socket这一个句柄,所以这里一定ret=1,如果同时有多个句柄发生变化返回的就是句柄的总和了 */

    /* 这里我们就应该从socket这个句柄里读取数据了,因为select函数已经告诉我们这个句柄里有数据可读 */

    if(FD_ISSET(socket, &rdfds)) { /* 先判断一下socket这外被监视的句柄是否真的变成可读的了 */

        /* 读取socket句柄里的数据 */

        recv(...);

    }

}

   注意select函数的第一个参数,是所有加入集合的句柄值的最大那个值还要加1。比如我们创建了3个句柄:

/************关于本文档********************************************

*filename: Linux网络编程一步一步学-select详解

*purpose: 详细说明select的用法

*wrote by: zhoulifa([email protected]) 周立发(http://zhoulifa.bokee.com)

Linux爱好者 Linux知识传播者 SOHO开发者最擅长C语言

*date time:2007-02-03 19:40

*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途

* 但请遵循GPL

*Thanks to:Google

*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力

* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!

*********************************************************************/

 

int sa, sb, sc;

sa = socket(...); /* 分别创建3个句柄并连接到服务器上 */

connect(sa,...);

sb = socket(...);

connect(sb,...);

sc = socket(...);

connect(sc,...);

 

FD_SET(sa, &rdfds);/* 分别把3个句柄加入读监视集合里去 */

FD_SET(sb, &rdfds);

FD_SET(sc, &rdfds);

   在使用select函数之前,一定要找到3个句柄中的最大值是哪个,我们一般定义一个变量来保存最大值,取得最大socket值如下:

 

int maxfd = 0;

if(sa > maxfd) maxfd = sa;

if(sb > maxfd) maxfd = sb;

if(sc > maxfd) maxfd = sc;

   然后调用select函数:

ret = select(maxfd + 1, &rdfds, NULL, NULL, &tv); /* 注意是最大值还要加1 */

   同样的道理,如果我们要检测用户是否按了键盘进行输入,我们就应该把标准输入0这个句柄放到select里来检测,如下:

 

FD_ZERO(&rdfds);

FD_SET(0, &rdfds);

tv.tv_sec = 1;

tv.tv_usec = 0;

ret = select(1, &rdfds, NULL, NULL, &tv); /* 注意是最大值还要加1 */

if(ret < 0) perror("select");/* 出错 */

else if(ret == 0) printf("超时\n"); /* 在我们设定的时间tv内,用户没有按键盘 */

else { /* 用户有按键盘,要读取用户的输入 */

    scanf("%s", buf);

}----------------------------------------------------------

Linux select学习笔记

 

 

select系统调用是用来让我们的程序监视多个文件描述符(file descrīptor)的状态变化的。程序会停在select这里等待,直到被监视的文件描述符有某一个或多个发生了状态改变。select()的机制中提供一fd_set的数据结构,实际上是一long类型的数组,每一个数组元素都能与一打开的文件描述符(不管是Socket描述符,还是其他文件或命名管道或设备描述符)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪一Socket或文件可读,

 

select函数原型如下:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

 

函数的最后一个参数timeout显然是一个超时时间值,其类型是struct timeval *,即一个struct timeval结构的变量的指针,所以我们在程序里要申明一个struct timeval tv;然后把变量tv的地址&tv传递给select函数。struct timeval结构如下:

 

struct timeval {

long   tv_sec;     /* seconds */

long   tv_usec;    /* microseconds */

};

 

234三个参数的类型是一样的: fd_set *,即我们在程序里要申明几个fd_set类型的变量,比如定义了rfds, wfds, efds

 

另外关于fd_set类型的变量,还有一组标准的宏定义来处理此类变量:

 

FD_ZERO(fd_set *fdset):清空fdset与所有文件描述符的联系。

 

FD_SET(int fd, fd_set *fdset):建立文件描述符fdfdset的联系。

 

FD_CLR(int fd, fd_set *fdset):清除文件描述符fdfdset的联系。

 

FD_ISSET(int fd, fd_set *fdset):检查fd_set联系的文件描述符fd是否可读写,>0表示可读写。

 

(关于fd_set及相关宏的定义见/usr/include/sys/types.h)定义的这三个参数都是描述符的集合,第一个rfds是用来保存这样的描述符的:当描述符的状态变成可读的时系统就会告诉select函数返回,第二个wfds是指有描述符状态变成可写的时系统就会告诉select函数返回,第三个参数efds是特殊情况,即描述符上有特殊情况发生时系统会告诉select函数返回。下面以一个输入为例来说明:

 

int fd1, fd2;         /* 在定义两个描述符*/

 

fd1 = socket(...);    /* 创建socket连接*/

 

fd2 = open(/dev/tyS0,O_RDWR); /* 打开一个串口*/

 

FD_ZERO(&rfds);       /* select函数之前先把集合清零 */

 

FD_SET(fd1, &rfds);   /* 分别把2个描述符加入读监视集合里去 */

 

FD_SET(fd2, &rfds);

 

int maxfd = 0;

 

maxfd = (fd1>fd2)?(fd1+1):(fd2+1);           /* 注意是最大值还要加1 */

 

ret = select(maxfd, &rfds, NULL, NULL, &tv); /*然后调用select函数*/

 

这样就可以使用一个开关语句(switch语句)来判断到底是哪一个输入源在输入数据。具体判断如下:

 

switchret{

 

case -1perror("select");/* 这说明select函数出错 */

 

case 0printf("超时\n"); /* 说明在设定的时间内,socket的状态没有发生变化 */

 

default

 

ifFD_ISSET(fd1, &rfds)处理函数1();/*socket有数据来*/

 

ifFD_ISSET(fd2, &rfds)处理函数2();/*ttyS0有数据来*/

 

}

 

 

 

 

以下来自网络搜索:

 

Linuxselect调用的过程:

 

1.用户层应用程序调用select(),底层调用poll())

 

2.核心层调用sys_select() ------> do_select()

 

最终调用文件描述符fd对应的struct file类型变量的struct file_operations *f_oppoll函数。

 

poll指向的函数返回当前可否读写的信息。

 

1)如果当前可读写,返回读写信息。

 

2)如果当前不可读写,则阻塞进程,并等待驱动程序唤醒,重新调用poll函数,或超时返回。

 

3.驱动需要实现poll函数。

 

当驱动发现有数据可以读写时,通知核心层,核心层重新调用poll指向的函数查询信息。

 

poll_wait(filp,&wait_q,wait) // 此处将当前进程加入到等待队列中,但并不阻塞

 

在中断中使用wake_up_interruptible(&wait_q)唤醒等待队列

 

 

 

----------------------------------------------------------

Socket编程中select()的妙用

 

用过 WinSock API 网友们知道:WinSock 编程中有一很方便的地方便是其

息驱动机制,不管是底层 API WSAAsyncSelect() 还是 MFC 的异步Socket类:

CAsyncSocket,都提供了诸如 FD_ACCEPTFD_READFD_CLOSE 之类的消息

供编程人员捕捉并处理。FD_ACCEPT 通知进程有客户方Socket请求连接,

FD_READ通知进程本地Socket有东东可读,FD_CLOSE通知进程对方Socket

关闭。那么,BSD Socket 是不是真的相形见拙呢?

 

非也! 'cause cpu love unix so.

 

BSD UNIX中有一系统调用芳名select()完全可以提供类似的消息驱动机制。

cpu郑重宣布:WinSockWSAAsyncSeclet()不过是此select()fork版!

 

bill也是fork出来的嘛,xixi.

 

select()的机制中提供一fd_set的数据结构,实际上是一long类型的数组,

每一个数组元素都能与一打开的文件句柄(不管是Socket句柄,还是其他

文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,

当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执

行了select()的进程哪一Socket或文件可读,下面具体解释:

 

#include  <sys/types.h>

#include  <sys/times.h>

#include  <sys/select.h>

 

int select(nfds, readfds, writefds, exceptfds, timeout)

int nfds;

fd_set *readfds, *writefds, *exceptfds;

struct timeval *timeout;

 

ndfsselect监视的文件句柄数,视进程中打开的文件数而定,一般设为呢要监视各文件

      中的最大文件号加一。

readfdsselect监视的可读文件句柄集合。

writefds: select监视的可写文件句柄集合。

exceptfdsselect监视的异常文件句柄集合。

timeout:本次select()的超时结束时间。(见/usr/sys/select.h

        可精确至百万分之一秒!)

 

readfdswritefds中映象的文件可读或可写或超时,本次select()

就结束返回。程序员利用一组系统提供的宏在select()结束时便可判

断哪一文件可读或可写。对Socket编程特别有用的就是readfds

几只相关的宏解释如下:

 

FD_ZERO(fd_set *fdset):清空fdset与所有文件句柄的联系。

FD_SET(int fd, fd_set *fdset):建立文件句柄fdfdset的联系。

FD_CLR(int fd, fd_set *fdset):清除文件句柄fdfdset的联系。

FD_ISSET(int fd, fdset *fdset):检查fdset联系的文件句柄fd是否

                                可读写,>0表示可读写。

(关于fd_set及相关宏的定义见/usr/include/sys/types.h

 

这样,你的socket只需在有东东读的时候才读入,大致如下:

 

...

int     sockfd;

fd_set  fdR;

struct  timeval timeout = ..;

...

for(;;) {

        FD_ZERO(&fdR);

        FD_SET(sockfd, &fdR);

        switch (select(sockfd + 1, &fdR, NULL, &timeout)) {

                case -1:

                        error handled by u;

                case 0:

                        timeout hanled by u;

                default:

                        if (FD_ISSET(sockfd)) {

                                now u read or recv something;

                                /* if sockfd is father and 

                                server socket, u can now

                                accept() */

                        }

        }

}

 

所以一个FD_ISSET(sockfd)就相当通知了sockfd可读。

至于struct timeval在此的功能,请man select。不同的timeval设置

使使select()表现出超时结束、无超时阻塞和轮询三种特性。由于

timeval可精确至百万分之一秒,所以WindowsSetTimer()根本不算

什么。你可以用select()做一个超级时钟。

 

FD_ACCEPT的实现?依然如上,因为客户方socket请求连接时,会发送

连接请求报文,此时select()当然会结束,FD_ISSET(sockfd)当然大

于零,因为有报文可读嘛!至于这方面的应用,主要在于服务方的父

Socket,你若不喜欢主动accept(),可改为如上机制来accept()

 

至于FD_CLOSE的实现及处理,颇费了一堆cpu处理时间,未完待续。

 

--

讨论关于利用select()检测对方Socket关闭的问题:

 

仍然是本地Socket有东东可读,因为对方Socket关闭时,会发一个关闭连接

通知报文,会马上被select()检测到的。关于TCP的连接(三次握手)和关

闭(二次握手)机制,敬请参考有关TCP/IP的书籍。

 

不知是什么原因,UNIX好象没有提供通知进程关于SocketPipe对方关闭的

信号,也可能是cpu所知有限。总之,当对方关闭,一执行recv()read()

马上回返回-1,此时全局变量errno的值是115,相应的sys_errlist[errno]

"Connect refused"(请参考/usr/include/sys/errno.h)。所以,在上

篇的for(;;)...select()程序块中,当有东西可读时,一定要检查recv()

read()的返回值,返回-1时要作出关断本地Socket的处理,否则select()

一直认为有东西读,其结果曾几令cpu伤心欲断针脚。不信你可以试试:不检

recv()返回结果,且将收到的东东(实际没收到)写至标准输出...

在有名管道的编程中也有类似问题出现。具体处理详见拙作:发布一个有用

Socket客户方原码。

 

至于主动写Socket时对方突然关闭的处理则可以简单地捕捉信号SIGPIPE并作

出相应关断本地Socket等等的处理。SIGPIPE的解释是:写入无读者方的管道。

在此不作赘述,请详man signal

 

以上是cpu在作tcp/ip数据传输实验积累的经验,若有错漏,请狂炮击之。

 

唉,昨天在hacker区被一帮孙子轰得差点儿没短路。ren cpu(奔腾的心) z80

 

补充关于select在异步(非阻塞)connect中的应用,刚开始搞socket编程的时候

我一直都用阻塞式的connect,非阻塞connect的问题是由于当时搞proxy scan

而提出的呵呵

通过在网上与网友们的交流及查找相关FAQ,总算知道了怎么解决这一问题.同样

select可以很好地解决这一问题.大致过程是这样的:

 

1.将打开的socket设为非阻塞的,可以用fcntl(socket, F_SETFL, O_NDELAY)

(有的系统用FNEDLAY也可).

 

2.connect调用,这时返回-1,但是errno被设为EINPROGRESS,意即connect仍旧

在进行还没有完成.

 

3.将打开的socket设进被监视的可写(注意不是可读)文件集合用select进行监视,

如果可写,

        getsockopt(socket, SOL_SOCKET, SO_ERROR, &error, sizeof(int));

来得到error的值,如果为零,connect成功.

 

在许多unix版本的proxyscan程序你都可以看到类似的过程,另外在solaris精华

->编程技巧中有一个通用的带超时参数的connect模块

 

===================================================================================

 

socket select 使用指南

原型

 

int select(

 

int nfds

 

fd_set* readfds

 

fd_set* writefds

 

fd_set* exceptfds

 

const struct timeval* timeout

 

);

 

第一个参数 nfds

 

linux下的描述:指定测试的描述符最大值,在0nfds都会被测试,

 

到了windows下: Ignored. The nfds parameter is included only for compatibility with Berkeley sockets. 忽略了。

 

accept开始.

 

首先:

 

SOCKET sock;

 

sock= socket(AF_INETSOCK_STREAM0);

 

struct sockaddr_in addr; //告诉sock 应该再什么地方licence

 

memset(&addr0sizeof(addr));

 

addr.sin_family=AF_INET;

 

addr.sin_port=htons(11111); //端口啦

 

addr.sin_addr.s_addr=htonl(INADDR_ANY); //在本机的所有ip上开始监听

 

bind (sock(sockaddr *)&addrsizeof(addr));//bind....

 

listen(sock5); ;//最大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(sockFIONBIO&ul); //用非阻塞的连接

 

//现在开始用select

 

FD_SET(sock&rfd); //sock放入要测试的描述符集就是说把sock放入了rfd里面这样下一步调用selectrfd进行测试的时候就会测试sock(因为我们将sock放入的rdf) 一个描述符集可以包含多个被测试的描述符

 

if(select(sock+1&rfd00 &timeout)==0) // select的第一个参数是可以忽略的(这样写是为了保持和linux下一致) 第二个参数放入需要测试的读描述符集(也就是说假如这里面有一个描述符可以读取了select就返回) 第三个放入需要测试的写描述符集第四个放入"可执行描述符集"(??我也不知道) 第五个参数是超时时间(如果过了这个超时时间依然没有描述符预备好select也返回.(如果为NULL那就一直等到一个描述符集变成准备好的状态)

 

{ //这个大括号接上面的返回0那么就超过了timeout预定的时间

 

//处理....

 

}

 

if(FD_ISSET(sock&rfd))

 

{ //有一个描述符准备好了

 

socka=accept(sock00); //好了接受它吧

 

//你还要判定一下socka是不是有效的socket才行....

 

-------------------------------------------------------------------------------------------------------------------------------

 

一般的情况下

 

假设你要判断两个socket 是否可读可写那就这样:

 

假设 socka sockb 是两个socket 他们已经被连接上并且能够收发数据

 

fd_set rfdwfd;//一个用来测试读一个用来测试写

 

FD_ZERO(&rfd);

 

FD_ZERO(&wfd);

 

FD_SET(socka&rfd);//socka放入读描述符集

 

FD_SET(sockb&rfd);//sockb放入读描述符集

 

FD_SET(socka&wfd);socka放入写描述符集

 

 

 

原型

 

int select(

 

int nfds

 

fd_set* readfds

 

fd_set* writefds

 

fd_set* exceptfds

 

const struct timeval* timeout

 

);

 

第一个参数 nfds

 

linux下的描述:指定测试的描述符最大值,在0nfds都会被测试,

 

到了windows下: Ignored. The nfds parameter is included only for compatibility with Berkeley sockets. 忽略了。

 

accept开始.

 

首先:

 

SOCKET sock;

 

sock= socket(AF_INETSOCK_STREAM0);

 

struct sockaddr_in addr; //告诉sock 应该再什么地方licence

 

memset(&addr0sizeof(addr));

 

addr.sin_family=AF_INET;

 

addr.sin_port=htons(11111); //端口啦

 

addr.sin_addr.s_addr=htonl(INADDR_ANY); //在本机的所有ip上开始监听

 

bind (sock(sockaddr *)&addrsizeof(addr));//bind....

 

listen(sock5); ;//最大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(sockFIONBIO&ul); //用非阻塞的连接

 

//现在开始用select

 

FD_SET(sock&rfd); //sock放入要测试的描述符集就是说把sock放入了rfd里面这样下一步调用selectrfd进行测试的时候就会测试sock(因为我们将sock放入的rdf) 一个描述符集可以包含多个被测试的描述符

 

if(select(sock+1&rfd00 &timeout)==0) // select的第一个参数是可以忽略的(这样写是为了保持和linux下一致) 第二个参数放入需要测试的读描述符集(也就是说假如这里面有一个描述符可以读取了select就返回) 第三个放入需要测试的写描述符集第四个放入"可执行描述符集"(??我也不知道) 第五个参数是超时时间(如果过了这个超时时间依然没有描述符预备好select也返回.(如果为NULL那就一直等到一个描述符集变成准备好的状态)

 

{ //这个大括号接上面的返回0那么就超过了timeout预定的时间

 

//处理....

 

}

 

if(FD_ISSET(sock&rfd))

 

{ //有一个描述符准备好了

 

socka=accept(sock00); //好了接受它吧

 

//你还要判定一下socka是不是有效的socket才行....

 

-------------------------------------------------------------------------------------------------------------------------------

 

一般的情况下

 

假设你要判断两个socket 是否可读可写那就这样:

 

假设 socka sockb 是两个socket 他们已经被连接上并且能够收发数据

 

fd_set rfdwfd;//一个用来测试读一个用来测试写

 

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&wfd00)) //测试这两个描述符集永不超时其中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函数的应用

 

今天在读取一个485串口设备的时候遇到了select函数,开始一直不明白该函数的作用。

 

在网上查找了select函数的用法后,才知道该函数的精妙用法,因为该485设备需要写入一串命令,然后读取该设备,得到想要的数据,但是在写入读取的过程中就会碰到何时读取的问题,因为485设备得到命令后会有一个间隔时间,所以在读取过程中会碰到不知485设备何时预备好了数据的问题,select函数很好的解决了这一问题。

 

对于这个问题,除了select函数,我还没有看到更好的解决方法。

 

网上更多的是select函数在socket通信中的应用,由于socket通信部分的学习刚刚开始,就留待以后跟大家探讨。

 

 

int select(int maxfdpfd_set *readfdsfd_set *writefdsfd_set *errorfdsstruct timeval *timeout);

先说明两个结构体:

第一,struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,比如清空集合FD_ZERO(fd_set *),将一个给定的文件描述符加入集合之中FD_SET(int fd_set *),将一个给定的文件描述符从集合中删除FD_CLR(int fd_set*),检查集合中指定的文件描述符是否可以读写FD_ISSET(int fd_set* )。一会儿举例说明。

第二,struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。

详细解释select的参数:

int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不准确。

fd_set *readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,假如这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判定是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。

fd_set *writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。

fd_set *errorfds同上面两个参数的意图,用来监视文件错误异常。

struct timeval* timeoutselect的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为00毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都马上返回继承执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即selecttimeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。

返回值:

负值:select错误正值:某些文件可读写或出错 0:等待超时,没有可读写或错误的文件

在有了select后可以写出像样的网络程序来!举个简朴的例子,就是从网络上接受数据写入一个文件中。

 

 

 

函数说明:

 

select()用来等待文件描述词状态的改变。参数maxfdp代表最大的文件描述词加1,参数readfdswritefds exceptfds 称为描述词组,是用往返传该描述词的读,写或例外的状况。底下的宏提供了处理这三种描述词组的方式:

FD_CLR(inr fdfd_set* set);用来清除描述词组set中相关fd 的位

FD_ISSET(int fdfd_set *set);用来测试描述词组set中相关fd 的位是否为真

FD_SETint fdfd_set*set);用来设置描述词组set中相关fd的位

FD_ZEROfd_set *set);用来清除描述词组set的全部位

===================================================================================

 

linux编程select函数处理I/O复用(转载)

 

阻塞操作是指,在执行设备操作时,若不能获得资源,则进程挂起直到满意可操作的条件再进行操作。非阻塞操作的进程在不能进行设备操作时,并不挂起。被挂起的进程进入sleep状态,被从调度器的运行队列移走,直到等待的条件被满意。

 

其中,selectI/O多路转接模型是处理I/O复用的一个高效的方法。

 

 

下面是 select()

 

#include ;

 

#include ;

 

#include ;

 

int select(int numfds fd_set *readfds fd_set *writefdsfd_set

 

*exceptfds struct timeval *timeout);

 

这个函数监视一系列文件描述符,特殊是 readfdswritefds exceptfds。假如你想知道你是否能够从标准输入和套接字描述符 sockfd 读入数据,你只要将文件描述符 0 sockfd 加入到集合 readfds 中。参 numfds 应该等于最高的文件描述符的值加1。在这个例子中,你应该设置该值为 sockfd+1。因为它一定大于标准输入的文件描述符 (0)当函数 select() 返回的时候,readfds 的值修改为反映你选择的哪个文件描述符可以读。你可以用下面讲到的宏 FD_ISSET() 来测试。在我们继承下去之前,让我来讲讲如何对这些集合进行操作。每个集合类型都是 fd_set。下面有一些宏来对这个类型进行操作:

 

FD_ZERO(fd_set *set) 清除一个文件描述符集合

 

FD_SET(int fd fd_set *set) - 添加fd到集合

 

FD_CLR(int fd fd_set *set) 从集合中移去fd

 

FD_ISSET(int fd fd_set *set) 测试fd是否在集合中

 

最后,是有点古怪的数据结构 struct timeval。有时你可不想永远等待别人发送数据过来。也许什么事情都没有发生的时候你也想每隔96秒在终端上打印字符串 "Still Going..."。这个数据结构答应你设定一个时间,假如时间到了,而 select() 还没有找到一个预备好的文件描述符,它将返回让你继承处理。

 

数据结构 struct timeval 是这样的:

 

struct timeval {

 

int tv_sec; /* seconds */

 

int tv_usec; /* microseconds */

 

};

 

只要将 tv_sec 设置为你要等待的秒数,将 tv_usec 设置为你要等待的微秒数就可以了。当然,函数返回的时候 timeout 可能是剩余的时间,之所以是可能,是因为它依靠于你的 Unix 操作系统。

 

哈!我们现在有一个微秒级的定时器!别计算了,标准的 Unix 系统的时间片是100毫秒,所以无论你如何设置你的数据结构 struct timeval你都要等待那么长的时间。

 

还有一些有趣的事情:如果你设置数据结构 struct timeval 中的数据为 0select() 将立刻超时,这样就可以有效地轮询集合中的所有的文件描述符。如果你将参数 timeout 赋值为 NULL,那么将永远不会发生超时,即一直等到第一个文件描述符就绪。最后,如果你不是很关心等待多长时间,那么就把它赋为 NULL 吧。

 

下面的代码演示了在标准输入上等待 2.5 秒:

 

#include ;

 

#include ;

 

#include ;

 

#define STDIN 0 /* file descriptor for standard input */

 

main()

 

{

 

struct timeval tv;

 

fd_set readfds;

 

tv.tv_sec = 2;

 

tv.tv_usec = 500000;

 

FD_ZERO(&readfds);

 

FD_SET(STDIN &readfds);

 

/* don't care about writefds and exceptfds: */

 

select(STDIN+1 &readfds NULL NULL &tv);

 

if (FD_ISSET(STDIN &readfds))

 

printf("A key was pressed!\n");

 

else

 

printf("Timed out.\n");

 

}

 

如果你是在一个 line buffered 终端上,那么你敲的键应该是回车 (RETURN),否则无论如何它都会超时。

 

 

现在,你可能回认为这就是在数据报套接字上等待数据的方式--你是对的:它可能是。有些 Unix 系统可以按这种方式,而另外一些则不能。你在尝试以前可能要先看看本系统的 man page 了。

 

最后一件关于 select() 的事情:如果你有一个正在侦听 (listen()) 的套接字,你可以通过将该套接字的文件描述符加入到 readfds 集合中来看是否有新的连接。

 

这就是我关于函数select() 要讲的所有的东西。

 

一般来说,在使用select函数之前,首先使用FD_ZEROFD_SET来初始化文件描述符集,在使用了select函数时,可循环使用FD_ISSET测试描述符集,在执行完对相关后文件描述符后,使用FD_CLR来清除描述符集。

 

 

本例主要实现将文件hello1里的内容读出,并将此内容每隔10s写入hello2中。在这里建立了两个描述符集,其中一个描述符集inset1是用于读取文件内容,另一个inset2是用于写入文件。两个文件描述符fds[0] fds[1]分别指向这一文件描述符。在首先初始化完各文件描述符集之后,就开始了循环测试这两个文件描述符是否读写,由于在这里没有阻塞,所以文件描述符处理预备就绪的状态。这时,就分别对文件描述符fds[0]fds[1]进行读写操作。

 

#include

 

#include

 

#include

 

#include

 

#include

 

#include

 

#include

 

#include

 

int main(void){

 

int fds[2];

 

char buf[7];

 

int ircmaxfd;

 

fd_set inset1inset2;

 

struct timeval tv;

 

if ((fds[0]=open("hello1"O_CREAT | O_RDWR0666))0){

 

buf[rc]='\0';

 

printf("rc=%dwrite:%s\n"rcbuf);

 

}

 

else

 

perror("wirte");

 

sleep(10);

 

}

 

}

 

}

 

exit(0);

 

}

 

=================================================================================

 

select函数的学习

 

 

select系统调用是用来让我们的程序监视多个文件描述符(file descrīptor)的状态变化的。程序会停在select这里等待,直到被监视的文件描述符有某一个或多个发生了状态改变。select()的机制中提供一fd_set的数据结构,实际上是一long类型的数组,每一个数组元素都能与一打开的文件描述符(不管是Socket描述符,还是其他文件或命名管道或设备描述符)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪一Socket或文件可读,

 

select函数原型如下:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

 

函数的最后一个参数timeout显然是一个超时时间值,其类型是struct timeval *,即一个struct timeval结构的变量的指针,所以我们在程序里要申明一个struct timeval tv;然后把变量tv的地址&tv传递给select函数。struct timeval结构如下:

 

struct timeval {

long   tv_sec;     /* seconds */

long   tv_usec;    /* microseconds

 

}

 

234三个参数的类型是一样的: fd_set *,即我们在程序里要申明几个fd_set类型的变量,比如定义了rfds, wfds, efds

 

另外关于fd_set类型的变量,还有一组标准的宏定义来处理此类变量:

 

FD_ZERO(fd_set *fdset):清空fdset与所有文件描述符的联系。

 

FD_SET(int fd, fd_set *fdset):建立文件描述符fdfdset的联系。

 

FD_CLR(int fd, fd_set *fdset):清除文件描述符fdfdset的联系。

 

FD_ISSET(int fd, fd_set *fdset):检查fd_set联系的文件描述符fd是否可读写,>0表示可读写。

 

(关于fd_set及相关宏的定义见/usr/include/sys/types.h)定义的这三个参数都是描述符的集合,第一个rfds是用来保存这样的描述符的:当描述符的状态变成可读的时系统就会告诉select函数返回,第二个wfds是指有描述符状态变成可写的时系统就会告诉select函数返回,第三个参数efds是特殊情况,即描述符上有特殊情况发生时系统会告诉select函数返回。下面以一个输入为例来说明:

 

int fd1, fd2;         /* 在定义两个描述符*/

 

fd1 = socket(...);    /* 创建socket连接*/

 

fd2 = open(/dev/tyS0,O_RDWR); /* 打开一个串口*/

 

FD_ZERO(&rfds);       /* select函数之前先把集合清零 */

 

FD_SET(fd1, &rfds);   /* 分别把2个描述符加入读监视集合里去 */

 

FD_SET(fd2, &rfds);

 

int maxfd = 0;

 

maxfd = (fd1>fd2)?(fd1+1):(fd2+1);           /* 注意是最大值还要加1 */

 

ret = select(maxfd, &rfds, NULL, NULL, &tv); /*然后调用select函数*/

 

这样就可以使用一个开关语句(switch语句)来判断到底是哪一个输入源在输入数据。具体判断如下:

 

switchret{

 

case -1perror("select");/* 这说明select函数出错 */

 

case 0printf("超时\n"); /* 说明在设定的时间内,socket的状态没有发生变化 */

 

default

 

ifFD_ISSET(fd1, &rfds)处理函数1();/*socket有数据来*/

 

ifFD_ISSET(fd2, &rfds)处理函数2();/*ttyS0有数据来*/

 

}

 

 ==========================================================

 

 

 

多路复用I/O 

 

 

为了解决创建子进程带来的系统资源消耗,人们又想出了多路复用I/O模型

 

首先介绍一个函数select 

 

 int select(int nfds,fd_set *readfds,fd_set *writefds,

                fd_set *except fds,struct timeval *timeout)

 void FD_SET(int fd,fd_set *fdset)

 void FD_CLR(int fd,fd_set *fdset)

 void FD_ZERO(fd_set *fdset)

 int FD_ISSET(int fd,fd_set *fdset)

 

一般的来说当我们在向文件读写时,进程有可能在读写出阻塞,直到一定的条件满足. 比如我们从一个套接字读数据时,可能缓冲区里面没有数据可读(通信的对方还没有发送数据过来),这个时候我们的读调用就会等待(阻塞)直到有数据可读.如果我们不希望阻塞,我们的一个选择是用select系统调用. 只要我们设置好select的各个参数,那么当文件可以读写的时候select"通知"我们说可以读写了. readfds所有要读的文件文件描述符的集合 

writefds所有要的写文件文件描述符的集合 

 

exceptfds其他的服要向我们通知的文件描述符 

 

timeout超时设置

 

nfds所有我们监控的文件描述符中最大的那一个加

 

在我们调用select时进程会一直阻塞直到以下的一种情况发生. 1)有文件可以读.2)有文件可以写.3)超时所设置的时间到

 

为了设置文件描述符我们要使用几个宏. FD_SETfd加入到fdset 

 

FD_CLRfdfdset里面清除 

 

FD_ZEROfdset中清除所有的文件描述符 

 

FD_ISSET判断fd是否在fdset集合中 

 

使用select的一个例子 

 

int use_select(int *readfd,int n)

{

   fd_set my_readfd;

   int maxfd;

   int i;

   

   maxfd=readfd[0];

   for(i=1;i    if(readfd[i]>maxfd) maxfd=readfd[i];

   while(1)

   {

        /*   将所有的文件描述符加入   */

        FD_ZERO(&my_readfd);

        for(i=0;i            FD_SET(readfd[i],*my_readfd);

        /*     进程阻塞                 */

        select(maxfd+1,& my_readfd,NULL,NULL,NULL); 

        /*        有东西可以读了       */

        for(i=0;i          if(FD_ISSET(readfd[i],&my_readfd))

              {

                  /* 原来是我可以读了  */ 

                        we_read(readfd[i]);

              }

   }

}

 

使用select后我们的服务器程序就变成了

 

 

        初始话(socket,bind,listen);

        

    while(1)

        {

        设置监听读写文件描述符(FD_*);   

        

        调用select;

        

        如果是倾听套接字就绪,说明一个新的连接请求建立

            

                建立连接(accept);

                加入到监听文件描述符中去;

             }

       否则说明是一个已经连接过的描述符

                {

                    进行操作(read或者write);

                 }

                        

        }               

 

你可能感兴趣的:(linux,socket,struct,测试,null,Descriptor)