【Linux系统编程】IO多路复用之select
00. 目录
文章目录
01. 概述
I/O 多路复用技术是为了解决进程或线程阻塞到某个 I/O 系统调用而出现的技术,使进程不阻塞于某个特定的 I/O 系统调用。
select(),poll(),epoll()都是I/O多路复用的机制。I/O多路复用通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪,就是这个文件描述符进行读写操作之前),能够通知程序进行相应的读写操作。但select(),poll(),epoll()本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。
与多线程和多进程相比,I/O 多路复用的最大优势是系统开销小,系统不需要建立新的进程或者线程,也不必维护这些线程和进程。
02. select函数
#include <sys/select.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 功能: 监视并等待多个文件描述符的属性变化(可读、可写或错误异常)。 select()函数监视的文件描述符分 3 类,分别是writefds、readfds、和 exceptfds。 调用后 select() 函数会阻塞,直到有描述符就绪(有数据可读、可写、或者有错误异常), 或者超时( timeout 指定等待时间),函数才返回。 当 select()函数返回后,可以通过遍历 fdset,来找到就绪的描述符。 参数: nfds: 要监视的文件描述符的范围,一般取监视的描述符数的最大值+1,如这里写 10, 这样的话,描述符 0,1, 2 …… 9 都会被监视,在 Linux 上最大值一般为1024。 readfd: 监视的可读描述符集合,只要有文件描述符即将进行读操作,这个文件描述符就存储到这。 writefds: 监视的可写描述符集合。 exceptfds: 监视的错误异常描述符集合。 timeout: 超时时间,它告知内核等待所指定描述字中的任何一个就绪可花多少时间。 其 timeval 结构用于指定这段时间的秒数和微秒数。 返回值: 成功:就绪描述符的数目,超时返回 0, 出错:-1 timeout参数有三种可能: 1)永远等待下去:仅在有一个描述字准备好 I/O 时才返回。为此,把该参数设置为空指针 NULL。 2)等待固定时间:在指定的固定时间( timeval 结构中指定的秒数和微秒数)内, 在有一个描述字准备好 I/O 时返回,如果时间到了,就算没有文件描述符发生变化, 这个函数会返回 0。 3)根本不等待(不阻塞):检查描述字后立即返回,这称为轮询。 为此,struct timeval变量的时间值指定为 0 秒 0 微秒, 文件描述符属性无变化返回 0,有变化返回准备好的描述符数量。 struct timeval { long tv_sec; /* seconds */ long tv_usec; /* microseconds */ }; 中间的三个参数 readfds、writefds 和 exceptfds 指定我们要让内核监测读、写和异常条件的描述字。 如果不需要使用某一个的条件,就可以把它设为空指针( NULL )。 集合fd_set 中存放的是文件描述符,可通过以下四个宏进行设置: //将一个给定的文件描述符从集合中删除 void FD_CLR(int fd, fd_set *set); // 检查集合中指定的文件描述符是否可以读写 int FD_ISSET(int fd, fd_set *set); //将一个给定的文件描述符加入集合之中 void FD_SET(int fd, fd_set *set); //清空集合 void FD_ZERO(fd_set *set);
03. select程序示例
我们写这么一个例子,同时循环读取标准输入的内容,读取有名管道的内容,默认的情况下,标准输入没有内容,read()时会阻塞,同样的,有名管道如果没有内容,read()也会阻塞,我们如何实现循环读取这两者的内容呢?最简单的方法是,开两个线程,一个线程循环读标准输入的内容,一个线程循环读有名管道的内容。而在这里,我们通过 select() 函数实现这个功能:
#include <sys/select.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char *argv[]) { fd_set rfds; struct timeval tv; int ret; int fd; ret = mkfifo("test_fifo", 0666); // 创建有名管道 if(ret != 0){ perror("mkfifo:"); } fd = open("test_fifo", O_RDWR); // 读写方式打开管道 if(fd < 0){ perror("open fifo"); return -1; } ret = 0; while(1){ // 这部分内容,要放在while(1)里面 FD_ZERO(&rfds); // 清空 FD_SET(0, &rfds); // 标准输入描述符 0 加入集合 FD_SET(fd, &rfds); // 有名管道描述符 fd 加入集合 // 超时设置 tv.tv_sec = 1; tv.tv_usec = 0; // 监视并等待多个文件(标准输入,有名管道)描述符的属性变化(是否可读) // 没有属性变化,这个函数会阻塞,直到有变化才往下执行,这里没有设置超时 // FD_SETSIZE 为 <sys/select.h> 的宏定义,值为 1024 ret = select(FD_SETSIZE, &rfds, NULL, NULL, NULL); //ret = select(FD_SETSIZE, &rfds, NULL, NULL, &tv); if(ret == -1){ // 出错 perror("select()"); }else if(ret > 0){ // 准备就绪的文件描述符 char buf[100] = {0}; if( FD_ISSET(0, &rfds) ){ // 标准输入 read(0, buf, sizeof(buf)); printf("stdin buf = %s\n", buf); }else if( FD_ISSET(fd, &rfds) ){ // 有名管道 read(fd, buf, sizeof(buf)); printf("fifo buf = %s\n", buf); } }else if(0 == ret){ // 超时 printf("time out\n"); } } return 0; }
下面为上面例子的往有名管道写内容的示例代码:
#include <sys/select.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> int main(int argc, char *argv[]) { //select_demo(8); fd_set rfds; struct timeval tv; int ret; int fd; ret = mkfifo("test_fifo", 0666); // 创建有名管道 if(ret != 0){ perror("mkfifo:"); } fd = open("test_fifo", O_RDWR); // 读写方式打开管道 if(fd < 0){ perror("open fifo"); return -1; } while(1){ char *str = "this is for test"; write(fd, str, strlen(str)); // 往管道里写内容 printf("after write to fifo\n"); sleep(5); } return 0; }
04. select优缺点
select优点:
select()目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。
select的缺点:
1)每次调用 select(),都需要把 fd 集合从用户态拷贝到内核态,这个开销在 fd 很多时会很大,同时每次调用 select() 都需要在内核遍历传递进来的所有 fd,这个开销在 fd 很多时也很大。
2)单个进程能够监视的文件描述符的数量存在最大限制,在 Linux 上一般为 1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低。
05. 附录
- 点赞 1
- 收藏
- 分享
- 文章举报
- 【Linux系统编程】IO多路复用之epoll
- Linux网络通信编程(套接字模型TCP\UDP与IO多路复用模型select\poll\epoll)
- Linux网络通信编程(套接字模型TCP\UDP与IO多路复用模型select\poll\epoll)
- linux基础编程:IO模型:阻塞/非阻塞/IO复用 同步/异步 Select/Epoll/AIO
- linux socket编程之多路复用select的例子
- Linux下C语言编程入门-18关于多路复用IO
- linux下IO复用编程select 和 epoll
- 函数模型Linux系统文件I/O编程(三)---I/O多路复用
- Linux应用编程基础之多路复用:select和poll的简单使用示例
- Linux下的socket编程实践(七) I/O多路复用技术之select模型
- TCP IO复用 select并发服务端 Linux socket编程入门(3)
- 【Linux系统编程】IO多路复用之poll
- Linux网络编程基础-06_IO模型和多路复用模型
- linux基础编程:IO模型:阻塞/非阻塞/IO复用 同步/异步 Select/Epoll/AIO
- 【Linux编程】IO复用之select详解
- Linux-(C)IO多路复用之select学习(转载)
- Linux IO多路复用之epoll网络编程
- Linux下多路复用IO接口 epoll select poll 的区别
- [nginx] Linux下多路复用IO接口 epoll select poll 的区别
- Linux下多路复用IO接口epoll/select/poll的区别