微信公众号:CPP进阶之旅
如果觉得这篇文章对您有帮助,欢迎关注 CPP进阶之旅 学习更多技术干货
I/O多路复用之select
1、select函数作用
select,poll,epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,而异步I/O则无需自己负责进行读写。
select函数的作用是在一段指定时间内,检测一组用户感兴趣的文件描述符中某个或某几个是否有“事件”,这里的“事件”一般分为如下三类:
- 可读事件,一般意味着可以调用recv或read 函数从该 socket 上读取数据;如果该 socket是侦听socket(即调用了 bind 函数绑定过 ip 地址和端口号,并调用了 listen 启动侦听的socket)可读意味着此时可以有新的客户端连接到来,此时可调用 accept 函数接受新连接。
- 可写事件,一般意味着此时调用 send 或 write 函数发送数据。
- 异常事件,某个文件描述符出现异常。
2、select详细介绍
-
函数原型
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
-
参数说明
nfds : 表示指定被监听的文件描述符的总数,这个参数的值通常设置成所有需要使用select函数监听的fd中最大fd值加 1,因为文件描述符是从0开始计数的。
readfds : 表示需要监听可读事件的 fd 集合。
writefds : 表示需要监听可写事件的 fd 集合。
exceptfds : 表示需要监听异常事件的 fd 集合。
timeout : 表示超时时间,即在这个参数设定的时间内检测这些 fd 的事件,超过这个时间后 select 函数将立即返回。如果timeout设置为null,则select将一直阻塞,直到某个文件描述符就绪。 -
返回值
执行成功则返回文件描述符已经就绪(可读、可写、异常)的总数,这时我们可以通过遍历的方式获取到已经就绪的文件描述符。如果在超时时间内没有任何文件描述符就绪则返回0。当有错误发生时则返回-1,错误原因存于errno。如果在select等待期间,程序接收到信号,则select返回-1,并设置errno为EINTR。 -
其他说明
程序在调用select时通过readfds、writefds和exceptfds这三个参数传入我们感兴趣的文件描述符。select返回时,内核修改他们来通知哪些文件描述符已经就绪。
这三个参数的类型都是fd_set结构体,而fd_set结构仅包含一个整形数组,该数组的每个元素的每一位标记一个文件描述符,该数组的大小由FD_SETSIZE指定,linux下一般为1024。
下面是对readfds、writefds和exceptfds这三种文件描述符集合操作相关的宏:
FD_CLR(inr fd,fd_set* set);//用来清除fd_set中相关fd 的位
FD_ISSET(int fd,fd_set set);//用来测试fd_set中相关fd 的位是否为真 FD_SET(int fd,fd_setset);//用来设置fd_set中相关fd的位
FD_ZERO(fd_set *set);//用来清除fd_set的全部位
3、select注意事项
- select函数调用会修改 readfds、writefds 和 exceptfds 这三个集合中的内容,如果下次调用select时需要复用这个变量,需要在下次调用 select 前先使用 FD_ZERO 将集合清零,然后调用 FD_SET 将需要检测事件的fd再次添加进去。
- 在调用select函数之后我们需要用FD_ISSET检测文件描述符是否有事件。
- select 函数也会修改 timeval 结构体的值,这也要求我们如果像复用这个变量,必须给 timeval 变量重新设置值。
4、select函数缺点
- 单个进程可监视的fd数量被限制,即能监听端口的大小有限。
- 对文件描述符进行扫描时是线性扫描,即采用轮询的方法,效率较低。
- 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大。
5、重要说明
欢迎大家关注我的个人微信公众号,查看专业的客户端/服务端开发知识、笔试面试题目、程序员职场经验与心得分享。