• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2023-08-20 15:39 Aet 隐藏边栏 |   抢沙发  20 
文章评分 1 次,平均分 5.0

平台相关

  1. select
    1. 支持:windows
    2. 支持:linux,macos,bsd,aix
  2. poll
    1. 支持:linux,macos,bsd
    2. 不支持:一些交旧的或特定的unix操作系统可能不支持poll
    3. 不支持:windows不提供标准的poll,但可以通过其他相似的api来达到类似效果
  3. epoll
    1. 支持:linux特有的
  4. 三方库
    1. 虽然selectpoll在大多数UNIX-like系统中广泛支持,epoll却仅限于Linux
    2. 要编写跨平台的网络代码,可以考虑使用库(如libeventBoost.Asio等)
      这些库提供了对不同操作系统I/O多路复用机制的统一封装

fd_set

select

概述

  1. select 允许应用程序同时监视多个文件描述符(例如套接字),以查看它们是否准备好进行读取、写入或是否有异常发生
  2. 这种能力使得程序可以同时处理多个网络连接或其他I/O操作,而无需为每个连接使用单独的线程或进程

原型

参数

  1. 要监视的文件描述符数量(最大描述符值+1)
  2. 准备好读取的描述符集
  3. 准备好写入的描述符集
  4. 异常条件出现的描述符集
  5. 最长等待时间,或NULL以无限等待

返回值

  1. 正值
    1. 返回值表示在所提供的文件描述符集中准备好进行某些操作(读、写、异常)的文件描述符数量
    2. 也就是说表示有多少描述符具有待处理的事件
  2. 0
    1. 如果在指定的超时期间没有任何描述符准备好进行操作,select 将返回0
    2. 这在某些情况下可能是正常的,但也可能是您的代码应该处理的特殊情况
  3. 负值
    1. 如果发生错误,select 将返回-1,并设置全局错误变量 errno 以提供有关错误的更多信息

优点

  1. 简单易用
  2. 跨平台支持

缺点

  1. 描述符数量通常限制为1024
  2. 每次调用 select 时都必须重新初始化文件描述符集和超时结构
  3. 可能不适合处理大量并发连接

详细

  1. 初始化文件描述符集:
    1. 使用 FD_ZEROFD_SET 函数初始化要监视的文件描述符集合
    2. 通常,这些集合分别用于监视读操作、写操作和异常
  2. 设置超时:
    1. 设置一个超时值,以指定 select 函数等待的最长时间
  3. 调用select函数:
  4. 检查结果
    1. 返回后,读取、写入和异常的文件描述符集将被更新,以反映哪些描述符准备好进行相应的操作
      1. 可以使用 FD_ISSET 宏来检查特定描述符是否准备好
  5. 处理准备好的描述符
    1. 遍历准备好的描述符,并进行读取、写入或处理异常
  6. 重复:
    1. 通常,select 调用将放在循环中,以持续监视描述符

示例

select理解

描述符数量限制

  1. 在许多UNIX系统上,select函数的文件描述符数量限制确实为1024
  2. 这个限制主要是因为在select的实现中,文件描述符集合通常使用位图表示,该位图的大小固定为FD_SETSIZE,它通常定义为1024

