zoukankan      html  css  js  c++  java
  • epoll事件模型

    事件模型

    EPOLL事件有两种模型:

    Edge Triggered (ET) 边缘触发只有数据到来才触发,不管缓存区中是否还有数据。

    Level Triggered (LT) 水平触发只要有数据都会触发。

    思考如下步骤:

    1. 假定我们已经把一个用来从管道中读取数据的文件描述符(RFD)添加到epoll描述符。
    2. 管道的另一端写入了2KB的数据
    3. 调用epoll_wait,并且它会返回RFD,说明它已经准备好读取操作
    4. 读取1KB的数据
    5. 调用epoll_wait……

    在这个过程中,有两种工作模式:

    ET模式

    ET模式即Edge Triggered工作模式。

    如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口,在后面会介绍避免可能的缺陷。

    1)      基于非阻塞文件句柄

    2)      只有当read或者write返回EAGAIN(非阻塞读,暂时无数据)时才需要挂起、等待。但这并不是说每次read时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成,当read返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。

    LT模式

    LT模式即Level Triggered工作模式。

    与ET模式不同的是,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll,无论后面的数据是否被使用。

    LT(level triggered):LT是缺省的工作方式,并且同时支持block和no-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。

    ET(edge-triggered):ET是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知。请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once).

    实例一:

    基于管道epoll ET触发模式

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/epoll.h>
    #include <errno.h>
    #include <unistd.h>
    
    #define MAXLINE 10
    
    int main(int argc, char *argv[])
    {
        int efd, i;
        int pfd[2];
        pid_t pid;
        char buf[MAXLINE], ch = 'a';
    
        pipe(pfd);
        pid = fork();
        if (pid == 0) {
            close(pfd[0]);
            while (1) {
                for (i = 0; i < MAXLINE/2; i++)
                    buf[i] = ch;
                buf[i-1] = '
    ';
                ch++;
    
                for (; i < MAXLINE; i++)
                    buf[i] = ch;
                buf[i-1] = '
    ';
                ch++;
    
                write(pfd[1], buf, sizeof(buf));
                sleep(2);
            }
            close(pfd[1]);
        } else if (pid > 0) {
            struct epoll_event event;
            struct epoll_event resevent[10];
            int res, len;
            close(pfd[1]);
    
            efd = epoll_create(10);
            /* event.events = EPOLLIN; */
            event.events = EPOLLIN | EPOLLET;        /* ET 边沿触发 ,默认是水平触发 */
            event.data.fd = pfd[0];
        epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event);
    
            while (1) {
                res = epoll_wait(efd, resevent, 10, -1);
                printf("res %d
    ", res);
                if (resevent[0].data.fd == pfd[0]) {
                    len = read(pfd[0], buf, MAXLINE/2);
                    write(STDOUT_FILENO, buf, len);
                }
            }
            close(pfd[0]);
            close(efd);
        } else {
            perror("fork");
            exit(-1);
        }
        return 0;
    }

    水平触发:

    运行结果:

    ubuntu1604@ubuntu:~/wangqinghe/linux/20190827$ ./et

    res 1

    aaaa

    res 1

    bbbb

    res 1

    cccc

    res 1

    dddd

    res 1

    eeee

    res 1

    ffff

    ^C

    实例二:

    基于网络C/S模型的epoll ET触发模式

    server

    /* server.c */
    #include <stdio.h>
    #include <string.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <signal.h>
    #include <sys/wait.h>
    #include <sys/types.h>
    #include <sys/epoll.h>
    #include <unistd.h>
    
    #define MAXLINE 10
    #define SERV_PORT 8080
    
    int main(void)
    {
        struct sockaddr_in servaddr, cliaddr;
        socklen_t cliaddr_len;
        int listenfd, connfd;
        char buf[MAXLINE];
        char str[INET_ADDRSTRLEN];
        int i, efd;
    
        listenfd = socket(AF_INET, SOCK_STREAM, 0);
    
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port = htons(SERV_PORT);
    
        bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    
        listen(listenfd, 20);
    
        struct epoll_event event;
        struct epoll_event resevent[10];
        int res, len;
        efd = epoll_create(10);
        event.events = EPOLLIN | EPOLLET;        /* ET 边沿触发 ,默认是水平触发 */
    
        printf("Accepting connections ...
    ");
        cliaddr_len = sizeof(cliaddr);
        connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
        printf("received from %s at PORT %d
    ",
                inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
                ntohs(cliaddr.sin_port));
    
        event.data.fd = connfd;
        epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);
    
        while (1) {
            res = epoll_wait(efd, resevent, 10, -1);
            printf("res %d
    ", res);
            if (resevent[0].data.fd == connfd) {
                len = read(connfd, buf, MAXLINE/2);
                write(STDOUT_FILENO, buf, len);
            }
        }
        return 0;
    }

    client

    /* client.c */
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <netinet/in.h>
    
    #define MAXLINE 10
    #define SERV_PORT 8080
    
    int main(int argc, char *argv[])
    {
        struct sockaddr_in servaddr;
        char buf[MAXLINE];
        int sockfd, i;
        char ch = 'a';
    
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
        servaddr.sin_port = htons(SERV_PORT);
    
        connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    
        while (1) {
            for (i = 0; i < MAXLINE/2; i++)
                buf[i] = ch;
            buf[i-1] = '
    ';
            ch++;
    
            for (; i < MAXLINE; i++)
                buf[i] = ch;
            buf[i-1] = '
    ';
            ch++;
    
            write(sockfd, buf, sizeof(buf));
            sleep(10);
        }
        Close(sockfd);
        return 0;
    }

    边沿触发:

    运行结果:

    ubuntu1604@ubuntu:~/wangqinghe/linux/20190827$ ./et

    res 1

    aaaa

    res 1

    bbbb

    res 1

    cccc

    ^C

    实例三:

    基于网络C/S非阻塞模型的epoll ET触发模式

    server

    /* server.c */
    #include <stdio.h>
    #include <string.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <sys/wait.h>
    #include <sys/types.h>
    #include <sys/epoll.h>
    #include <unistd.h>
    #include <fcntl.h>
    
    #define MAXLINE 10
    #define SERV_PORT 8080
    
    int main(void)
    {
        struct sockaddr_in servaddr, cliaddr;
        socklen_t cliaddr_len;
        int listenfd, connfd;
        char buf[MAXLINE];
        char str[INET_ADDRSTRLEN];
        int i, efd, flag;
    
        listenfd = socket(AF_INET, SOCK_STREAM, 0);
    
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port = htons(SERV_PORT);
    
        bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    
        listen(listenfd, 20);
    
        struct epoll_event event;
        struct epoll_event resevent[10];
        int res, len;
        efd = epoll_create(10);
        /* event.events = EPOLLIN; */
        event.events = EPOLLIN | EPOLLET;        /* ET 边沿触发 ,默认是水平触发 */
    
        printf("Accepting connections ...
    ");
        cliaddr_len = sizeof(cliaddr);
        connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
        printf("received from %s at PORT %d
    ",
                inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
                ntohs(cliaddr.sin_port));
    
        flag = fcntl(connfd, F_GETFL);
        flag |= O_NONBLOCK;
        fcntl(connfd, F_SETFL, flag);
        event.data.fd = connfd;
        epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);
    
        while (1) {
            printf("epoll_wait begin
    ");
            res = epoll_wait(efd, resevent, 10, -1);
            printf("epoll_wait end res %d
    ", res);
    
            if (resevent[0].data.fd == connfd) {
                while ((len = read(connfd, buf, MAXLINE/2)) > 0)
                    write(STDOUT_FILENO, buf, len);
            }
        }
        return 0;
    }

    client

    /* client.c */
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <netinet/in.h>
    
    #define MAXLINE 10
    #define SERV_PORT 8080
    
    int main(int argc, char *argv[])
    {
        struct sockaddr_in servaddr;
        char buf[MAXLINE];
        int sockfd, i;
        char ch = 'a';
    
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
        servaddr.sin_port = htons(SERV_PORT);
    
        connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    
        while (1) {
            for (i = 0; i < MAXLINE/2; i++)
                buf[i] = ch;
            buf[i-1] = '
    ';
            ch++;
    
            for (; i < MAXLINE; i++)
                buf[i] = ch;
            buf[i-1] = '
    ';
            ch++;
    
            write(sockfd, buf, sizeof(buf));
            sleep(10);
        }
        Close(sockfd);
        return 0;
    }
  • 相关阅读:
    公安的方案特点。
    12345
    js发送邮件 不会调用客户端。
    jsonp 跨域 jsonp 发音
    RESTful restful api Representational State Transfer
    Autofac 组件、服务、自动装配 《第二篇》
    Fiddler关闭自动更新
    武大樱花抢票软件使用说明
    Visual Studio实用小技巧
    C#6.0语法特性
  • 原文地址:https://www.cnblogs.com/wanghao-boke/p/11425936.html
Copyright © 2011-2022 走看看