标准输入上等待输入事件
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
int main() {
fd_set read_fds; // 读文件描述符集合
struct timeval timeout; // 超时时间结构体
int max_fd = 1; // 文件描述符的最大值
int result;
while (1) {
FD_ZERO(&read_fds); // 清空文件描述符集合
FD_SET(STDIN_FILENO, &read_fds); // 将标准输入添加到文件描述符集合
timeout.tv_sec = 5; // 设置超时时间为5秒
timeout.tv_usec = 0;
// 使用 select 函数检查文件描述符集合上是否有可读事件
result = select(max_fd, &read_fds, NULL, NULL, &timeout);
if (result == -1) {
perror("select");
exit(EXIT_FAILURE);
} else if (result == 0) {
// 超时,可以在这里添加相应的处理逻辑
printf("Timeout occurred!\n");
fflush(stdout);
} else {
// 文件描述符上有可读事件发生
if (FD_ISSET(STDIN_FILENO, &read_fds)) {
char buffer[1024];
// 从标准输入读取数据
ssize_t bytesRead = read(STDIN_FILENO, buffer, sizeof(buffer));
if (bytesRead == -1) {
perror("read");
exit(EXIT_FAILURE);
} else if (bytesRead == 0) {
// EOF,表示输入结束
printf("End of file reached. Exiting...\n");
break;
} else {
// 处理读取到的数据,这里简单地将其输出
printf("Read from stdin: %.*s", (int)bytesRead, buffer);
fflush(stdout);
}
}
}
}
return 0;
}
select 的局限性与性能分析
-
fd_set 是位图结构,大小固定
fd_set
在 Linux 下定义为:typedef struct { __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS]; } fd_set; #define __FD_SETSIZE 1024
这意味着单个
fd_set
最多只能同时监控 1024 个文件描述符对于高并发服务器,这个限制非常明显
-
无法重用 fd_set,每次循环都要重建
select
调用会修改传入的fd_set
,使其只保留有事件发生的文件描述符- 所以每次循环都必须重新
FD_ZERO
和FD_SET
,增加了额外的开销
-
用户态与内核态频繁拷贝,性能开销大
select
内部需要从用户态拷贝 fd_set 到内核态,再从内核态拷贝结果回用户态- 对大量文件描述符的程序,拷贝成本不可忽视。
-
轮询文件描述符,O(n) 复杂度
- 内核会遍历 fd_set 中的每个文件描述符,检查是否就绪
- 随着 fd 数量增加,轮询开销线性增长,影响性能
flowchart %% 用户态调用 A[用户态程序] -->|调用 select/poll/epoll_wait| B[内核态] %% 内核判断就绪 B --> C{文件描述符就绪?} C -->|是| D[返回就绪 fd 集合] C -->|否| E["阻塞或超时"] subgraph select_poll [select / poll 特点] F["fd_set / fd数组线性扫描, O(n)"] G[每次调用需重建 fd_set / 数组] H["用户态 <-> 内核态频繁拷贝"] end subgraph epoll [epoll 特点] I[内核维护就绪事件链表] J["只返回有事件的 fd, O(1)"] K[适合高并发场景] end %% 连接节点 D --> F D --> G D --> H D --> I I --> J J --> K