一、select函数的功能
1.1select函数原型解析
select是系统提供的一个用来实现多路复用的输入/输出模型:
1、select系统调用可以用来让我们监视多个文件描述符的状态变化
2、程序会一直停在select这里进行等待,直到一个或多个被监视的文件描述符发生了状态改变。
select函数原型:
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数解析:
1、nfds 是需要监视的最大的文件描述符值+1。
2、rdset,wrset,exset 后三个参数分别对应需要检测的可读文件描述符集合、可写文件描述符集合、异常文件描述符集合。
3、参数 timeout 为 timeval的结构,用于设置 select()的等待时间。
timeval:
timeval结构用于描述一段时间的长度,如果在这个时间内,监视的描述符没有事件发生则函数返回,返回值为 0。
1.2timeout的作用
1、NULL:表示 select()没有 timeout,也就是select 将一直被阻塞,直到某一个文件描述符发生了事件即发生了状态改变;
2、0:仅检测目标描述符集合的状态,然后立即进行返回,不等待外部事件的发生。
3、特定的时间值:如果在设定的指定时间断内没有事件发生,select将进行超时返回。
1.3fd_set
fd_set就是一个整数数组,严格来讲,其是一个位图结构。通过位图中不同的位对应的数来表示要监视的文件描述符,通常用于辅助配合selsect进行使用。
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中全部的对应fd位
1.4select函数的返回值
1、执行成功返回文件描述词状态已经改变的个数。
2、返回 0 代表在描述词状态在timeout 时间之前没有改变,没有返回。
3、当有错误发生时则返回-1,错误码errno被设置,此时readfds,writefds, exceptfds 和 timeout四个参数的值变为不可预测。
select函数错误值:
EBADF表示文件描述词为无效的或该文件已关闭。
EINTR表示此调用被信号所中断。
EINVAL表示参数n为负值。
ENOMEM表示核心内存不足。
二、socket就绪
2.1读就绪
1、socket内核中, 接收缓冲区里的字节数,大于等于低水位标记 SO_RCVLOWAT。此时可以无阻塞的读该文件描述符中的数据, 并且返回值大于 0。
2、socket TCP 通信中, 如果对端关闭连接, 此时对该socket读,则返回值为0,表示另一方关闭。
3、监听的 socket 上有新的连接请求。
4、监听的 socket 上有未处理的错误。
2.2写就绪
1、socket内核中, 发送缓冲区里的字节数,大于等于低水位标记 SO_RCVLOWAT。此时可以无阻塞的向该文件描述符中写入数据, 并且返回值大于 0。
2、socket 的写操作被关闭(close /shutdown),如果对一个写操作被关闭的 socket 进行写操作, 会触发 SIGPIPE 信号;
3、socket 使用非阻塞 connect 连接成功或失败之后;
4、socket 上有未被读取的错误;
三、select的特点及不足
1、可监控的文件描述符个数取决于 sizeof(fd_set)的值,假设sizeof(fd_set)=512,每 bit 表示一个文件描述符,则该服务器上支持的最大文件描述 符是 512*8=4096。
2、将 fd 加入 select 监控集fd_set的同时,还需要再使用一个数据结构 array 也就是一个整形数组来保存放到 select 监控集中的fd。
3、当select返回后,之前创建的 array 作为源数据可以和 fd_set 进行 FD_ISSET 判断。
4、select 返回后会把以前加入的但并无事件发生的文件描述符 fd 清空,所以每次开始 select 前都要重新从 array 依次取得 fd 逐一加入,同时扫描array时取得fd最大值用于select的第一个参数。
slect的不足:
每次调用 select, 都要手动去设置 fd 集合, 从接口使用角度来说非常不便。
每次调用 select,都要把 fd 集合从用户态拷贝到内核态,这个开销在 fd 很 多时会很大。
每次调用 select 都需要在内核遍历传递进来的所有 fd,当fd过多时,开销很大。
select 支持的文件描述符数量太小。