Epoll多路I/O复用技术

来源:互联网 发布:犀牛软件模型立面 编辑:程序博客网 时间:2024/06/08 05:53

Epoll多路I/O复用技术

通常学习一个新的linux技术,我们应该看看man手册对其定义。

NAME   epoll - I/O event notification facilitySYNOPSIS   #include <sys/epoll.h>DESCRIPTION   The  epoll  API performs a similar task to poll(2): monitoring multiple file descriptors to see if I/O is possible on any of them.     The epoll API can be used either as an edge-triggered or a level-triggered interface and scales well to large  numbers  of  watched  file descriptors. 

那么从man手册这段文字我们可以看出,epoll它是由linux另一套的并发处理方案poll演变过来的,它与poll相类似:能够监控多个文件描述符的I/O变化。在Linux中,一切皆文件(有部分不是)所以,任何一个连接,也有一个文件描述符(一般为int类型)来存放


重点:epoll比poll的优点:支持水平触发(level-triggered)边沿触发(edge-triggered)两种方案

Edge Triggered (ET) 边缘触发只有数据到来才触发,不管缓存区中是否还有数据。Level Triggered (LT) 水平触发只要有数据都会触发。

epoll所需的API函数:

  • epoll_create(2) creates an epoll instance and returns a file descriptor referring to that instance. (The more recent epoll_create1(2) extends the functionality of epoll_create(2).)

  • Interest in particular file descriptors is then registered via epoll_ctl(2). The set of file descriptors currently registered on an epoll instance is sometimes called an epoll set.

  • epoll_wait(2) waits for I/O events, blocking the calling thread if no events are currently available.


1、创建epoll文件描述符

创建一个epoll句柄,参数size用来告诉内核监听的文件描述符的个数,跟内存大小有关。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

int epoll_create(int size);      //size:监听数目

2、管理epoll中的文件描述符集合,增加、修改、删除

epoll的事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。

第一个参数是epoll_create()的返回值
第二个参数表示动作,用三个宏来表示

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

第三个参数是需要监听的fd
第四个参数是告诉内核需要监听什么事

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *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 */} __EPOLL_PACKED;

3、收集在epoll监控的事件中已经发送的事件(默认阻塞等待)

int epoll_wait(int epfd, struct epoll_event *events,                int maxevents, int timeout);/*    events:用来从内核得到事件的集合,    maxevents:告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,    timeout:是超时时间    -1:阻塞    0:立即返回,非阻塞    >0:指定微秒    返回值:成功返回有多少文件描述符就绪,时间到时返回0,出错返回-1*/

epoll工作原理

1、epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。

2、另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。

epoll 服务端例子

#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/socket.h>#include <arpa/inet.h>#include <sys/wait.h>#include <sys/epoll.h>#include <fcntl.h>#include <unistd.h>#include <errno.h>int main(){    int sock_server = socket(AF_INET,SOCK_STREAM,0);    struct sockaddr_in addr;    addr.sin_family = AF_INET;    addr.sin_port = htons(10099);    addr.sin_addr.s_addr = 0;    int ret = bind(sock_server,(struct sockaddr*)&addr,sizeof(addr));    listen(sock_server,5);    int epollfd = epoll_create(2);    struct epoll_event ev;    ev.events = EPOLLIN;    ev.data.fd = sock_server;    //吧sock_server加入eopll集合中    epoll_ctl(epollfd,EPOLL_CTL_ADD,sock_server,&ev);    while(1)    {        struct epoll_event outev[8];        int ret = epoll_wait(epollfd,outev,8,1000);        if (ret < 0)        {            if (errno == EINTR)//若被信号打断,则重新循环                continue;            break;        }        if (ret > 0)//有被唤醒的文件描述符        {            for(int i = 0 ; i<ret; i++)            {                int fd = outev[i].data.fd;                if (fd == sock_server)                {                //若为socket的文件描述符,则用accept进行三次握手,建立连接                    int newfd = accept(fd,NULL,NULL);                //EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭)                    ev.events = EPOLLIN;                    ev.data.fd = newfd;                //把新的fd加到epollfd中,继续等待下一次唤醒                                       epoll_ctl(epollfd,EPOLL_CTL_ADD,newfd,&ev);                }                else                {                //若不是socketfd,则为已经建立的连接,可以直接读取数据                    char buf[1024];                    int readlen = read(fd,buf,sizeof(buf));                    if (readlen<=0)                    {                    //read<=0,证明已经没有数据,或者出错,关闭fd                        close(fd);                    }                    else                    {                        printf("read data is :%s\n",buf);                    }                }            }        }    }    return 0;}

测试epoll的客户端

#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/socket.h>#include <arpa/inet.h>#include <sys/wait.h>#include <sys/epoll.h>#include <fcntl.h>#include <unistd.h>#include <errno.h>int main(){    int fd = socket(AF_INET,SOCK_STREAM,0);    struct sockaddr_in addr;    addr.sin_family = AF_INET;    addr.sin_port = htons(10099);    addr.sin_addr.s_addr = inet_addr("127.0.0.1");    int ret = connect(fd , (struct sockaddr*)&addr, sizeof(addr));    write(fd,"hello server",sizeof("hello server"));    char buf[1024];    read(fd,buf,sizeof(buf));    printf("server:%s\n",buf);    close(fd);    return 0;}

测试结果我就暂时不贴图了,希望这次博客能给大家带来一点收获!

2 0
原创粉丝点击