安徽合肥建设银行招聘网站,国外搜索引擎大全,icp网站备案,asp.net做的网站要放到网上空间去要放哪些文件上去文章目录 五种IO模型fcntl多路转接selectpollepollepoll的工作模式 五种IO模型
阻塞IO: 在内核将数据准备好之前, 系统调用会一直等待. 所有的套接字, 默认都是阻塞方式.阻塞IO是最常见的IO模型。非阻塞IO: 如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULD… 文章目录 五种IO模型fcntl多路转接selectpollepollepoll的工作模式 五种IO模型
阻塞IO: 在内核将数据准备好之前, 系统调用会一直等待. 所有的套接字, 默认都是阻塞方式.阻塞IO是最常见的IO模型。非阻塞IO: 如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK错误码。非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询. 这对CPU来说是较大的浪费, 一般只有特定场景下才使用.信号驱动IO: 内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作。O多路转接:和阻塞IO类似. 最核心在于IO多路转接能够同时等待多个文件描述符的就绪状态.异步IO: 由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据)。
任何IO过程中, 都包含两个步骤. 第一是等待, 第二是拷贝. 而且在实际的应用场景中, 等待消耗的时间往往都远远高于拷贝的时间. 让IO更高效, 最核心的办法就是让等待的时间尽量少。
同步通信VS异步通信 所谓同步就是在发出一个调用时在没有得到结果之前该调用就不返回. 但是一旦调用返回就得到返回值了; 换句话说就是由调用者主动等待这个调用的结果;异步则是相反调用在发出之后这个调用就直接返回了所以没有返回结果; 换句话说当一个异步过程调用发出后调用者不会立刻得到结果; 而是在调用发出后被调用者通过状态、通知来通知调用者或通过回调函数处理这个调用。
fcntl
函数原型
传入的cmd的值不同, 后面追加的参数也不相同 fcntl函数有5种功能:
复制一个现有的描述符cmdF_DUPFD获得/设置文件描述符标记(cmdF_GETFD或F_SETFD).获得/设置文件状态标记(cmdF_GETFL或F_SETFL).获得/设置异步I/O所有权(cmdF_GETOWN或F_SETOWN)获得/设置记录锁(cmdF_GETLK,F_SETLK或F_SETLKW)。
由于IO大多数都是阻塞的但是我们要设置非阻塞只是用第三种功能, 获取/设置文件状态标记, 就可以将一个文件描述符设置为非阻塞.
下面是一个可以将文件描述符设置为非阻塞的函数。
void SetNoBlock(int fd)
{int fl fcntl(fd, F_GETFL);if (fl 0) {perror(fcntl);return;}fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}多路转接
select
select系统调用是用来让我们的程序监视多个文件描述符的状态变化的;程序会停在select这里等待直到被监视的文件描述符有一个或多个发生了状态改变。 参数nfds是需要监视的最大的文件描述符值1
中间的fd_set* 参数是一个输入输出型参数表示我们要select给我们监视的文件描述符的集合一旦有文件就绪也会通过这个参数给我们传递哪个文件描述符就绪了。
参数timeout为结构timeval用来设置select()的等待时间。 NULL则表示select没有timeoutselect将一直被阻塞直到某个文件描述符上发生了事件。 0仅检测描述符集合的状态然后立即返回并不等待外部事件的发生也就是非阻塞 特定的时间值如果在指定的时间段里没有事件发生select将超时返回。
fd_set是一个位图结构比特位的位置表示那个fd比特位的数值表示是否需要关心这个fd或者这个fd是否就绪了。OS为我们提供了一些对位图进行的操作函数。
函数返回值 执行成功则返回文件描述词状态已改变的个数。 如果返回0代表在描述词状态改变前已超过timeout时间没有返回。 当有错误发生时则返回-1错误原因存于errno此时参数readfdswritefds, exceptfds和timeout的值变成不可预测。
select的缺点 可监控的文件描述符个数取决与sizeof(fd_set)的值. 将fd加入select监控集的同时还要再使用一个数据结构array保存放到select监控集中的fd。 每次调用select, 都需要手动设置fd集合, 从接口使用角度来说也非常不便。 每次调用select都需要把fd集合从用户态拷贝到内核态这个开销在fd很多时会很大。 同时每次调用select都需要在内核遍历传递进来的所有fd这个开销在fd很多时也很大。
poll
poll解决了select上限的问题并且把输入和输出参数进行分离不需要每一次都对参数进行重置。 pollfd结构中存在一个fd文件描述符events表示我们需要OS帮我们关心的事件revents表示每次响应是这个fd上的哪些事件就绪了。timeout表示poll函数的超时时间, 单位是毫秒(ms).fds是一个poll函数监听的结构列表需要我们自己维护传给系统nfds表示这个数组的长度。
events和revents的取值 返回值的含义和select是一样的。
poll的优点
可以等待多个文件描述符效率高。输入输出分离不需要频繁的对poll参数进行重置了。poll关心的fd没有上限。
poll的缺点
用户到内核是要有数据拷贝的poll在内核和应用层都是需要遍历看fd是否关心或者是否就绪的影响效率。
epoll
epoll的原理 在使用epoll时我们需要现在内核中创建一个epoll模型这个模型中存在一颗红黑树这颗红黑树维护了我们需要关心的文件描述符的各种时间所以系统给我们提供了对红黑树增删改的接口poll和select在内核中都是需要遍历来确定fd是否就绪的因为他们要检测的是struct file但是epoll不一样当有数据需要发送或者到来时硬件会发生硬件中断此时硬件会提醒epoll我们在创建epoll后在红黑树中添加或者修改fd会在内核和硬件中间驱动层为每个fd都设置对应的回调方法所以当硬件就绪时就会执行这个回调方法在epoll模型中还存在一个就绪队列这个回调方法就会把红黑树对应fd的事件同时链入到就绪队列当上层意识有fd就绪时直接就去就绪队列中取就可以了。因此凡是在就绪队列中的节点一定是就绪的一定是用户关心的。这些操作都是OS完成的我们不需要关心。 接下来介绍一下接口 epoll_create 自从linux2.6.8之后size参数是被忽略的它的作用是帮助我们创建一个epoll模型包括红黑树和就绪队列然后创建好epoll模型之后为我们返回一个fd标识这个epoll模型因为OS中可能会存在很多的epoll模型它就一定要这么多的epoll模型进行管理而我们用户层主要就是创建好之后通过返回的fd进行访问对应的epoll模型的。 epoll_ctl epoll的事件注册函数主要是对底层的红黑树进行操作第一个参数是epoll_create()的返回值第二个参数表示动作用三个宏来表示第三个参数是需要监听的fd第四个参数是告诉内核需要监听什么事件。 第二个参数的取值 EPOLL_CTL_ADD 注册新的fd到epfd中。 EPOLL_CTL_MOD 修改已经注册的fd的监听事件。 EPOLL_CTL_DEL 从epfd中删除一个fd。 分别对应的也就是对红黑树数据结构的增改删。 struct epoll_event结构 events就表示要监听的事件可以是以下几个宏的集合 data中的fd一般是设置给我们自己看的在内核给我们返回好的事件的时候我们可以通过这个参数知道哪个fd就绪了。 epoll_wait 第一个参数依然是我们创建的epoll模型对应的表示要在那个epoll模型上等epoll将会把发生的事件赋值到events数组中 (events不可以是空指针内核只负责把数据复制到这个events数组中不会去帮助我们在用户态中分配内存)maxevents告之内核这个events有多大这个 maxevents的值不能大于创建epoll_create()时的sizetimeout和返回值和poll和select是一模一样的。我们不需要担心就绪队列太多但是我们给的数组太少怎么办epoll会把我们数组先给满然后我们进行处理完后epoll再次等待是会立刻返回接着给我们响应。队列对保留已经发生的事件方便我们下一次读取。 epoll_wait检测事件就绪的时间复杂度是O(1)的因为只需要判断就绪队列是否为空就可以了获取所有事件的时间复杂度是O(N)的这个是无法避免的并且epoll可以保证数组的每一次遍历都是有效遍历和select和poll不一样他们还需要判断自己维护的所有fd是否就绪了而epoll拿到的就一定是就绪的。没有多余的遍历动作。
总结一下, epoll的使用过程就是三部曲: 调用epoll_create创建一个epoll模型; 调用epoll_ctl, 将要监控的文件描述符进行注册; 调用epoll_wait, 等待文件描述符就绪;
优点 接口使用方便: 虽然拆分成了三个函数, 但是反而使用起来更方便高效. 不需要每次循环都设置关注的文件描述符, 也做到了输入输出参数分离开。 数据拷贝轻量: 只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中, 这个操作并不频繁(而select/poll都是每次循环都要进行拷贝). 事件回调机制: 避免使用遍历, 而是使用回调函数的方式, 将就绪的文件描述符结构加入到就绪队列中,epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪. 这个操作时间复杂度O(1). 即使文件描述符数目很多, 效率也不会受到影响. 同样没有文件描述符数量的限制。
epoll的工作模式
epoll有2种工作方式-水平触发(LT)和边缘触发(ET) 水平触发Level Triggered 工作模式 当epoll检测到socket上事件就绪的时候, 可以不立刻进行处理. 或者只处理一部分那么下一次wait时就要立刻返回直到缓冲区上所有的数据都被处理完, epoll_wait 才不会立刻返回所以LT模式下阻塞读取和非阻塞读取都是没有问题的。 边缘触发Edge Triggered工作模式 当epoll检测到socket上事件就绪时, 必须立刻处理但是在ET模式下如果上一次没有处理完那么下一次在wait时就不会立即返回了只有当再次有新的事件就绪时才会返回那么下一次处理老的没处理完的数据新的数据就要等下一次处理正是这种机制倒逼这我们程序员如果时间就绪取数据的话就要一次取完因此就需要对对应的fd设置非阻塞读取epoll_wait 返回的次数少了很多有因为我们每次都把就绪的数据读完了所以也就让tcp发送给对方的窗口大小变大了从而从概率上提高了通信效率。所以ET策略是相对比较高效的。
select和poll其实也是工作在LT模式下. epoll既可以支持LT, 也可以支持ET.
在多路转接中读事件大部分情况都是阻塞的所以对于读事件一开始一般都直接关心但是对于写事件因为一开始发送缓冲区就是空的就是可以直接写的所以对于事件一般都是直接发直到发送缓冲区满了发送条件不具备才会把fd对应的写事件交给OS。