标准输入上等待输入事件
#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