Poll的原理及代码

本文对比了select和poll系统调用在监控文件描述符状态方面的特点,讨论了select的缺点如文件描述符数量限制、线性扫描和不支持高精度超时,强调了poll的优点如无数量限制、无需预设位集,但也提到了poll可能在大规模文件描述符下的性能问题。
摘要由CSDN通过智能技术生成

一、Select的存在问题及缺点

        使用 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

  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;
}

  • 24
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值