【系统编程】多路复用(select、poll、epoll)

I/O复用函数

  • 使用select()函数

    1、这个函数会一直阻塞等待并不停监测集合里的文件描述符是否发生变化,如果发生变化就返回,不再阻塞,并且会将集合里没有发生变化的文件描述符从集合里踢出去。

            缺点:

1 线程不安全,如果其他线程发现文件描述符没有在使用可能会回收 select()函数不允许,如果强制关闭,可能会发生未知错误。
2 监测的文件描述符有上限,那么如果要超过这个上线可能就需要开 启其他进程,又会有进程的问题。#define __FD_SETSIZE    1024
3 将文件描述符剔除,如果要一直监测就需要重新添加。
4 随着文件描述符数量的增多,开销随着增大。

#include <sys/select.h>

#include <sys/time.h>

函数: int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

nfds 添加到集合中的最大文件描述符+1
readfds 监测可读的文件描述符的集合
writefds 监测可写的文件描述符的集合
exceptfds 监测发生异常的文件描述符的集合
timeou

监测超时时间

1、NULL 永久等待

2、struct timeval 设置了超时时间,超过这个时间集 合里的文件描述符还是没有发生变化,select()函数将不再等待

            fd_set是一个文件描述符集合,可以通过以下宏来操作:

            FD_CLR(inr fd,fd_set* set):用来清除文件描述符集合set中相关fd的位
            FD_ISSET(int fd,fd_set *set):用来测试文件描述符集合set中相关fd的位是否为真
            FD_SET(int fd,fd_set*set):用来设置文件描述符集合set中相关fd的位
            FD_ZERO(fd_set *set):用来清除文件描述符集合set的全部位
 

     2、 将某个文件描述符从集合里清除      函数: void FD_CLR(int fd, fd_set *set);

fd 要清除的文件描述符
set 要从哪个集合清除文件描述符


     3、 将某个文件描述符添加到集合里   函数: void FD_SET(int fd, fd_set *set);

fd    要添加的文件描述符
set 要将文件描述符添加到哪个集合

   4、 清空集合里的文件描述符      函数: void FD_ZERO(fd_set *set);

set 要将哪个集合里的文件描述符清空

   5、 检测某个文件描述符是否在集合里    函数: int  FD_ISSET(int fd, fd_set *set);

fd 要检测的文件描述符
set  要检测的集合
返回值

返回1: 要检测的文件描述符在集合里

返回0: 要检测的文件描述符不在集合里

 

  • 使用poll()函数

  1. 跟select()差不多的效果(只是Poll选择了pollfd结构体来处理文件描述符的相关操作)

优点: 没有文件描述符个数的上限

缺点: 线程不安全

# include <poll.h>

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

fds

存储监听的文件描述符的结构体指针

struct pollfd

{

        int   fd;         /* 要监听的文件描述符*/

        short events;     /* 请求的事件*/

        short revents;    /* 返回的事件 */

 };

events取值:

     POLLIN 普通或优先级带数据可读

     POLLRDNORM 普通数据可读

     POLLRDBAND 优先级带数据可读

     POLLPRI 高优先级数据可读

     POLLOUT 普通数据可写

     POLLWRNORM 普通数据可写

     POLLWRBAND 优先级带数据可写

后面这三个只能作为描述字的返回结果存储在revents中, 而不能作为测试条件用于events中

     POLLERR 发生错误

     POLLHUP 发生挂起

     POLLNVAL 描述字不是一个打开的文件

nfds struct pollfd结构体数组的个数
timeout

设置等待超时时间

    INFTIM 永远等待

    0 立即返回,不阻塞进程

   >0 等待指定数目的毫秒数

返回值

1、>0 就绪描述符的个数

2、-1 发生错误

