I/O多路复用之select

I/O多路复用之select

select函数

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

参数

其中第一个参数n:表示文件描述符+1

fd_set是一个结构体,源代码如下:

typedef struct
  {
    /* XPG4.2 requires this member name.  Otherwise avoid the name
       from the global namespace.  */
#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;

这个结构体是一个数组,更严格来说是一个“位图”,用每一个比特位来标识对应要监视的文件描述符。

那么,select函数中,中间三个参数分别表示需要检测的读文件描述符集合、写文件描述符集合、异常文件描述符集合。

最后一个参数:timeout,它的类型是一个结构体,该结构体源代码如下:

/* A time value that is accurate to the nearest
   microsecond but also has a range of years.  */
struct timeval
{
#ifdef __USE_TIME_BITS64
  __time64_t tv_sec;		/* Seconds.  */
  __suseconds64_t tv_usec;	/* Microseconds.  */
#else
  __time_t tv_sec;		/* Seconds.  */
  __suseconds_t tv_usec;	/* Microseconds.  */
#endif
};

这个结构体用于描述一段时间长度,它在select函数中的作用是超时时间。

关于它的取值:

NULL:表示阻塞,文件描述符对应事件就绪之前一直处于阻塞状态。

0:表示非阻塞,仅检测文件描述符对应事件是否就绪,然后直接返回,返回值(即select函数返回值)在后面介绍。

特定值:在这个特定值之前,如果没有事件发生就会一直处于阻塞状态,当等待事件超过这个特定值,就会变成非阻塞。

返回值

返回值有三种情况:

1、> 0:表示文件描述符事件就绪的个数。

2、== 0:表示文件描述符事件就绪前已经超过timeout时间了,直接返回。

3、< 0:表示有错误发生,错误码被设置。

错误码可能为:

EBADF:文件描述符无效或者该文件描述符已关闭。

EINTR:此调用被信号所中断。

EINVAL:参数n为负值。

ENOMEN:核心内存不足。

补充

另外,需要注意的点:

1、select函数中间三个参数在每次使用select函数前都需要置0,再逐个设置文件描述符到对应事件集合中。

关于一些fd_set的接口:

void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd 的位。

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

void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位。

void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位。

2、timeout默认为0,如果不想设置为非阻塞,那么就自己指定值,当然,哪怕是想设置为非阻塞也最好还是写上。

3、第一个参数n是最大文件描述符+1,不要忘了+1(这一条准确来说是注意事项)。

示例

#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <sys/select.h>

int main()
{
    // struct timeval *timeout = nullptr;
    fd_set read_fd;
    FD_ZERO(&read_fd);
    FD_SET(0, &read_fd);
    while (true)
    {
        printf(">");
        fflush(stdout);
        int res = select(1, &read_fd, nullptr, nullptr, nullptr); // 设置为阻塞
        if (res < 0)
        {
            perror("select");
            continue;
        }
        if (FD_ISSET(0, &read_fd))
        {
            char buf[1024] = {0};
            int n = read(0, buf, sizeof(buf) - 1);
            buf[n - 1] = 0;
            printf("output:%s\n", buf);
        }
        FD_ZERO(&read_fd);
        FD_SET(0, &read_fd);
    }
    return 0;
}

select的特点

1、最多可监控文件描述符的个数取决于sizeof(fd_set)的值,我这里用虚拟机试了下是128Byte,而文件描述符在fd_set中是以位图结构来存储的,也就是说我这台虚拟机最多可监控128 * 8 = 1024个文件描述符,不同机器可能不一样。

2、在使用select函数之前,还需要一个数据结构来保存要放到fd_set对象中的文件描述符,因为在select函数返回后,需要对数据结构中的文件描述符进行判断,判断该文件描述符事件是否就绪(判断函数:FD_ISSET(),上面有它的简单介绍),还有一个原因就是select函数返回后,没有就绪的文件描述符会被置0,等下次使用需要重新放到fd_set对象中。

select的缺点

  • 每次调用select时,都需要把想要监视的文件描述符添加到fd_set中。
  • 每次调用select时,都需要把fd集合从用户态拷贝到内核态。
  • 每次都需要在内核遍历所传进来的fd。
  • select支持的文件描述符数量太少。
  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值