定期检查(timeout)?

  1. select函数不是定时检查所有描述符
    1. 它是一种允许程序监视多个文件描述符(例如套接字)以查看它们是否准备好进行读取、写入或是否有异常条件的方法
  2. 调用select时,需要提供三个文件描述符集合(用于读取、写入和异常检测)以及一个可选的超时值(timeout
    函数将阻塞,直到以下情况之一发生:

    1. 描述符集合中的一个或多个描述符准备好进行读取、写入或有异常
    2. 指定的超时时间到达
    3. 被信号中断

timeout参数理解

  1. timeout是一个“等待限制”,而不是监视描述符集合的时间
  2. timeout设置为NULL
    1. select会无限期地等待,直到至少有一个描述符准备好
  3. 超时时间到达,但没有描述符就绪
    1. select仍然会返回,但这是由于时间超时,而不是因为有描述符准备好
    2. 这种情况下,可以检查返回的描述符集,看看它们是否为空,从而知道select是因为哪种原因返回的
  4. 在超时时间到达之前,有描述符就绪
    1. 这种情况下,select会立即返回,并告诉你哪些描述符已经准备好

文件描述符位于?

  1. 文件描述符本身实际上是内核中资源的句柄,代表了一个打开的文件、套接字、管道等
    1. 这些描述符的状态和相关信息都是由内核管理的
    2. 调用select函数并传递一个或多个描述符集合时,内核会检查这些描述符的状态

内核单独记录文件描述符状态?

  1. 每个文件描述符都有一组与其关联的状态,这些状态用于跟踪文件描述符的不同属性,如是否可读、可写、是否存在异常条件等
  2. 这些状态并不是分开管理的,而是与文件描述符紧密关联的

select与内核的交互

  1. 调用select函数并传递readfds参数时,实际上是在告诉内核您想要监视哪些特定的文件描述符是否可读
    1. 因此,需要在调用select之前,手动设置readfds中您关心的文件描述符
    2. 内核会检查我们关心的这些描述符的当前状态
  2. 然后,select函数会阻塞,直到这些指定的文件描述符之一变为可读、超时发生,或者接收到一个信号
    1. 这些情况下,select会返回,并且readfds将被修改以指示哪些文件描述符现在是可读的
    2. 任何未准备好的文件描述符将在readfds集合中被清除

select与客户端服务器程序

  1. 服务端
    1. 服务器首先创建一个监听套接字来等待客户端连接请求
      该套接字的文件描述符通常会被加入到selectreadfds集合中,以便服务器能知道何时有新的连接请求到来
    2. 一旦客户端连接被接受,它的新的文件描述符(代表特定的客户端连接)也会被加入到readfds(有时也可能是writefds)集合中
      以便服务器能读取该客户端发送的数据或向其发送数据
      accept成功时返回一个新的套接字描述符,该描述符代表与客户端的新连接
  2. 客户端
    1. 客户端创建一个连接到服务器的套接字,这个套接字的文件描述符通常会被加入到客户端的readfds和/或writefds集合中
      以便客户端可以知道何时可以从服务器读取数据或向服务器写入数据

关于select的返回值

  1. select函数的返回值表示整个集合中就绪的文件描述符的数量
    1. 如果readfds里面有1个就绪的描述符,而writefds里面有2个就绪的描述符,那么select的返回值将会是3

需要重新设置文件描述符

  1. 每次select调用返回后,只有那些就绪的文件描述符会保留在集合中,而其他的描述符则会被清除
  2. 所以在每次调用select之前,你都需要重新设置你感兴趣的文件描述符集合
  3. 常见的用法:

pollfd

  1. 要监视的文件描述符
  2. 指定我们感兴趣的事件,如POLLIN(可读),POLLOUT(可写)等
  3. 在调用返回时由内核设置,以报告哪些请求的事件实际上是就绪的

poll

概述

  1. poll是另一种I/O多路复用机制
    1. select相比,poll提供了更直接和灵活的接口来监视多个文件描述符的状态
    2. select使用三个不同的描述符集合不同,poll使用一个pollfd结构数组,其中每个结构对应一个描述符

原型

参数

  1. fds是一个指向pollfd结构数组的指针
  2. nfds是数组中的项数
  3. timeout是等待时间(以毫秒为单位)

返回值

  1. 正值
    1. 返回的正数表示已经就绪的文件描述符的数量
      换句话说,它告诉你有多少个文件描述符在指定的事件(如可读、可写等)上已经就绪
  2. 0
    1. 表示指定的超时时间已经过去,但没有文件描述符就绪
      这是一个正常的情况,表示在给定的时间内没有任何活动
  3. 负值
    1. 如果返回值为负,则表示发生了错误

优点

  1. 不需要每次调用前重新设置文件描述符集合
  2. 没有文件描述符数量的固定限制,因为它是基于数组,而不是固定大小的位集合

缺点

  1. 虽然API使用起来相对简单,但与select相比,在性能方面通常没有显著的改进
  2. 在大量描述符的情况下,遍历所有描述符可能会相对低效

示例

epoll_event

  1. 感兴趣的事件,如 EPOLLINEPOLLOUT
  2. 用户定义的数据,可以是文件描述符或其它信息

epoll

概述

  1. epollLinux操作系统专有的I/O多路复用机制,它被设计用于解决大量并发连接的高性能监视问题
  2. 相比于selectpollepoll更加高效,特别是在大量文件描述符需要被监视时

相关函数

  1. 此参数可以设置为 0 EPOLL_CLOEXEC
    1. 如果设置为 EPOLL_CLOEXEC,则在执行 exec 调用新程序时会关闭文件描述符
  2. 返回值
    1. 成功:返回一个文件描述符,代表新创建的 epoll 实例
    2. 失败:返回 -1,并设置 errno

  1. epoll_create1 返回的 epoll 实例描述符
  2. 操作类型,如 EPOLL_CTL_ADD(添加)、EPOLL_CTL_MOD(修改)、EPOLL_CTL_DEL(删除)
  3. 要操作的文件描述符
  4. 指向 struct epoll_event 的指针,描述感兴趣的事件和其他信息
  5. 返回值
    1. 成功:返回 0
    2. 失败:返回 -1,并设置 errno

  1. epoll_create1 返回的 epoll 实例描述符
  2. 指向 struct epoll_event 数组的指针,用于接收已触发的事件
  3. events 数组的大小
  4. 等待的毫秒数,-1 表示无限等待,0 表示立即返回
  5. 返回值
    1. 成功:返回准备好的文件描述符数量
    2. 失败:返回 -1,并设置 errno
    3. 超时:返回 0

详细

  1. 创建epoll实例
    1. 首先通过调用epoll_createepoll_create1函数来创建一个epoll实例
  2. 注册感兴趣的事件
    1. 然后,使用epoll_ctl来注册文件描述符和对应的感兴趣的事件(如读、写等)
    2. 这些信息保存在内核中,而非每次调用都重新传递
  3. 等待事件发生
    1. 通过调用epoll_wait来等待感兴趣的事件发生
    2. 不同于selectpoll每次调用都遍历所有文件描述符,epoll只返回已经准备好的文件描述符,因此更加高效
  4. 处理事件
    1. epoll_wait返回后,程序可以处理返回的事件列表,不需要遍历和检查所有描述符

优点

  1. 能够高效地处理大量并发连接
  2. 不需要每次都传递所有监视的文件描述符
  3. 可以更灵活地添加、修改和删除监视的文件描述符和事件

示例

epoll理解

文件描述符

  1. epoll_create1 函数的返回值是一个文件描述符(file descriptor),该描述符代表新创建的 epoll 实例
  2. Linux 系统中,许多资源(如文件、套接字、管道等)都可以通过文件描述符来引用和操作,epoll 实例也不例外

注册事件

  1. epoll_ctl 函数用于控制 epoll 实例,并允许你在内核中注册、修改或删除文件描述符及其对应的感兴趣的事件
    1. 调用 epoll_ctl 并使用 EPOLL_CTL_ADD 操作来注册一个文件描述符时
      实际上是在告诉内核我们对该描述符的某些特定事件(如可读、可写等)感兴趣
      对应结构体和文件描述符一同存储在内核的数据结构中
  2. 这个机制允许我们有效地监视多个文件描述符的状态,而无需不断轮询它们
    1. 当事件发生时,我们可以使用 epoll_wait 函数来检索这些事件

等待事件

  1. 使用 epoll_ctlEPOLL_CTL_ADD 操作注册一个文件描述符和相应的感兴趣事件时,内核不会立即通知任何东西
    1. 需要使用 epoll_wait 函数来等待这些事件发生
  2. 调用 epoll_wait 时,它会阻塞,直到注册的文件描述符上发生了感兴趣的事件或超时
    1. 返回时,epoll_wait 将填充一个 epoll_event 数组,其中包括已发生的事件及其对应的文件描述符
    2. 具体是,内核将检查已注册的文件描述符,并将之前注册的感兴趣的事件匹配的事件填充到数组中
      事件填充到数组中的顺序可能是不确定的,但它们都将是在你调用 epoll_wait 之前已准备好的事件

处理事件

  1. 可以迭代 epoll_event 数组并根据事件类型处理每个事件

清理

  1. 当完成时,使用 close 关闭 epoll 实例

epoll是否可以理解为通知

  1. 可以将 epoll 的工作方式理解为一种通知机制
    1. 当你使用 epoll_ctl 注册文件描述符和感兴趣的事件后,内核会开始监视这些文件描述符
    2. 当感兴趣的事件发生时,内核会“通知”应用程序
  2. 这个通知不是通过异步信号或其他中断机制来实现的,而是通过将事件信息存储在内部数据结构中
    1. 然后,当应用程序调用 epoll_wait 时,内核将检查已经发生的事件,并将它们填充到提供的数组中
    2. 这样,应用程序就可以知道哪些文件描述符已经准备好进行读取或写入
  3. 进一步理解:
    1. 先向内核注册要监听的文件描述符,并指明感兴趣的事件
    2. 当注册的文件描述符上发生了感兴趣的事件,内核会将事件的信息存储在内部数据结构中
      并不是所有的事件会被记录
    3. 当调用epoll_wait时,该调用可能会阻塞(除非设置了超时或使用非阻塞方式)
      一旦有一个或多个感兴趣的事件发生,epoll_wait将返回,并将这些事件填充到提供的数组中
      而返回的整数值表示了填充到数组中的事件数量

ET模式

概述

  1. 边缘触发(Edge-Triggered, ET)模式是 epoll 提供的一种高级选项,用于通知感兴趣的事件
  2. 这种模式下,当事件从未发生到发生状态的转变时,epoll 仅通知应用程序一次

一次性通知

  1. 在边缘触发模式下,当感兴趣的事件首次发生时,应用程序会被通知
    1. 只要应用程序不完全处理该事件,例如读取所有可用的数据,即使条件仍然满足(例如仍有数据可读),应用程序将不会再次被通知

非阻塞操作

  1. 由于边缘触发模式只通知一次事件的发生,因此通常需要将文件描述符设置为非阻塞,并确保在通知后完全处理该事件
    1. 否则,如果事件没有完全处理,应用程序可能会错过后续的事件通知

性能优势

  1. 边缘触发模式通常用于高性能网络编程,因为它减少了内核与应用程序之间的通信开销
    1. 在高流量环境中,减少不必要的通知可以提高效率

复杂性

  1. 由于边缘触发模式的一次性通知特性,正确使用它可能会更加复杂
    1. 必须仔细设计代码以确保不会丢失事件,并能够适当地处理部分完成的操作(例如部分读取或写入)

启用方式

  1. 要启用边缘触发模式,你可以在调用 epoll_ctl 时将 EPOLLET 标志与感兴趣的事件组合
    1. 例如,要以边缘触发模式监视读取事件,你可以使用 EPOLLIN | EPOLLET

组合标志

  1. 除了基本的读取和写入事件,边缘触发模式还可以与其他 epoll 标志和选项结合使用
    1. 例如 EPOLLONESHOT,该选项表示只通知一次事件,之后将自动将文件描述符从 epoll 实例中删除

LT模式

概述

  1. 水平触发(Level-TriggeredLT)是 epoll 的默认工作模式
  2. 这种模式下,当文件描述符准备好进行读取或写入时,epoll 会一直报告该描述符,直到条件不再满足为止

重复通知

  1. 在水平触发模式下,只要文件描述符处于可读或可写的状态,每次调用 epoll_wait 都会返回该描述符的事件
    1. 这意味着即使你没有读取或写入文件描述符,只要条件仍然满足,你将继续收到通知

不需要完全消费

  1. 水平触发模式下,你不需要在接收通知后完全消费文件描述符的所有数据
    1. 例如,如果一个套接字可读,并且你只读取了一部分数据,下一次调用 epoll_wait 时你仍然会收到可读事件的通知,只要还有更多数据可读

相似于select和poll

  1. 水平触发模式的行为与传统的 selectpoll 函数类似
    1. 如果文件描述符的状态没有改变,每次检查都会返回相同的结果

区别于边缘触发

  1. 主要区别在于通知频率
    1. 在边缘触发模式下,文件描述符的状态变化会触发通知,但只有在状态从“不可用”变为“可用”时才会通知一次
    2. 水平触发会在描述符可用时持续通知

iocp

概述

  1. IOCPI/O Completion Ports)是Windows操作系统提供的一种高效的I/O多路复用模型
  2. Unix/Linux下的 epollkqueue 类似,IOCP提供了一种可扩展的方式来同时处理大量I/O操作,但它是针对Windows的异步I/O设计的

