分析多路IO复用的原理,实现SELECT的案例
多路I/O复用是现代网络编程中非常重要的技术,允许一个单独的线程或进程同时监视多个文件描述符(通常是套接字),从而能够在这些描述符之一准备好进行I/O操作时进行相应的处理。多路I/O复用的主要系统调用有select
、poll
和epoll
(在Linux上)。
以下是一个关于select
的简单案例,展示了它的基本使用方法。
原理
select
系统调用允许程序监视多个文件描述符,等待其中的一个或多个变为"就绪"状态(可读、可写或异常)。调用select
时,程序可以指定三个文件描述符集(可读、可写和异常),以及一个超时时间。select
会阻塞,直到:
- 一个或多个文件描述符就绪。
- 超时发生。
- 被信号中断。
使用步骤
- 初始化文件描述符集:使用
FD_ZERO
、FD_SET
等宏来初始化和设置文件描述符集。 - 调用
select
:传递文件描述符集和超时时间。 - 处理就绪文件描述符:检查哪些文件描述符就绪,并进行相应的处理。
示例代码:
以下是一个用 fd_set
和 select
的示例程序,演示了如何将监听文件描述符加入集合,并在 while
循环中判断哪些文件描述符已就绪。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
int main() {
int ret;
char buff[1024];
fd_set set, rset;
int maxfd;
struct timeval tv;
// 初始化文件描述符集合
FD_ZERO(&set);
FD_SET(STDIN_FILENO, &set); // 将标准输入文件描述符加入集合
maxfd = STDIN_FILENO;
// 设置超时时间
tv.tv_sec = 2;
tv.tv_usec = 0;
while (1) {
rset = set; // 每次循环都要重置rset,因为select会修改它
printf("Waiting for input...\n");
ret = select(maxfd + 1, &rset, NULL, NULL, &tv);
if (ret < 0) {
perror("select");
break;
}
if (ret == 0) {
printf("Timeout occurred! No data after 2 seconds.\n");
tv.tv_sec = 2; // 重新设置超时时间
tv.tv_usec = 0;
continue;
}
// 检查哪些文件描述符已就绪
for (int i = 0; i <= maxfd; i++) {
if (FD_ISSET(i, &rset)) {
ret = read(i, buff, sizeof(buff) - 1);
if (ret > 0) {
buff[ret] = '\0'; // 确保字符串以null结尾
printf("Received input: %s\n", buff);
} else if (ret == 0) {
// 处理EOF
printf("EOF encountered on file descriptor %d\n", i);
} else {
perror("read");
}
}
}
}
return 0;
}
代码解释
-
初始化文件描述符集合:
FD_ZERO(&set); FD_SET(STDIN_FILENO, &set); // 将标准输入文件描述符加入集合 maxfd = STDIN_FILENO;
-
设置超时时间:
tv.tv_sec = 2; tv.tv_usec = 0;
-
进入主循环:
while (1) { rset = set; // 每次循环都要重置rset,因为select会修改它 printf("Waiting for input...\n"); ret = select(maxfd + 1, &rset, NULL, NULL, &tv);
-
处理
select
返回值:if (ret < 0) { perror("select"); break; } if (ret == 0) { printf("Timeout occurred! No data after 2 seconds.\n"); tv.tv_sec = 2; // 重新设置超时时间 tv.tv_usec = 0; continue; }
-
检查哪些文件描述符已就绪:
for (int i = 0; i <= maxfd; i++) { if (FD_ISSET(i, &rset)) { ret = read(i, buff, sizeof(buff) - 1); if (ret > 0) { buff[ret] = '\0'; // 确保字符串以null结尾 printf("Received input: %s\n", buff); } else if (ret == 0) { // 处理EOF printf("EOF encountered on file descriptor %d\n", i); } else { perror("read"); } } }
通过这个示例,你可以看到如何使用 select
系统调用来监视标准输入,并在输入数据可用时读取数据并打印。每次 select
调用后,程序会检查哪些文件描述符已就绪,并对其进行处理。