详述socket编程之select()和poll()函数

select()函数和poll()函数均是主要用来处理多路I/O复用的情况。比如一个服务器既想等待输入终端到来,又想等待若干个套接字有客户请求到达,这时候就需要借助select或者poll函数了。

(一)select()函数

原型如下:

1  int  select( int  fdsp1, fd_set  * readfds, fd_set  * writefds, fd_set  * errorfds,  const   struct  timeval  * timeout);

各个参数含义如下:
  • int fdsp1:最大描述符值 + 1
  • fd_set *readfds:对可读感兴趣的描述符集
  • fd_set *writefds:对可写感兴趣的描述符集
  • fd_set *errorfds:对出错感兴趣的描述符集
  • struct timeval *timeout:超时时间(注意:对于linux系统,此参数没有const限制,每次select调用完毕timeout的值都被修改为剩余时间,而unix系统则不会改变timeout值)
select函数会在发生以下情况时返回:
  1. readfds集合中有描述符可读
  2. writefds集合中有描述符可写
  3. errorfds集合中有描述符遇到错误条件
  4. 指定的超时时间timeout到了
当select返回时, 描述符集合将被修改以指示哪些个描述符正处于可读、可写或有错误状态。可以用FD_ISSET宏对描述符进行测试以找到状态变化的描述符。如果select因为超时而返回的话,所有的描述符集合都将被清空。
select函数返回状态发生变化的描述符总数。返回0意味着超时。失败则返回-1并设置errno。可能出现的错误有:EBADF(无效描述符)、EINTR(因终端而返回)、EINVAL(nfds或timeout取值错误)。
设置描述符集合通常用如下几个宏定义:

1  FD_ZERO(fd_set  * fdset);                 /*  clear all bits in fdset            */
2  FD_SET( int  fd, fd_set  * fdset);          /*  turn on the bit for fd in fd_set   */
3  FD_CLR( int  fd, fd_set  * fdset);          /*  turn off the bit for fd in fd_set  */
4  int  FD_ISSET( int  fd, fd_set  * fdset);    /*  is the bit for fd on in fdset?     */

如:

1  fd_set rset;
2  FD_ZERO( & rset);                         /*  initialize the set: all bits off   */
3  FD_SET( 1 & rset);                       /*  turn on bit for fd 1               */
4  FD_SET( 4 & rset);                       /*  turn on bit for fd 4               */
5  FD_SET( 5 & rset);                       /*  turn on bit for fd 5               */

当select返回的时候,rset位都将被置0,除了那些有变化的fd位。
当发生如下情况时认为是可读的:
  1. socket的receive buffer中的字节数大于socket的receive buffer的low-water mark属性值。(low-water mark值类似于分水岭,当receive buffer中的字节数小于low-water mark值的时候,认为socket还不可读,只有当receive buffer中的字节数达到一定量的时候才认为socket可读)
  2. 连接半关闭(读关闭,即收到对端发来的FIN包)
  3. 发生变化的描述符是被动套接字,而连接的三路握手完成的数量大于0,即有新的TCP连接建立
  4. 描述符发生错误,如果调用read系统调用读套接字的话会返回-1。
当发生如下情况时认为是可写的:
  1. socket的send buffer中的字节数大于socket的send buffer的low-water mark属性值以及socket已经连接或者不需要连接(如UDP)。
  2. 写半连接关闭,调用write函数将产生SIGPIPE
  3. 描述符发生错误,如果调用write系统调用写套接字的话会返回-1。
注意:
select默认能处理的描述符数量是有上限的,为FD_SETSIZE的大小。
对于timeout参数,如果置为NULL,则表示wait forever;若timeout->tv_sec = timeout->tv_usec = 0,则表示do not wait at all;否则指定等待时间。
如果使用select处理多个套接字,那么需要使用一个数组(也可以是其他结构)来记录各个描述符的状态。而使用poll则不需要,下面看poll函数。

(二)poll()函数

原型如下:

1  int  poll( struct  pollfd  * fdarray, unsigned  long  nfds,  int  timeout);

