首页 > 编程语言 >网络编程IO多路复用之poll模式

网络编程IO多路复用之poll模式

时间:2024-11-08 16:15:58浏览次数:6  
标签:struct 多路复用 list 描述符 pollfd fd IO poll

网络编程IO多路复用之poll模式

文章目录

1. poll 函数原型

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数说明

1、struct pollfd *fds:一个指向 pollfd 结构体数组的指针,每个结构体表示一个要监控的文件描述符。

2、nfds_t nfds:要监控的文件描述符数量。

3、int timeout:等待的时间(以毫秒为单位)。如果设置为 -1,则 poll 将无限期阻塞,直到至少一个文件描述符准备好为止。如果设置为 0,则 poll 立即返回,不阻塞。

pollfd 结构体

  • events:请求的事件,可以是以下值的组合:

    • POLLIN:文件描述符可读。
    • POLLOUT:文件描述符可写。
    • POLLERR:发生错误。
    • POLLHUP:挂起(例如,对端关闭连接)。
    • POLLNVAL:无效的请求。
  • revents:实际发生的事件,返回时会被填充。

返回值

> 0:准备好的文件描述符的数量。

0:超时,没有文件描述符准备好。

-1:发生错误,可以通过 errno 获取具体的错误码。

2. 系统调用过程

**用户初始化:**用户程序初始化pollfd结构体数组,指定需要检测的文件描述符和事件类型。

// 使用方式
struct pollfd fds[100];  // 数组形式

系统调用:用户程序通过poll系统调用将这些结构体传递给内核,并指定一个超时时间。在内核中保存pollfd的数据结构是poll_list,poll_list链表单个元素可以存储固定数量的pollfd对象。

// 内核态的 poll_list 结构
struct poll_list {
    struct poll_list *next;   // 指向下一个 poll_list
    int len;                  // 当前片段包含的 pollfd 数量
    struct pollfd entries[]; // 柔性数组
};

// POLL_ENTRY_SIZE:每个 poll_list 节点最大能存储的 pollfd 数量
#define POLL_ENTRY_SIZE ((PAGE_SIZE - sizeof(struct poll_list)) / \
                         sizeof(struct pollfd))

内核轮询:内核开始轮询这些结构体中的文件描述符,检查是否有指定的事件发生。一次poll调用需完成整个poll_list链表轮询工作,轮询socket的过程中会创建socket等待队列项,并加入socket等待队列(用于socket唤醒进程)

事件检测如果检测到某个文件描述符处于就绪状态,内核会将此文件描述符对应的revents字段设置为实际发生的事件。poll系统调用完成一次轮询后

  • 如果检测到有 socket 处于就绪状态,则将poll_list 链表所有的 pollfd 通过copy_to_user 拷贝至用户 pollfd 数组。
  • 如果未检测到有socket处于就绪状态,根据超时时间确定是否返回或者阻塞进程。
if (copy_to_user(ufds, list->entries, 
                nfds * sizeof(struct pollfd))) {
   return -EFAULT;
}

超时处理:如果在指定时间内没有文件描述符变为就绪状态,则poll会阻塞进程直到超时时间到达。

结果返回:一旦有文件描述符变为就绪状态,或者超时时间到达,内核会通过注册到socket等待队列的回调函数poll_wake将进程唤醒,唤醒的进程将再次轮询poll_list链表。

在这里插入图片描述

超时机制:

poll支持非阻塞模式(立即返回)和阻塞模式(等待超时)。用户可以通过提供一个int值来指定超时时间。如果在指定时间内没有任何文件描述符变为就绪状态,poll会阻塞当前线程直到超时时间到达。

// timeout 的三种情况:
timeout = -1   // 永久阻塞
timeout = 0   // 立即返回
timeout > 0   // 等待指定毫秒数

3. poll编程模型图


+------------------+     1. 创建 socket     +------------------+
|                  |-------------------->   |                  |
|                  |                        |    Socket 1      |
|                  |     2. bind/listen     |                  |
|                  |-------------------->   |                  |
|                  |                        +------------------+
|                  |
|                  |                        +------------------+
|                  |     1. 创建 socket     |                  |
|    应用程序      |-------------------->    |    Socket 2      |
|                  |                        |                  |
|  +----------+    |     2. connect         |                  |
|  | pollfd[] |    |-------------------->   |                  |
|  |----------|    |                        +------------------+
|  | fd1      |    |
|  | events1  |    |                        +------------------+
|  | revents1 |    |     1. 创建 socket     |                  |
|  |----------|    |-------------------->   |    Socket 3      |
|  | fd2      |    |                        |                  |
|  | events2  |    |     2. connect         |                  |
|  | revents2 |    |-------------------->   |                  |
|  |----------|    |                        +------------------+
|  | ...      |    |
|  +----------+    |                        +------------------+
|       |         |                         |   等待队列        |
|       |         |     3. poll() 调用      |   +--------+     |
|       +----------------------------------------> 进程   |     |
|                 |                         |   |  节点   |     |
|                 |                         |   +--------+     |
|                 |                         +------------------+
|                 |
|                 |     4. 事件就绪          +------------------+
|                 | <--------------------   |    事件源        |
|                 |                         |  (数据/连接等)   |
+------------------+                        +------------------+

