I/O复用--select()

select

man手册:select(2) - Linux manual page (man7.org)

参考:https://www.cnblogs.com/alantu2018/p/8612722.html

函数原型

#include <sys/select.h>

int select(int nfds, fd_set *restrict readfds,
           fd_set *restrict writefds, 
           fd_set *restrict exceptfds,
           struct timeval *restrict 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);

第一个参数:nfds必须是当前最大文件描述符+1

  • 函数限制:

select() can monitor only file descriptors numbers that are less than FD_SETSIZE (1024)—an unreasonably low limit for many modern applications—and this limitation will not change. All modern applications should instead use poll(2) or epoll(7), which do not suffer this limitation.

select()可以监听的文件描述符小于1024,如果需要监听更多的文件描述符,就用pollepoll

以下的代码来自centos7

  • fd_set结构体,下面是类型定义
/* The fd_set member is required to be an array of longs.  */
typedef long int __fd_mask;
/* Sometimes the fd_set member is assumed to have this type.  */
typedef __fd_mask fd_mask;

/* Maximum number of file descriptors in `fd_set'.  */
#define	FD_SETSIZE		__FD_SETSIZE
/* Number of descriptors that can fit in an `fd_set'.  */
#define __FD_SETSIZE		1024

/* Number of bits per word of `fd_set' (some code assumes this is 32).  */
# define NFDBITS		__NFDBITS
/* It's easier to assume 8-bit bytes than to get CHAR_BIT.  */
#define __NFDBITS	(8 * (int) sizeof (__fd_mask))

/* fd_set for select and pselect.  */
typedef struct
  {
#ifdef __USE_XOPEN
    __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
#else
    __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->__fds_bits)
#endif
  } fd_set;
  • 简化版本:将以上的类型定义替换成最原始的数据类型,可以看出fd_set就是一个long int 数组
/* fd_set for select and pselect.  */
typedef struct
{
	long int fds_bits[1024 / (8 * (int) sizeof (long int))];
} fd_set;

// sizeof(long int) = 8,对于centos7,也即是上面的数组大小=1024/8*8=16
  • 对ffd_set操作的宏。
/* It's easier to assume 8-bit bytes than to get CHAR_BIT.  */
#define __NFDBITS	(8 * (int) sizeof (__fd_mask))
// __NFDBITS = 8 * 8 = 64
#define	__FD_ELT(d)	((d) / __NFDBITS)
// __FD_ELT(d) = (d / 64)
#define	__FD_MASK(d)	((__fd_mask) 1 << ((d) % __NFDBITS))
// __FD_MASK(d) = ((long int)1<<((d)%64))

/* Access macros for `fd_set'.  */
#define	FD_SET(fd, fdsetp)	__FD_SET (fd, fdsetp)
#define	FD_CLR(fd, fdsetp)	__FD_CLR (fd, fdsetp)
#define	FD_ISSET(fd, fdsetp)	__FD_ISSET (fd, fdsetp)
#define	FD_ZERO(fdsetp)		__FD_ZERO (fdsetp)

/* We don't use `memset' because this would require a prototype and
   the array isn't too big.  */
# define __FD_ZERO(set)  \
  do {									      \
    unsigned int __i;							      \
    fd_set *__arr = (set);						      \
    for (__i = 0; __i < sizeof (fd_set) / sizeof (__fd_mask); ++__i)	      \
      __FDS_BITS (__arr)[__i] = 0;					      \
  } while (0)

#endif	/* GNU CC */

// __FDS_BITS (set)就是取fd_set的数组
// FD_SET(d, set)的操作为: [d/64] |= ((long int)1<<((d)%64))
// FD_CLR(d, set)的操作为: [d/64] &= ~((long int)1<<((d)%64))
// FD_ISSET(d, set)的操作为: [d/64] & ((long int)1<<((d)%64)) != 0
#define __FD_SET(d, set) \
  ((void) (__FDS_BITS (set)[__FD_ELT (d)] |= __FD_MASK (d)))
#define __FD_CLR(d, set) \
  ((void) (__FDS_BITS (set)[__FD_ELT (d)] &= ~__FD_MASK (d)))
#define __FD_ISSET(d, set) \
  ((__FDS_BITS (set)[__FD_ELT (d)] & __FD_MASK (d)) != 0)

由函数限制可知,select监听的文件描述符的数字大小应该小于1024。则文件描述符d可以用10个bit表示。由上面的宏知道,这个10个bit的前4bit确定对应于数组的哪个数,而后6个bit用于确定对数组中的某个long int的哪一位进行操作。前4bit对应[d/64],后6bit对应(d%64)

  • timeval结构体
struct timeval {
    time_t      tv_sec;         /* seconds */
    suseconds_t tv_usec;        /* microseconds */
};

函数使用

第一 .struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(filedescriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然 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 返回值:
负值:select错误
正值:某些文件可读写或出错
0:等待超时,没有可读写或错误的文件

底层原理

https://my.oschina.net/fileoptions/blog/911091

使用例子

传入accept后得到的connfd,持续监听connfd是否有数据进入。

可见使用select函数是比较麻烦的,因为要传入最大描述符+1作为第一个参数,并且select之前的都要重新FD_SET,对于检测都要使用FD_ISSET.

void mon_connfd(int connfd) {
    fd_set read_fds;
    fd_set exception_fds;
    FD_ZERO(&read_fds);
    FD_ZERO(&exception_fds);

    char buf[1024];

    while (1)
    {
        memset(buf, '\0', sizeof(buf));
        /**
         * 每次调用select之前都要FD_SET,
         * 因为事件发生后,文件描述符集合将被内核修改
        */
        FD_SET(connfd, &read_fds);
        FD_SET(connfd, &exception_fds);
        int ret = select(connfd+1, &read_fds, NULL, &exception_fds, NULL);
        if (ret < 0) {
            printf("select() error!\n");
            break;
        }
        // 对于可读事件,用recv函数读取数据
        if(FD_ISSET(connfd, &read_fds)) {
            ret = recv(connfd, buf, sizeof(buf)-1, 0);
            if(ret <= 0) {
                break;
            }
            printf("get %d bytes of normal data : %s\n", ret, buf);
        }
        // 对于可读事件,用带MSG_OOB标记的recv函数读取数据
        else if(FD_ISSET(connfd, &exception_fds)) {
            ret = recv(connfd, buf, sizeof(buf)-1, MSG_OOB);
            if(ret <= 0) {
                break;
            }
            printf("get %d bytes of oob data : %s\n", ret, buf);
        }
    }
    close(connfd);
}
posted @ 2023-01-09 16:25  DavidJIAN  阅读(10)  评论(0)    收藏  举报