IO多路复用(select, epoll)

来源:互联网 发布:全国教师网络培训平台 编辑:程序博客网 时间:2024/06/04 17:42

在Linux系统编程中,会遇到一个进程处理多个IO问题,例如:

//本例想实现循环读取鼠标和键盘的输入int fd_mice = open("/dev/input/mice", O_RDONLY); //获取鼠标的文件描述符int fd_key = opeb("/dev/input/event1",O_RDONLY); //获取键盘的文件描述符char buf[1024];while(1){    if(read(fd_key,buf,sizeof(buf))>0) printf("keyboard event");    if(read(fd_mice,buf,sizeof(buf))>0) printf("mice event");}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

由于read为阻塞模式, 如果2个read 写在一个循环中, 当出现键盘阻塞时,无法读取鼠标信息. 
要解决问题可以在打开时设置非阻塞模式 open(“”, O_RDONLY | O_NONBLOCK); 但这样做极占CPU. 
所以要同时处理多路IO时, 我们用select, epoll来解决问题.

select

select()要引入头文件 sys/select.h,功能:“让程序能够监控文件描述符,直到文件描述符的I/O事件变成就绪状态,也就是文件描述符变成可读写。”

在看select前,要了解2个结构体类型:

fd_set是 sys/select.h 中定义的 文件描述符集合结构体

typedef struct{#ifdef __USE_XOPEN    __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];# define __FDS_BITS(set) ((set)->fds_bits)#else    __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];# define __FDS_BITS(set) ((set)->__fds_bits)#endif} fd_set;//原型里面一堆"_",其实都是long int的宏定义,结构体里面只有一个成员 fds_bits[],我们可以把它理解成一个 int数组.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

timeval是 sys/time.h 中定义的 时间结构体:

struct timeval {    long    tv_sec;         /* seconds */    long    tv_usec;        /* microseconds */};
  • 1
  • 2
  • 3
  • 4
  • 5

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set **exceptfds, struct timeval *timeout); 
返回值:就绪描述符的数目,超时返回0,出错返回-1

1.第一个参数: 最大的文件描述符+1

2.中间的三个参数readset、writeset和exceptset指定读、写和异常条件的描述字。如果对某一个的条件不感兴趣,可以设置NULL。fd_set定义的集合,可通过以下四个宏进行设置:

      void FD_ZERO(fd_set *fdset);           //清空集合      void FD_SET(int fd, fd_set *fdset);   //将一个给定的文件描述符加入集合之中      void FD_CLR(int fd, fd_set *fdset);   //将一个给定的文件描述符从集合中删除      int FD_ISSET(int fd, fd_set *fdset);   // 检查集合中指定的文件描述符是否可以读写 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

3.第三个参数是超时时间。

//本例用select解决循环读取鼠标和键盘的输入#include<stdio.h>#include<stdlib.h>#include<fcntl.h>#include<sys/stat.h>#include<sys/types.h>#include<sys/select.h>int main(){    int fd_mice = open("/dev/input/mice", O_RDONLY);    int fd_key = open("/dev/input/event1",O_RDONLY);    char buf[1024];    fd_set readfds;  //申请一个文件描述符集合    FD_ZERO(&readfds);  //清空集合    FD_SET(fd_mice,&readfds);  //把fd_mice添加到集合    FD_SET(fd_key,&readfds);   //把fd_key添加到集合    struct timeval timeout;    timeout.tv_sec = 5;    timeout.tv_usec = 5;    while(1)    {           int ret = select(1024, &readfds, NULL, NULL, &timeout);        if(ret==0) continue; //阻塞超时,无就绪        if(ret<0 && errno == EINTR) continue; //被信号打断        if(ret <0) break; //错误        if(FD_ISSET(fd_key, &readfds))  //位操作,判断fd_key事件是否在readfds集合中激活        {            read(fd_key, buf, sizeof(buf));            printf("keyboard event");        }        if(FD_ISSET(fd_mice, &readfds))  //位操作,判断fd_mice事件是否在readfds集合中激活        {            read(fd_mice, buf, sizeof(buf));            printf("keyboard event");        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

epoll

epoll分为LT水平触发模型和ET边沿触发模型 
LT模型类似于原来的select/poll操作,只要事件还没有处理完就会一直通知直到处理为止. 
ET模型当套接字“状态发生变化”时候触发一次通知.

epoll只有3个函数: 
int epoll_create(int size); 
返回值:返回一个epoll的epfd操作句柄,内部是个RB-Tree,占用一个fd,所以不用了需要close。 
参数:size用来告诉内核这个fd监听的数目一共有多大,在Linux 2.6.8后这个参数已经忽略了,传值时大于0即可。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 
返回值:成功返回0,失败返回-1,设置errno。 
参数:epfd: epoll_create的返回值,epoll句柄。 
   op: 操作宏,有EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL 
   fd : 要操作的fd 
   event:要监控的事件 
    
   struct epoll_event结构如下:

typedef union epoll_data {    void *ptr;    int fd;    __uint32_t u32;    __uint64_t u64;} epoll_data_t;struct epoll_event {    __uint32_t events; /* Epoll events */    epoll_data_t data; /* User data variable */};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); 
返回值:>0 就绪的fd数目,0 没有fd就绪, -1 错误并设置errno 
参数: epfd epoll_create的返回值 
    events 就绪events集合 
    maxevents 集合大小 
    timeout 超时时间

#include<stdio.h>#include<stdlib.h>#include<fcntl.h>#include<sys/stat.h>#include<sys/types.h>#include<sys/epoll.h>int main(){    int epollfd = epoll_create(512);    int fd_key = open("/dev/input/event1", O_RDONLY|O_NONBLOCK);    int fd_mice = open("/dev/input/mice", O_RDONLY|O_NONBLOCK);    struct epoll_event ev;    ev.events = EPOLLIN; // 监控可读    ev.data.fd = fd_key;     epoll_ctl(epollfd, EPOLL_CTL_ADD, fd_key,  &ev);    ev.data.fd = fd_mice;    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd_mice, &ev);    struct epoll_event evout[2];    char buf[1024];    while(1)    {        int ret = epoll_wait(epollfd, evout, 2, 5000);        if(ret == 0) continue; // 超时        if(ret < 0 && errno == EINTR) continue; // 被信号打断        if(ret < 0) break; // 错误发生了        // ret > 0情况        int i;        for(i=0; i<ret; ++i)        {            int fd = evout[i].data.fd;            if(read(fd, buf, sizeof(buf)) < 0)            {                // close自动将它从epoll中移除                close(fd);             }            if(fd == fd_key) printf("键盘有消息\n");            else if(fd == fd_mice) printf("鼠标有消息\n");        }    }}
原创粉丝点击