执行流程:
1. 应用程序创建多个 socket
2. 创建 pollfd 数组,设置感兴趣的事件(events)
3. 调用 poll():
   - 如果没有就绪事件,进程被挂起
   - 进程节点加入等待队列
4. 当有事件发生:
   - 设置对应的 revents
   - 唤醒进程
5. poll() 返回,应用程序处理就绪事件
6. 重复步骤 3-5

事件类型:
POLLIN:  可读
POLLOUT: 可写
POLLERR: 错误
POLLHUP: 挂起

这个模型图展示了:

  1. pollfd 数组的结构
  2. 进程与多个 socket 的关系
  3. poll 调用的等待机制
  4. 事件通知的流程
  5. 整体的事件循环模式

4. poll事件

  1. 基本事件定义
// <poll.h> 中的事件定义
#define POLLIN      0x0001    // 可读
#define POLLPRI     0x0002    // 高优先级可读
#define POLLOUT     0x0004    // 可写
#define POLLERR     0x0008    // 错误
#define POLLHUP     0x0010    // 挂起
#define POLLNVAL    0x0020    // 文件描述符未打开
  1. 组合事件定义
// 常用的组合事件
#define POLLRDNORM  0x0040    // 普通数据可读
#define POLLRDBAND  0x0080    // 优先级带数据可读
#define POLLWRNORM  0x0100    // 普通数据可写
#define POLLWRBAND  0x0200    // 优先级带数据可写
#define POLLMSG     0x0400    // 消息可用
#define POLLRDHUP   0x2000    // TCP连接被对端关闭,或关闭写半连接
  1. 事件使用示例
// 1. 基本读写监控
struct pollfd pfd = {
    .fd = sockfd,
    .events = POLLIN | POLLOUT,  // 监控读写事件
};

// 2. 全面监控
struct pollfd pfd = {
    .fd = sockfd,
    .events = POLLIN | POLLPRI | POLLOUT | POLLRDHUP,
};

// 3. 只读监控
struct pollfd pfd = {
    .fd = sockfd,
    .events = POLLIN | POLLRDNORM,
};
  1. 常见事件组合场景
// TCP 服务器监听套接字
struct pollfd listen_fd = {
    .fd = listenfd,
    .events = POLLIN,  // 只关注新连接到达
};

// TCP 客户端数据处理
struct pollfd client_fd = {
    .fd = clientfd,
    .events = POLLIN | POLLOUT | POLLRDHUP,  // 关注读写和连接关闭
};

// 管道读端
struct pollfd pipe_read = {
    .fd = pipefd[0],
    .events = POLLIN | POLLPRI,  // 关注普通数据和优先级数据
};
  1. 事件触发条件
// POLLIN 触发条件
if (POLLIN & revents) {
    // 1. 接收缓冲区中有数据可读
    // 2. 对端关闭连接(读到EOF)
    // 3. 监听socket有新连接请求
    // 4. 有错误待处理
}

// POLLOUT 触发条件
if (POLLOUT & revents) {
    // 1. 发送缓冲区有空闲空间
    // 2. 非阻塞connect连接完成
}

// POLLERR 触发条件
if (POLLERR & revents) {
    // 1. 发生错误
    // 注意:不需要在 events 中设置,自动监测
}
  1. 完整的事件处理示例
void handle_poll_events(struct pollfd *pfds, nfds_t nfds)
{
    for (nfds_t i = 0; i < nfds; i++) {
        // 检查是否有错误
        if (pfds[i].revents & POLLERR) {
            handle_error(pfds[i].fd);
            continue;
        }

        // 检查连接是否断开
        if (pfds[i].revents & (POLLHUP | POLLRDHUP)) {
            handle_disconnect(pfds[i].fd);
            continue;
        }

        // 检查是否可读
        if (pfds[i].revents & (POLLIN | POLLRDNORM)) {
            if (pfds[i].fd == listen_fd) {
                handle_new_connection();
            } else {
                handle_read(pfds[i].fd);
            }
        }

        // 检查是否可写
        if (pfds[i].revents & (POLLOUT | POLLWRNORM)) {
            handle_write(pfds[i].fd);
        }

        // 检查无效文件描述符
        if (pfds[i].revents & POLLNVAL) {
            cleanup_fd(pfds[i].fd);
        }
    }
}
  1. 自动监测的事件
// 这些事件不需要在 events 中设置
POLLERR  // 错误
POLLHUP  // 挂起
POLLNVAL // 无效fd

5. 总结

1:对比select,poll不是跨平台的。

2: poll 和 select一样,需要遍历所有的文件描述符,poll是遍历的poll_list链表

3:poll 对比 select,没有1024限制。

