0%

IO多路复用 select 入门

[TOC]

概述

使用select搭建的多路I/O转接服务器是一种基于非阻塞的服务器:当有客户端连接请求到达时,accept会返回一个文件描述符,该文件描述符会被存储到由select监控的文件描述符表中,每个文件描述符对应的文件都可进行I/O操作,因此select可通过监控表中各个文件描述符,来获取对应的客户端I/O状态。若每路程序中都没有数据到达,线程将阻塞在select上;否则select将已就绪客户端程序的数量返回到服务器。

不阻塞是指不阻塞在 accept 系统调用上, 也不阻塞在 read 系统调用上(因为数据已经准备好了), 而是阻塞在 select 系统调用上。 开始时, 系统阻塞在 select系统调用中。一个数据包过来,如果是请求建立连接的, 那么走 accept 系统调用,得到 connFd, 并注册到 select 监控的 文件描述符列表中。如果是建立好的连接发来的数据包,那么遍历文件描述符集,处理已就绪的文件描述符。

DKnEQ0.png

select 系统调用

1
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  • nfds用来设置select监控的文件描述符的范围,需设置为文件描述符最大值加1
  • 参数readfds、writefds、exceptfds分别用于表示可读取数据的文件描述符集、可写入数据的文件描述符集以及发生异常的文件描述符集,它们都为传入传出参数,其参数类型fd_set实质为长整型,这些集合中的每一位都对应一个文件描述符的状态,若集合参数被设置为NULL,表示不关心文件的对应状态。
  • select()函数的返回值有3种:若返回值大于0,表示已就绪文件描述符的数量,此种情况下某些文件可读写或有错误信息;若返回值等于0,表示等待超时,没有可读写或错误的文件;若返回值-1,表示出错返回,同时errno将被设置。

参数timeout用于设置select的阻塞时长,其取值有如下几种情况:

● 若timeval=NULL,表示永远等待;

● 若timeval>0,表示等待固定时长;

● 若timeval=0,select将在检查过指定文件描述符后立即返回(轮询)。

文件描述符集操作函数

函数声明 函数功能
void FD_CLR(int fd,fd_set *set); 将集合中的文件描述符fd清除(将fd位置为0)
int FD_ISSET(int fd,fd_set *set); 测试集合中文件描述符fd是否存在于集合中,若存在则返回非0
void FD_SET(int fd,fd_set *set); 将文件描述符fd添加到集合中(将fd位置为1)
void FD_ZERO(fd_set *set); 清除集合中所有的文件描述符(所有位置0)

select 模式存在的问题

超大数据包或超长的处理时间导致连接饿死

因为我们知道,当建立好的连接连接发来数据包时,服务会去处理这个数据包,而不是阻塞在系统调用上,那么如果这个数据包的处理时间非常长,比如说1个小时,那么整个服务这一个小时内既不能处理新建连接的数据包,也不能处理已建立的连接发来的数据包。对于不能处理新建连接的数据包的情况,如果新建连接的数量超过全连接队列的长度,就会导致新建连接直接拒绝。

数量有限

select可监控的文件数量是有限的,该数量受到两个因素的限制。第一个因素是进程可打开的文件数量,第二个因素是select中的集合fd_set的容量。进程可打开文件的上限可通过ulimit –n命令或setrlimit函数设置,但系统所能打开的最大文件数也是有限的;select中集合fd_set的容量由宏FD_SETSIZE(定义在linux/posix_types.h中)指定,一般为1024,但即便通过重新编译内核的方式修改FD_SETSIZE,也不一定能提升select服务器的性能,因为若select一次监测的进程过多,单轮询便要耗费大量的时间。

实现

go-science/example/cpp/io/select_s.c

参考

select

还没有看