3、0 没有文件描述符就绪或者超时

       poll函数与select函数的最大不同之处在于:select函数有最大文件描述符的限制,一般1024个,而poll函数对文件描述符的数量没有限制。但select和poll函数都是通过轮询的方式来查询某个文件描述符状态是否发生了变化,并且需要将整个文件描述符集合在用户空间和内核空间之间来回拷贝,这样随着文件描述符的数量增加,相应的开销也随之增加。
 

      如何检测是哪个文件描述符发生了变化?

           If(revents & 事件) //代表监测的事件有发生变化

 

  • 使用epoll()函数

  • #include <sys/epoll.h>

  • epoll是在Linux内核2.6引进的,是select和poll函数的增强版。与select相比,epoll没有文件描述符数量的限制。epoll使用一个文件描述符管理多个文件描述符,将用户关心的文件描述符事件存放到内核的一个事件列表中,这样在用户空间和内核空间只需拷贝一次。

    1、创建句柄

函数: int epoll_create(int size);

参数: 1、size 支持的最大句柄数,在linux内核2.6.8后被忽略,但必须大 于0

 

      2、创建句柄

函数: int epoll_create1(int flags);

参数: 1、flags 0 则除了删除过时大小参数这一事实外, epoll_create1()与epoll_create()相同

                           EPOLL_CLOEXEC 在新文件描述符上设置close-on- exec(FD_CLOEXEC)标志,

                          请参阅 open(2)中O_CLOEXEC标志的描 述,了解这可能有用的原因。

              返回值: 成功         文件描述符

                             失败          -1

 

      3、添加、删除、修改文件描述符的情况

函数: int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

参数: 1、epfd epoll_create()的返回值

            2、op 动作

                 EPOLL_CTL_ADD:注册新的fd到epfd中;
                 EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
                 EPOLL_CTL_DEL:从epfd中删除一个fd;

            3、events 事件结构体

         typedef union epoll_data {

             void        *ptr; //如果需要发送数据

             int          fd; //哪个文件描述符变化了

             uint32_t     u32;

             uint64_t     u64;

         } epoll_data_t;

 

         struct epoll_event {

             uint32_t     events;      /* 事件 */

             epoll_data_t data;        /* 数据 */

            };

events可以是以下几个宏的集合:
            EPOLLIN : 表示对应的文件描述符可以读(包括对端 SOCKET正常关闭);
            EPOLLOUT: 表示对应的文件描述符可以写;
            EPOLLPRI: 表示对应的文件描述符有紧急的数据可读(这 里应该表示有带外数据到来);
            EPOLLERR: 表示对应的文件描述符发生错误;
            EPOLLHUP: 表示对应的文件描述符被挂断;
            EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模 式,这是相对于水平触发(Level Triggered)来说的。
            EPOLLONESHOT:只监听一次事件,当监听完这次事件之 后,如果还需要继续监听这个socket的话, 需要再次把这个socket加入到EPOLL队列里

如何判断是否发生了监测的事件?

      If(events[i].events&EPOLLOUT)

 

      4、监测等待

函数: int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

参数: 1、epfd epoll_create()的返回值

            2、events 事件结构体

            3、maxevents 告诉内核events的大小

            4、timeout 设置超时时间,毫秒为单位

                    正数 : 超时时间

                   0 : 马上返回

                  -1 : 一直等待

          返回值:1、>0 就绪的监测的文件描述符个数

                        2、0 文件描述符就绪但超时

                        3、-1 发生错误

 

    5、关于ET、LT两种工作模式:
         可以得出这样的结论:
         ET模式仅当状态发生变化的时候才获得通知,这里所谓的状态的变化并不包括缓 冲区中还有未处理的数据,也就是说,如果要采用ET模式,需要一直read/write直到 出错为止,很多人反映为什么采用ET模式只接收了一部分数据就再也得不到通知 了,大多因为这样;而LT模式是只要有数据没有处理就会一直通知下去的。

epoll对文件描述符的操作由两种模式:水平触发LT(level trigger)和边沿触发ET(edge trigger)。默认的情况下为LT模式。LT模式与ET模式的区别在于:

LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。
ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

参考博客:

[1]https://blog.csdn.net/chewbee/article/details/78108223

 

发布了64 篇原创文章 · 获赞 82 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_40602000/article/details/101123139