4:poll 返回后,和 select类似,程序需要手动遍历所有已经注册的文件描述符来确定哪些描述符已经准备好。

5:在再次调用 poll的时候,对于文件描述符数组,poll 不需要再次赋值,select 在每次调用之前都需要重新赋值文件描述符集合,因为上次的调用对集合做了修改。

6. 延伸问题

问题1:poll_list链表具体是怎么工作的?

问题2:poll返回的revent处理完毕后需要清零处理吗?

问题3:poll是同步还是异步?

问题4:poll如何设置非阻塞模式?

问题5: poll对比select有哪些优点?

问题6: poll在内核为什么要使用poll_list链表结构而不是数组?

问题7: poll在处理大量文件描述符时的性能瓶颈在哪里?

问题8:如何处理 poll 被信号中断的情况?

问题9:poll为什么要将events 和 revents分开?

在这里插入图片描述

标签:struct,多路复用,list,描述符,pollfd,fd,IO,poll
From: https://blog.csdn.net/2301_78694061/article/details/143564839

相关文章

  • Neural Networks for Image  Classification Duration
    Lab2:NeuralNetworksforImage ClassificationDuration:2hoursTools:JupyterNotebookIDE:PyCharm==2024.2.3(oranyIDEofyourchoice)Python:3.12Libraries:oPyTorch==2.4.0oTorchVision==0.19.0oMatplotlib==3.9.2LearningObjectives:Unders......
  • Android Audio中 AudioTrack、 AudioFlinger和 HAL 使用dump的区别
    Audiodump在定位音频的各种问题非常重要,我们主要在AudioTrack、AudioFlinger和HAL层中会用到,这里我们先明确一下在不同层使用dump的区别。以下是关于AudioTrack、AudioFlinger和HAL(HardwareAbstractionLayer,硬件抽象层)中dump的区别和使用场景:一、区别Audi......
  • 《EasyQuotation 与MongoDB在股市信息的奇妙融合》
    《EasyQuotation与MongoDB在股市信息的奇妙融合》一、EasyQuotation的强大功能二、数据存入MongoDB(一)配置与连接(二)存储方法三、K线图监视股市信息(一)自定义性能趋势图表(二)实时金融分析功能四、荐股信息生成(一)荐股信息生成基础(二)算法在荐股中的应用(三)风险提示与局限......
  • The 2022 ICPC Asia Hangzhou Regional Programming Contest C
    C.NoBugNoGame\(很简单的一个dp\)\(在枚举到当前为i的时候假设当前容量为j对其进行转移\)点击查看代码#include<bits/stdc++.h>#defineintlonglong#defineall(x)x.begin(),x.end()#definerall(x)x.rbegin(),x.rend()#definepbpush_back#definepiipair<......
  • VS 2022 不支持 .NET Framework 4.5 项目解决办法(Visual Studio 2022)
    VS2022不支持.NETFramework4.5项目解决办法(VisualStudio2022) 概述最近C#开发工具VisualStudio升级到了2022,打开速度快了很多,开发体验也舒服很多。只是使用过程中遇到了一个比较尴尬的问题:默认VisualStudio2022不再支持安装.NETFramework4.5组件,如下图所......
  • .NET IoC 容器
    .NETIoC容器1控制反转(IoC)是什么控制反转(IoC)是一种软件设计原则,它指导在软件组件之间解耦合和降低依赖性。通常,传统的程序设计中,组件会直接调用其他组件或者服务,这样会造成组件之间高度耦合,难以维护和测试。控制反转通过将控制权从调用方转移到外部容器或框架,使得组件不需要显......
  • .NET IoC 容器(二)Unity
    .NETIoC容器(二)Unity1UnityUnityApplicationBlock(Unity)是Microsoft模式和实践团队(Patterns&Practicesteam)推出的一个开源依赖注入容器。它是.NETFramework的一个组件,旨在简化应用程序的构建过程,提高代码的可测试性和可维护性。UnityApplicationBlock提供了以下功能:......
  • .NET IoC 容器(三)Autofac
    .NETIoC容器(三)Autofac1AutofacAutofac是一个用于.NET应用程序的依赖注入(DependencyInjection,DI)容器。它帮助开发人员管理对象的创建和生命周期,使得依赖项的注入更加灵活和可维护。以下是Autofac的主要功能和特性概述:依赖注入(DependencyInjection)Autofac允......
  • iOS-Tagent上新了,iOS17的wda的部署小技巧你get了吗
    此文章来源于项目官方公众号:“AirtestProject”版权声明:允许转载,但转载必须保留原链接;请勿用作商业或者非法用途一、前言之前有很多同学一直在问如何在iOS17的设备上安装wda?其实我们iOS-Tagent已经更新兼容iOS17以上的设备啦,虽然无法直接通过AirtestIDE进行连接,但是可以先......
  • MCGS_IOT配置步骤
    1,添加设备驱动2,配置驱动3,添加变量4,组态画面5,运行工程6,远程运维7,远程登录,运维8,完成......