????????使用 select
时,需要在每次调用前重新设置位集的原因主要是 select
的工作方式。
? ?select
函数使用三个位集(fd_set
)来表示文件描述符的状态,包括可读、可写和异常。在每次调用 select
之前,你需要告诉它哪些文件描述符你关心,以及关心它们的哪些事件。这是通过设置相应的位来实现的。
1. 初始化位集: 在每次调用 select
之前,你需要通过 FD_ZERO
宏将位集清零,然后通过 FD_SET
宏将你关心的文件描述符加入位集。
fd_set readfds;
FD_ZERO(&readfds); // 清零
FD_SET(fd1, &readfds); // 将 fd1 加入位集
FD_SET(fd2, &readfds); // 将 fd2 加入位集
2.调用 select
: 然后,你将准备好的位集传递给 select
函数?
int ready = select(maxfd + 1, &readfds, NULL, NULL, NULL);
?????????这里 maxfd
是要监视的文件描述符中的最大值加一。select
将会修改传递给它的位集,指示哪些文件描述符处于就绪状态。
3.?检查位集: 调用 select
后,你需要检查位集,以确定哪些文件描述符处于就绪状态。由于 select
修改传递给它的位集,因此在每次调用之前都需要重新设置,以确保你关心的文件描述符和事件正确地反映在位集中。
if (FD_ISSET(fd1, &readfds)) {
// fd1 可读
}
if (FD_ISSET(fd2, &readfds)) {
// fd2 可读
}
1.文件描述符数量限制:
select
使用位集来表示文件描述符的状态,而这种表示方式有一个上限,通常由 FD_SETSIZE
宏定义。这意味着在某些系统上,你可能不能使用 select
监视超过该限制的文件描述符。2. 线性扫描:
select
在监视文件描述符时需要线性扫描整个位集,这导致其时间复杂度为 O(n),其中 n 是文件描述符的数量。在大规模文件描述符集合上,性能可能较差。3. 无法原子地更新:
select
的接口要求在调用之前重新设置位集,而在调用期间可能有新的文件描述符被动态添加或移除。这可能导致在多线程或多进程环境中无法原子地更新位集,从而引入竞态条件。4. 不提供对文件描述符事件的详细信息:
select
只能告诉你文件描述符是否就绪,但不能提供更详细的信息,例如为什么文件描述符就绪以及具体的就绪事件。这可能导致在处理复杂场景时需要进行额外的查询或检查。5.不支持超时精度:
select
使用 struct timeval
来表示超时时间,但其精度通常较低,只能精确到微秒级别。在某些应用场景下,可能需要更高精度的超时控制。??poll
是一种系统调用,用于实现多路复用,允许一个进程监视多个文件描述符的状态,以确定它们中是否有可以进行 I/O 操作的文件描述符。它是 POSIX 标准的一部分,提供了一种相对现代且更强大的多路复用机制。
poll
的工作方式:
poll
通过数组 fds
来指定要监视的文件描述符和关注的事件。poll
时,它会阻塞,直到指定的文件描述符中的至少一个就绪,或者超时发生。poll
返回时,通过检查 revents
字段,可以确定哪些文件描述符处于就绪状态。#include <poll.h>
struct pollfd {
int fd; /* 委托内核检测的文件描述符 */
short events; /* 委托内核检测文件描述符的什么事件 */
short revents; /* 文件描述符实际发生的事件 */
};
struct pollfd myfd;
myfd.fd = 5;
myfd.events = POLLIN | POLLOUT;
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
- 参数:
- fds : 是一个struct pollfd 结构体数组,这是一个需要检测的文件描述符的集合
- nfds : 这个是第一个参数数组中最后一个有效元素的下标 + 1
- timeout : 阻塞时长
0 : 不阻塞
-1 : 阻塞,当检测到需要检测的文件描述符有变化,解除阻塞
>0 : 阻塞的时长
- 返回值:
-1 : 失败
>0(n) : 成功,n表示检测到集合中有n个文件描述符发生变化
1. poll
没有文件描述符数量的限制,因为它使用动态分配的数据结构来存储文件描述符。
2.?在使用 poll
函数时,不需要在每次调用前重新设置描述符集。与 select
不同,poll
使用一个数组(struct pollfd
数组)来传递要监视的文件描述符以及关注的事件,而不是位集。因此,不需要像 select
那样在每次调用前重新设置位集。
poll
的性能可能在大规模文件描述符集合上较差,因为它仍然需要线性扫描 struct pollfd
数组。#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include<poll.h>
int main() {
// 创建socket
int lfd = socket(PF_INET, SOCK_STREAM, 0);
struct sockaddr_in saddr;
saddr.sin_port = htons(9999);
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
// 绑定
bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
// 监听
listen(lfd, 8);
// 创建一个fd_set的集合,存放的是需要检测的文件描述符
// fd_set rdset, tmp;
// FD_ZERO(&rdset);
// FD_SET(lfd, &rdset);
// int maxfd = lfd;
// 初始化检测的文件描述符数组
struct pollfd fds[1024];
int i=0;
for(i;i<1024;i++){
fds[i].fd = -1;
fds[i].events = POLLIN;
}
fds[0].fd=lfd;
int nfds =0;
while(1) {
// tmp = rdset;
// 调用select系统函数,让内核帮检测哪些文件描述符有数据
// int ret = select(maxfd + 1, &tmp, NULL, NULL, NULL);
// 调用poll系统函数,让内核帮检测哪些文件描述符有数据
int ret = poll(fds,nfds+1,-1);
if(ret == -1) {
perror("poll");
exit(-1);
} else if(ret == 0) {
continue;
} else if(ret > 0) {
// 说明检测到了有文件描述符的对应的缓冲区的数据发生了改变
if(fds[0].revents & POLLIN) {
// 表示有新的客户端连接进来了
struct sockaddr_in cliaddr;
int len = sizeof(cliaddr);
int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);
// 将新的文件描述符加入到集合中
// FD_SET(cfd, &rdset);
int i1=1;
for ( i1;i1<1024;i1++){
if(fds[i1].fd==-1){
fds[i1].fd=cfd;
fds[i1].events=POLLIN;
break;
}
}
// 更新最大的文件描述符
nfds = nfds > cfd ? nfds : cfd;
}
int i = 1;
for(i; i <= nfds; i++) {
if(fds[i].revents & POLLIN) {
// 说明这个文件描述符对应的客户端发来了数据
char buf[1024] = {0};
int len = read(fds[i].fd, buf, sizeof(buf));
if(len == -1) {
perror("read");
exit(-1);
} else if(len == 0) {
printf("client closed...\n");
close(fds[i].fd);
fds[i].fd=-1;
} else if(len > 0) {
printf("read buf = %s\n", buf);
write(fds[i].fd, buf, strlen(buf) + 1);
}
}
}
}
}
close(lfd);
return 0;
}