select & poll
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
// 和 select 紧密结合的四个宏:
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);
select:
#include <stdio.h>
#include <poll.h>
int main() {
struct pollfd fds[1];
int timeout = 5000; // 设置超时时间为 5 秒
fds[0].fd = 0; // 标准输入的文件描述符为 0
fds[0].events = POLLIN; // 关注可读事件
while (1) {
int ready = poll(fds, 1, timeout); // 调用 poll 函数等待事件发生
if (ready == -1) {
perror("poll");
break;
} else if (ready == 0) {
printf("Timeout\n");
} else {
// 检查标准输入是否就绪
if (fds[0].revents & POLLIN) {
char buffer[1024];
int bytesRead = read(0, buffer, sizeof(buffer));
if (bytesRead > 0) {
// 处理从标准输入读取的数据
printf("Read %d bytes: %.*s\n", bytesRead, bytesRead, buffer);
} else if (bytesRead == 0) {
printf("EOF\n");
break;
} else {
perror("read");
break;
}
}
}
}
return 0;
}
poll:
#include <stdio.h>
#include <poll.h>
int main() {
struct pollfd fds[1];
int timeout = 5000; // 设置超时时间为 5 秒
fds[0].fd = 0; // 标准输入的文件描述符为 0
fds[0].events = POLLIN; // 关注可读事件
while (1) {
int ready = poll(fds, 1, timeout); // 调用 poll 函数等待事件发生
if (ready == -1) {
perror("poll");
break;
} else if (ready == 0) {
printf("Timeout\n");
} else {
// 检查标准输入是否就绪
if (fds[0].revents & POLLIN) {
char buffer[1024];
int bytesRead = read(0, buffer, sizeof(buffer));
if (bytesRead > 0) {
// 处理从标准输入读取的数据
printf("Read %d bytes: %.*s\n", bytesRead, bytesRead, buffer);
} else if (bytesRead == 0) {
printf("EOF\n");
break;
} else {
perror("read");
break;
}
}
}
}
return 0;
}
poll 的实现和 select 非常相似,只是描述 fd 集合的方式不同,poll 使用 pollfd 结构而不是 select 的 fd_set 结构,poll 解决了最大文件描述符数量限制的问题,但是同样需要从用户态拷贝所有的 fd 到内核态,也需要线性遍历所有的 fd 集合,所以它和 select 只是实现细节上的区分,并没有本质上的区别。
epoll:
epoll 的API
#include <sys/epoll.h>
int epoll_create(int size); // int epoll_create1(int flags);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
int main() {
int epoll_fd = epoll_create1(0); // 创建 epoll 实例
if (epoll_fd == -1) {
perror("epoll_create1");
return 1;
}
struct epoll_event event;
event.events = EPOLLIN; // 关注可读事件
event.data.fd = 0; // 标准输入的文件描述符为 0
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, 0, &event) == -1) {
perror("epoll_ctl");
close(epoll_fd);
return 1;
}
struct epoll_event events[1];
int max_events = 1;
int timeout = 5000; // 设置超时时间为 5 秒
while (1) {
int ready = epoll_wait(epoll_fd, events, max_events, timeout); // 调用 epoll_wait 函数等待事件发生
if (ready == -1) {
perror("epoll_wait");
break;
} else if (ready == 0) {
printf("Timeout\n");
} else {
// 检查标准输入是否就绪
if (events[0].events & EPOLLIN) {
char buffer[1024];
int bytesRead = read(0, buffer, sizeof(buffer));
if (bytesRead > 0) {
// 处理从标准输入读取的数据
printf("Read %d bytes: %.*s\n", bytesRead, bytesRead, buffer);
} else if (bytesRead == 0) {
printf("EOF\n");
break;
} else {
perror("read");
break;
}
}
}
}
close(epoll_fd);
return 0;
}
通过代码实例更好的了解并区分他们之间的差异.
epoll 相较于select/poll 高性能的地方在哪?
epoll
相较于 select
和 poll
具有以下几个性能优势:
-
事件通知方式:
select
和poll
在有事件发生时需要遍历整个文件描述符集合来找到就绪的描述符,而epoll
利用回调机制,只返回就绪的文件描述符,避免了遍历整个集合的开销。 -
更高的扩展性:
select
和poll
的时间复杂度为 O(n),其中 n 是监视的文件描述符数量,随着文件描述符数量的增加,性能下降明显。而epoll
使用红黑树(Red-Black Tree)来存储文件描述符,时间复杂度为 O(log n),具有更好的扩展性。 -
效率更高的内核通知机制:
select
和poll
需要通过轮询方式或定时器来检查文件描述符的状态变化,而epoll
利用内核的事件通知机制(例如 epoll_wait),在文件描述符状态变化时直接由内核通知应用程序,避免了无效的轮询和定时器开销。 -
支持大规模并发:由于
epoll
使用红黑树存储文件描述符,因此可以支持大规模的并发连接。相比之下,select
和poll
对于大量文件描述符的集合来说性能较差。