各参数含义如下:
  • struct pollfd *fdarray:一个结构体,用来保存各个描述符的相关状态。
  • unsigned long nfds:fdarray数组的大小,即里面包含有效成员的数量。
  • int timeout:设定的超时时间。(以毫秒为单位)
poll函数返回值及含义如下:
  • -1:有错误产生
  • 0:超时时间到,而且没有描述符有状态变化
  • >0:有状态变化的描述符个数
着重讲fdarray数组,因为这是它和select()函数主要的不同的地方:
pollfd的结构如下:

1  struct  pollfd {
2      int  fd;                   /*  descriptor to check  */
3      short  events;       /*  events of interest on fd  */
4      short  revents;      /*  events that occured on fd  */
5  };

其实poll()和select()函数要处理的问题是相同的,只不过是不同组织在几乎相同时刻同时推出的,因此才同时保留了下来。select()函数把可读描述符、可写描述符、错误描述符分在了三个集合里,这三个集合都是用bit位来标记一个描述符,一旦有若干个描述符状态发生变化,那么它将被置位,而其他没有发生变化的描述符的bit位将被clear,也就是说select()的readset、writeset、errorset是一个value-result类型,通过它们传值,而也通过它们返回结果。这样的一个坏处是每次重新select 的时候对集合必须重新赋值。而poll()函数则与select()采用的方式不同,它通过一个结构数组保存各个描述符的状态,每个结构体第一项fd代表描述符,第二项代表要监听的事件,也就是感兴趣的事件,而第三项代表poll()返回时描述符的返回状态。合法状态如下:

  • POLLIN:                有普通数据或者优先数据可读
  • POLLRDNORM:    有普通数据可读
  • POLLRDBAND:    有优先数据可读
  • POLLPRI:              有紧急数据可读
  • POLLOUT:            有普通数据可写
  • POLLWRNORM:   有普通数据可写
  • POLLWRBAND:    有紧急数据可写
  • POLLERR:            有错误发生
  • POLLHUP:            有描述符挂起事件发生
  • POLLNVAL:          描述符非法

对于POLLIN | POLLPRI等价与select()的可读事件;POLLOUT | POLLWRBAND等价与select()的可写事件;POLLIN 等价与POLLRDNORM | POLLRDBAND,而POLLOUT等价于POLLWRBAND。如果你对一个描述符的可读事件和可写事件以及错误等事件均感兴趣那么你应该都进行相应的设置。
对于timeout的设置如下:
  • INFTIM:   wait forever
  • 0:            return immediately, do not block
  • >0:         wait specified number of milliseconds

对于select()和poll()函数的讲解暂时到此。 更多细节请参考下面这篇博文:http://www.cppblog.com/just51living/archive/2011/07/28/151995.html



参考代码
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <unistd.h>
#include <poll.h>
#include <sys/epoll.h> 

#define  SERV_PORT    5060
#define  SERVER_IP    "192.168.2.35"
#define  MAX                2
#define  USE_SELECT
#define  USE_POLL

int readfd = 0;
int creat_udp_socket();


