当前位置:   article > 正文

Linux开发(五):I/O多路复用_开发 多路

开发 多路

一、什么是I/O多路复用

多路是指网络连接,复用指的是同一个线程。

  • IO 多路复用是一种同步IO模型,实现一个线程可以监视多个文件句柄;
  • 一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;
  • 没有文件句柄就绪就会阻塞应用程序,交出CPU。

二、IO多路复用的三种实现

1、select()函数

select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有描述符就绪(有数据可读、可写、或者有异常),或者超时(timeout指定等待时间,如果想要立即返回,设为NULL即可)。当select函数返回后,可以通过遍历fdset,来找到就绪的描述符。但有以下几点需要注意:

  1. 一个描述符阻塞与否不影响select是否阻塞。
  2. 如果在一个描述符上遇到了文件末尾,select认为它是可读的。也就是说,到达文件末尾时,select并不会给一个异常提示
  3. 如果调用成功,此时select()的所有参数都会被重置为0,所以需要重新赋值

select()有个很好的优点,那就是实现简单,良好的跨平台支持。但缺点也很明显:

  • 单个进程所打开的FD是有限制的,默认1024 ,具体可查看 /proc/sys/fs/file-max 中的FD_SETSIZE值

  • 对socket进行的是线性扫描,即采用轮询的方法,效率较低。 

  • 每次调用 select,都需要把 fd 集合从用户态拷贝到内核态,复制开销大。 

  1. #include <sys/select.h>
  2. int select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);

函数参数:

maxfdp:一个整数值,用以指示集合中所有文件描述符的范围,即所有文件描述符的最大值加1

readset:fd_set结构的指针,指示要监视这些文件描述符的是否可读,可以传入NULL值,表示不关心任何文件的读变化。 

writeset:fd_set结构的指针,指示要监视这些文件描述符的是否可写,可以传入NULL值,表示不关心任何文件的写变化。 

exceptset:fd_set结构的指针,指示要监视这些文件描述符的是否有异常,可以传入NULL值,表示不关心任何文件是否有异常。 

timeout:指定select的超时时间,它有三种选择:

  • 若将NULL以形参传入,select将一直处于阻塞状态,直到监视文件描述符集合中某个文件描述符发生变化为止
  • 若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;
  • timeout值大于0,这就是等待的超时时间
  1. struct timeval{
  2. long tv_sec; /*秒 */
  3. long tv_usec; /*微秒 */
  4. }

返回值:

正常则返回就绪的文件描述符的个数,超时返回0,出错则返回-1

fd_set类型的参数可以通过以下函数进行操作:

  1. // 将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空,由于在系统分配内存空间后,通常并不作清空处理,所以结果是不可知的。
  2. FD_ZERO(fd_set *fdset);
  3. // 用于在文件描述符集合中增加一个新的文件描述符。
  4. FD_SET(fd_set *fdset);
  5. // 用于在文件描述符集合中删除一个文件描述符。
  6. FD_CLR(fd_set *fdset);
  7. // 用于测试指定的文件描述符是否在该集合中
  8. FD_ISSET(int fd,fd_set *fdset);

2、poll()函数

poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制,因为它是基于链表来存储的。和 select 一样,文件描述符是否阻塞对 poll 是否阻塞没有任何影响。

 poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。 

  1. #include <poll.h>
  2. int poll ( struct pollfd * fds, unsigned int nfds, int timeout);

函数参数:

fds:存放需要检测其状态的socket描述符,且每次调用该函数后,系统不会清空这个数组,这点和select不同。

  1. struct pollfd {
  2. int fd; // 需要监视的文件描述符,用户指定
  3. short events; // 需要内核监视的事件,用户指定
  4. short revents; // 实际发生的事件,内核设置
  5. };

有效的事件有:

  •        POLLIN         有数据可读。
  •   POLLRDNORM       有普通数据可读。
  •   POLLRDBAND      有优先数据可读。
  •   POLLPRI        有紧迫数据可读。
  •   POLLOUT            写数据不会导致阻塞。
  •   POLLWRNORM      写普通数据不会导致阻塞。
  •   POLLWRBAND        写优先数据不会导致阻塞。
  •   POLLMSGSIGPOLL         消息可用。

此外,revents域中还可能返回下列事件,这些事件在events域中无意义,因为它们在合适的时候总是会从revents中返回:

  •        POLLER     指定的文件描述符发生错误。
  •   POLLHUP   指定的文件描述符挂起事件。
  •   POLLNVAL  指定的文件描述符非法。

nfds:标记数组fds中的结构体元素的总数量

timeou:poll函数调用阻塞的时间,单位:毫秒,这里有三种情况:

  1. timeout < 0 :这会造成 poll 永远等待。poll() 只有在一个描述符就绪时返回

  2. timeout == 0:这种情况下,测试所有的描述符,并且 poll() 立刻返回。这允许在 poll 中没有阻塞的情况下找出多个文件描述符的状态。

  3. time > 0:这将以毫秒为单位指定 timeout 的超时周期。poll() 只有在超时到期时返回0,除非一个描述符变为就绪

返回值:

成功时,poll()返回就绪的的文件描述符个数,如果超时,则返回0,失败返回-1

3、epoll()函数 

epoll()是为解决Linux内核处理大量文件描述符而提出的方案,属于select()/poll()的增强版本。其常应用于Linux下高并发服务型程序,特别是在大量并发连接中只有少部分连接处于活跃下的情况 下,能显著的提高程序的CPU利用率。
epoll采用的是事件驱动,在用户空间获取事件时,不需要去遍历被监听描述符集合中所有的文件描述符,而是遍历那些被内核I/O事件异步唤醒之后加入到就绪队列并返回到用户空间的描述符集合。
epoll提供了两种触发模式,水平触发(LT)和边沿触发(ET)。当然,涉及到I/O操作也必然会有阻塞和非阻塞两种方案。
 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/444026
推荐阅读
相关标签
  

闽ICP备14008679号