I/O复用之select与pselect

IO复用概念

有些进程需要一种预先告知内核的能力,使得内核一旦发现进程指定的一个或者多个I/O条件就绪(即是说,输入已经准备好读取,或者,描述字已经能承接更多的输出),它就通知进程,这个能力就成为I/O复用,是由select和poll这两个函数支持的。其中,关于poll的讲解可以参考http://blog.csdn.net/pngynghay/article/details/17327179。另外,select和poll的增强版本epoll模型,可以参考http://blog.csdn.net/pngynghay/article/details/8169455

典型的网络应用场合

  • 当客户端需要处理多个描述字(通常是交互式输入和网络套接口)时,必须使用IO复用。
  • 一个客户同时处理多个套接口是可能的,不过,比较少见。
  • 如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般就要哦使用IO复用。
  • 如果一个服务器既要处理TCP,又要处理UDP,一般要使用IO复用。
  • 如果一个服务器要处理多个服务或者多个协议,一般就要使用IO复用。

注:IO复用并非只限于网络编程。

select函数

函数原型

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

返回值:就绪描述字的正数目,0表示超时,-1表示你出错

参数:

maxfdpl:指定待测试的描述字个数,它的值是待测试的最大描述字加1(因为描述字是从0开始的);select.h中定义的FD_SETSIZE是数据类型fd_set中的描述字总数,通常为1024。

readset:读集合

writeset:写集合

exceptset:异常集合

timeout:告知内核等待所指定描述字中的任何一个就绪可花多长时间,timeval结构体用于指定这段时间的秒数和微秒数。如果该参数为NULL,那么,select函数将一直等待下去,直到有描述字准备好IO返回;如果该参数不为NULL,并且timeout为0(即定时器值的微秒数和秒数均为0),那么,select检查描述字后立即返回,成为轮询;如果该参数不为NULL,且timeout不为0,那么,select等待时间不超过timeout值,且在等待期间,如果有一个IO准备就绪,select也立即返回。

 

该函数允许进程指示内核等待多个事件中 的任何一个发生,并仅在有一个或者多个事件发生或者经历一段指定的时间后才唤醒它。我们调用select函数告诉内核对哪些描述字感兴趣以及等待多长时间。

注:我们感兴趣的描述字不局限于套接口,任何描述字都可以使用select来测试。

利用select函数,判断套接字上是否存在数据,或者能否向一个套接字写入数据。目的是防止应用程序在套接字处于锁定模式时,调用recv(或send)从没有数据的套接字上接收数据,被迫进入阻塞状态。

 

fd_set是一个SOCKET队列,以下宏可以对该队列进行操作:

FD_CLR( s, *set) 从队列set删除句柄s;

FD_ISSET( s, *set) 检查句柄s是否存在与队列set中;

FD_SET( s, *set )把句柄s添加到队列set中;

FD_ZERO( *set ) 把set队列初始化成空队列.

 

注:select函数的三个fd_set参数所指向的描述字集,都是值-结果参数。调用该函数时,我们指定所关心的描述字的值,该函数返回时,结果表示哪些描述字已经准备就绪。该函数返回后,我们使用FD_ISSET宏来测试fd_set数据类型中的描述子。描述字集中任何与未就绪的描述字想对应的位均清0,为此,每次重新调用select函数时,我们都得再次把我们感兴趣的所有描述字集的位置1.

 

附Linux man部分信息

NAME
       select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous I/O multiplexing

SYNOPSIS
       /* According to POSIX.1-2001 */
       #include <sys/select.h>

       /* According to earlier standards */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>

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

       void FD_CLR(int fd, fd_set *set);
       int FD_ISSET(int fd, fd_set *set);
       void FD_SET(int fd, fd_set *set);
       void FD_ZERO(fd_set *set);

       #define _XOPEN_SOURCE 600
       #include <sys/select.h>

       int pselect(int nfds, fd_set *readfds, fd_set *writefds,
                   fd_set *exceptfds, const struct timespec *timeout,
                   const sigset_t *sigmask);