int main(int argc, char **argv)
{
    struct sockaddr_in cliaddr;                /* IPv4套接口地址定义*/
    int addr_len = sizeof(struct sockaddr_in);
    char recvbuff[600] = {0};
    int lenth = 0;
    fd_set rfds;
      struct timeval tv;
      int retval;
    int count = 0;
    
    if( creat_udp_socket() != NULL )    
    {
        printf("creat_udp_socket error! ");
        return 0;
    }
    
    printf("readfd:%d ",readfd);

/************** epoll举例  ****************/
#if 1
    int epfd;
    int nfds = 0;
    int loop = 0;
    struct epoll_event ev;
    struct epoll_event events[MAX];
    
    epfd = epoll_create(MAX);
      
    if( epfd < 0 ) 
    {
        printf("epoll_create error! ");
        return 0;
    }
    
    /*ev.events = EPOLLIN|EPOLLET;*/
    
    /* 监听读事件 */
    ev.events = EPOLLIN;
    /* 设置监听的句柄 */
    ev.data.fd = readfd;
    
    /* 注册epoll事件 */
      epoll_ctl(epfd,EPOLL_CTL_ADD,readfd,&ev);  
  
      while(1)
      {
        /* 等待epoll事件的发生 */  
            nfds=epoll_wait(epfd, events, 200, -1);
    
            /* 如果没有数据可读,相当与轮询,将会消耗大量的CPU资源 */
          nfds = epoll_wait(epfd, events, 200, 0);
            /* 处理所发生的所有事件 */       
            for( loop = 0; loop < nfds; loop++ )
            {
                if( events[loop].events & EPOLLIN )
                {
                    lenth = recvfrom (events[loop].data.fd, recvbuff, sizeof(recvbuff), 0, (struct sockaddr *)&cliaddr, &addr_len);
              
                  if (lenth <= 0 )
                  {
                      printf("receve data errer! ");
                  }
              
                  /*printf("%s",recvbuff);*/
                  printf("%d ",count++);
                }
            }
            /*sleep(1);*/

      }
      close(readfd);
      close(epfd);
#endif 
  
/************** poll举例  ****************/  
#if 0    
    struct pollfd poll_list[MAX];
    int loop = 0;
    
    for( loop = 0; loop < MAX; loop++ )
    {
        poll_list[loop].fd = -1;
        poll_list[loop].events = NULL;
    }
    
    poll_list[0].fd = readfd;
    poll_list[0].events = POLLIN;

    
    while(1)
    {
        retval = poll(poll_list,MAX,-1);
        
        if( retval > 0 )
        {
            if( poll_list[0].revents & POLLIN )
            {
                lenth = recvfrom (readfd,recvbuff,sizeof(recvbuff),0,(struct sockaddr *)&cliaddr,&addr_len);
              
              if (lenth <= 0 )
              {
                  printf("receve data errer! ");
              }
              /*printf("%s",recvbuff);*/
              printf("%d ",count++);

            }
        }
        sleep(1);
    }
#endif

/************** select举例  ****************/  
#if 0
    while(1)
    {
        /* Watch stdin (fd 0) to see when it has input. */
          FD_ZERO(&rfds);
          FD_SET(readfd, &rfds);
      
          /* Wait up to five seconds. */
          tv.tv_sec = 200;
          tv.tv_usec = 0;
      
          /*retval = select (FD_SETSIZE, &rfds, NULL, NULL, &tv);*/
          /*retval = select (FD_SETSIZE, &rfds, NULL, NULL, NULL);*/
          retval = select (readfd+1, &rfds, NULL, NULL, NULL);
      
          if( retval > 0 )
          {
              if( FD_ISSET(readfd, &rfds) )
              {
                  lenth = recvfrom (readfd,recvbuff,sizeof(recvbuff),0,(struct sockaddr *)&cliaddr,&addr_len);
              
                  if (lenth <= 0 )
                  {
                      printf("receve data errer! ");
                  }
                  /*printf("%s",recvbuff);*/
                  printf("%d ",count++);
              }
          
          }
      
    }

#endif 

/*
    while(1)
    {
        lenth = recvfrom (readfd,recvbuff,sizeof(recvbuff),0,(struct sockaddr *)&cliaddr,&addr_len);
        if (lenth <= 0 )
        {
            printf("receve data errer! ");
        }
        else
        {
            printf ("recever string: %s",recvbuff);
        }
    }
    
    sleep(20);
*/

    return 0;

}

int creat_udp_socket()
{
    struct sockaddr_in servaddr, cliaddr;                /* IPv4套接口地址定义*/
    int addr_len = sizeof(struct sockaddr_in);
    bzero(&servaddr, sizeof(servaddr));                  /* 地址结构清零 */
    servaddr.sin_family = AF_INET;                       /* IPv4协议 */
    /*servaddr.sin_addr.s_addr = inet_addr (SERVER_IP);*/    /* 指定地址 */
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 
    servaddr.sin_port = htons(SERV_PORT);           


    readfd = socket (AF_INET, SOCK_DGRAM, 0);            /* 建立UDP套接字*/
    if(readfd < 0)
    {
        printf("socket errer! ");
        return -1;
    }
    /*分配协议地址,绑定端口*/
    
    if( bind(readfd, (struct sockaddr *)&servaddr, sizeof (struct sockaddr_in)) == -1 )
    {
        printf("bind errer! ");
        return -1;
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值