select模型
- select系统调用是用来让我们的程序监视多个文件描述符的状态变化的;
- 程序会停在select这里,select循环不断地对所监控的描述符进行就绪判断,如果没有描述符就绪,就继续循环等待。
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
nfds:指定待测试的描述符个数
readfds,writefds,exceptfds:指定了我们让内核测试读、写和异常条件的描述字
timeout
- NULL:则表示select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了事件;
- 0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。
- 特定的时间值:如果在指定的时间段里没有事件发生,select将超时返回。
返回值:失败-1 超时0 成功>0
select原理:对fd_set的理解
fd_set:为一个存放文件描述符的信息的结构体,可以通过下面的宏进行设置
void FD_ZERO(fd_set *fdset);
//清空集合
void FD_SET(int fd, fd_set *fdset);
//将一个给定的文件描述符加入集合之中
void FD_CLR(int fd, fd_set *fdset);
//将一个给定的文件描述符从集合中删除
int FD_ISSET(int fd, fd_set *fdset);
// 检查集合中指定的文件描述符是否可以读写
建立监控集合,对描述符指定的事件(可读、可写、异常),当监控集合中的某个描述就绪了,则返回,并且告诉我们有几个描述符就绪,将监控描述符中没有就绪的描述符从集合中移除。
select对描述符进行监控,分了三种监控,分别是可读、可写、异常事件,如果想要对描述符进行监控,则将这个描述符添加到对应集合中。这个集合实际上一个位图,位图的默认大小是1024(不同的主机fd_set的默认大小不确定,总之,fd_set的大小是有限的,可改变的。),添加指定的描述符到集合,就是置描述符数据所对应的比特位为1。
nfds:最大的描述符,select将集合拷贝到内核,然后轮循(循环判断一遍有没有就绪的描述符,没有就过一会儿再来轮循)监控。只轮循到nfds+1的位置处。
select在服务器程序中的应用
多路转接:对大量描述符进行事件阻塞监控,当描述符状态改变(可读、可写、异常),则返回。select创建了三个描述符集合分别监控可读事件、可写事件、异常事件。
将集合中的数据拷贝到内核,进行阻塞监控(间隔时间,轮循遍历,判断是否有描述符就绪)
如果遍历没有描述符就绪,判断是否超时,超时则会返回0。如果有描述符就绪,将集合中没有就绪的无关描述符从集合中移除(集合中剩下的都是就绪描述符)。
读事件就绪
- socket内核中, 接收缓冲区中的字节数, 大于等于低水位标记SO_RCVLOWAT. 此时可以无阻塞的读该文件描述符, 并且返回值大于0;
- socket TCP通信中, 对端关闭连接, 此时对该socket读, 则返回0;
- 监听的socket上有新的连接请求;
- socket上有未处理的错误;
写事件就绪
- socket内核中, 发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小), 大于等于低水位标记
SO_SNDLOWAT, 此时可以无阻塞的写, 并且返回值大于0; - socket的写操作被关闭(close或者shutdown). 对一个写操作被关闭的socket进行写操作, 会触发SIGPIPE信号;
- socket使用非阻塞connect连接成功或失败之后;
- socket上有未读取的错误;
异常事件就绪
- socket上收到带外数据. 关于带外数据, 和TCP紧急模式相关。
select优缺点
Select缺点:
- 能够监控的描述符有最大上限(因为位图最大取决于FD_SIZE)
- 将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd。
一是用于再select 返回后,array作为源数据和fd_set进行FD_ISSET判断。
二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得。 - 因为select每次都需要将集合数据拷贝到内核,并且在内核是轮循遍历实现监控,因此性能随着描述符增多而降低。
Select优点:
- 可跨平台。
- 超时时间控制比较精细,可以控制到微秒。