异步I/O操作

  1. IOCP基于异步I/O
    1. 当你开始一个I/O操作时,你可以提供一个称为“重叠结构”的数据结构(OVERLAPPED 结构),该结构包括用于后续处理的信息
    2. 如果I/O操作不能立即完成,函数调用会立即返回,让调用线程继续执行其他任务

I/O完成端口

  1. I/O完成端口是一个特殊的句柄,可以与一个或多个文件、套接字或其他I/O句柄关联
  2. 当与I/O完成端口关联的句柄上的异步I/O操作完成时,完成通知会排队到端口

工作者线程

  1. 通常会有多个线程等待在同一个I/O完成端口上
  2. I/O操作完成时,其中一个线程会被唤醒来处理结果
    1. 这些线程通常称为“工作者线程”,它们共享对I/O完成端口的访问,从而实现了线程池的效果

I/O完成通知

  1. 当异步I/O操作完成时,与I/O完成端口关联的工作者线程之一会接收到通知
    1. 通知包括I/O操作的结果和先前传递的“重叠结构”,其中包括操作的详细信息

可扩展性

  1. IOCP为处理大量并发I/O操作提供了一种可扩展的方法
    1. 工作者线程可以有效地在多个处理器核心之间分配,而I/O完成端口可以管理大量I/O句柄的通知

使用

  1. 创建

  1. 关联句柄
    1. 将文件、套接字或其他I/O句柄与I/O完成端口关联
  2. 启动异步I/O操作
    1. 使用Windows的异步I/O函数启动操作,并提供重叠结构
  3. 等待和处理I/O完成通知
    1. 工作者线程使用 GetQueuedCompletionStatus 函数等待I/O完成通知,并处理结果

本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

bingliaolong
Bingliaolong 关注:0    粉丝:0
Everything will be better.

发表评论

表情 格式 链接 私密 签到
扫一扫二维码分享