DESCRIPTION
       select()  and pselect() allow a program to monitor multiple file descriptors, waiting until one or
       more of the file descriptors become "ready" for some class of I/O operation  (e.g.,  input  possi-
       ble).   A  file  descriptor is considered ready if it is possible to perform the corresponding I/O
       operation (e.g., read(2)) without blocking.

       The operation of select() and pselect() is identical, with three differences:

       (i)    select() uses a timeout that is a struct timeval (with  seconds  and  microseconds),  while
              pselect() uses a struct timespec (with seconds and nanoseconds).

       (ii)   select()  may  update  the  timeout argument to indicate how much time was left.  pselect()
              does not change this argument.

       (iii)  select() has no sigmask argument, and behaves as pselect() called with NULL sigmask.

       Three independent sets of file descriptors are watched.  Those listed in readfds will  be  watched
       to  see  if  characters  become  available  for reading (more precisely, to see if a read will not
       block; in particular, a file descriptor is also ready on end-of-file), those in writefds  will  be
       watched  to  see if a write will not block, and those in exceptfds will be watched for exceptions.
       On exit, the sets are modified in place to indicate which file descriptors actually  changed  sta-
       tus.   Each  of the three file descriptor sets may be specified as NULL if no file descriptors are
       to be watched for the corresponding class of events.

       Four macros are provided to manipulate the sets.  FD_ZERO() clears a set.  FD_SET()  and  FD_CLR()
       respectively add and remove a given file descriptor from a set.  FD_ISSET() tests to see if a file
       descriptor is part of the set; this is useful after select() returns.

       nfds is the highest-numbered file descriptor in any of the three sets, plus 1.

       timeout is an upper bound on the amount of time elapsed before select() returns. It may  be  zero,
       causing select() to return immediately. (This is useful for polling.) If timeout is NULL (no time-
       out), select() can block indefinitely.

       sigmask is a pointer to a signal mask (see sigprocmask(2)); if it  is  not  NULL,  then  pselect()
       first  replaces  the  current signal mask by the one pointed to by sigmask, then does the ‘select’
       function, and then restores the original signal mask.

       Other than the difference in the precision of the timeout argument, the following pselect() call:

           ready = pselect(nfds, &readfds, &writefds, &exceptfds,
                           timeout, &sigmask);

       is equivalent to atomically executing the following calls:

           sigset_t origmask;

           sigprocmask(SIG_SETMASK, &sigmask, &origmask);
           ready = select(nfds, &readfds, &writefds, &exceptfds, timeout);
           sigprocmask(SIG_SETMASK, &origmask, NULL);

       The reason that pselect() is needed is that if one wants to wait for either a signal or for a file
       descriptor  to  become  ready, then an atomic test is needed to prevent race conditions.  (Suppose
       the signal handler sets a global flag and returns. Then a test of this global flag followed  by  a
       call of select() could hang indefinitely if the signal arrived just after the test but just before
       the call.  By contrast, pselect() allows one to first block signals, handle the signals that  have
       come in, then call pselect() with the desired sigmask, avoiding the race.)

   The timeout
       The time structures involved are defined in <sys/time.h> and look like

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

       and

         struct timespec {
             long    tv_sec;         /* seconds */
             long    tv_nsec;        /* nanoseconds */
         };

       (However, see below on the POSIX.1-2001 versions.)

       Some  code  calls  select()  with all three sets empty, n zero, and a non-NULL timeout as a fairly
       portable way to sleep with subsecond precision.

       On Linux, select() modifies timeout to reflect the amount of time not slept; most other  implemen-
       tations  do not do this.  (POSIX.1-2001 permits either behaviour.)  This causes problems both when
       Linux code which reads timeout is ported to other operating systems, and when code  is  ported  to
       Linux  that  reuses  a  struct timeval for multiple select()s in a loop without reinitializing it.
       Consider timeout to be undefined after select() returns.

RETURN VALUE
       On success, select() and pselect() return the number of file descriptors contained  in  the  three
       returned  descriptor  sets  (that  is, the total number of bits that are set in readfds, writefds,
       exceptfds) 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.

ERRORS
       EBADF  An invalid file descriptor was given in one of the sets.  (Perhaps a file  descriptor  that
              was already closed, or one on which an error has occurred.)

       EINTR  A signal was caught.

       EINVAL nfds is negative or the value contained within timeout is invalid.

       ENOMEM unable to allocate memory for internal tables.

 

服务器程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/time.h>

/* According to POSIX.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>


#define MAXLINE 1024
#define SERV_PORT 5000
#define LISTENQ 100 

void err_quit(char * msg)
{
        printf("err %s", msg);
        exit(0);
}

int main(int argc, char **argv)
{
        int i, maxi, maxfd, listenfd, connfd, sockfd;
        int nready, client[FD_SETSIZE];
        ssize_t n;
        fd_set rset, allset;
        char buf[MAXLINE];
        socklen_t clilen;
        struct sockaddr_in cliaddr, servaddr;

        listenfd = socket(AF_INET, SOCK_STREAM, 0);

        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family      = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port        = htons(SERV_PORT);

        bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));

        listen(listenfd, LISTENQ);

        maxfd = listenfd;                       /* initialize */
        maxi = -1;                                      /* index into client[] array */
        for (i = 0; i < FD_SETSIZE; i++)
                client[i] = -1;                 /* -1 indicates available entry */
        FD_ZERO(&allset);
        FD_SET(listenfd, &allset);
        /* end fig01 */

        /* include fig02 */
        for ( ; ; ) {
                rset = allset;          /* structure assignment */
                nready = select(maxfd+1, &rset, NULL, NULL, NULL);

                if (FD_ISSET(listenfd, &rset)) {        /* new client connection */
                        clilen = sizeof(cliaddr);
                        connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen);
#ifdef  NOTDEF
                        printf("new client: %s, port %d\n",
                                        inet_ntop(AF_INET, &cliaddr.sin_addr, 4, NULL),
                                        ntohs(cliaddr.sin_port));
#endif

                        for (i = 0; i < FD_SETSIZE; i++)
                                if (client[i] < 0) {
                                        client[i] = connfd;     /* save descriptor */
                                        break;
                                }
                        if (i == FD_SETSIZE)
                                err_quit("too many clients");

                        FD_SET(connfd, &allset);        /* add new descriptor to set */
                        if (connfd > maxfd)
                                maxfd = connfd;                 /* for select */
                        if (i > maxi)
                                maxi = i;                               /* max index in client[] array */

                        if (--nready <= 0)
                                continue;                               /* no more readable descriptors */
                }

                for (i = 0; i <= maxi; i++) {   /* check all clients for data */
                        if ( (sockfd = client[i]) < 0)
                                continue;
                        if (FD_ISSET(sockfd, &rset)) {
                                if ( (n = read(sockfd, buf, MAXLINE)) == 0) {
                                        /*4connection closed by client */
                                        close(sockfd);
                                        FD_CLR(sockfd, &allset);
                                        client[i] = -1;
                                } else
                                        write(sockfd, buf, n);

                                if (--nready <= 0)
                                        break;                          /* no more readable descriptors */
                        }
                }
        }
}

关系到套接字列表的操作都需要使用循环,在轮询的时候,需要遍历一次,再新的一轮开始时,将列表加入队列又需要遍历一次.也就是说,Select在工作一次时,需要至少遍历2次列表,这是它效率较低的原因之一.在大规模的网络连接方面,还是推荐使用IOCP或EPOLL模型.但是Select模型可以使用在诸如对战类游戏上,比如类似星际这种,因为它小巧易于实现,而且对战类游戏的网络连接量并不大.

 客户端程序同 http://blog.csdn.net/pngynghay/article/details/8169455中的客户端程序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值