epoll的提高--工作模式

  • 水平触发模式 — 默认就是这种模式(如上一篇所写)
  • 边沿阻塞触发模式
  • 边沿非阻塞工作模式 — 效率最高
先来个需求吧:

针对一个客户端(进程间管道通信)对应一个服务器来说
如果客户端发送的信息有100字节, 而服务器每次接收只接收50字节, 那么剩下的50字节怎么处理?

分析:
  1. 默认执行流程: 对应的缓冲区存放了发送来的100字节,系统epoll监听到了对应的文件描述符的变化, 此时服务端去读数据, 但是只读了50字节, 那么缓冲区中就还留有50字节
    此时有两种说法: 事实是第二种
    1.系统为提高效率, 不会再去调用epoll_wait函数, 那么50字节数据就只能等下次客户端发送信息的时候接收(为了提高效率, 减少该函数的调用次数)
    2.系统会再次调用epoll_wait函数, 将数据读出来.(后面会附带上代码)
  2. 边沿阻塞触发模式: 会想上述的第1种情况那样, 但是会导致缓冲区里每次都残留数据, 并且越来越多…
  3. 边沿非阻塞(O_NONBLOCK)触发模式: 该效率最高, 主要是因为将客户端对应的那个文件描述符即缓冲区(管道)设置成非阻塞模式, 此时接受(读取)信息的时候就需要循环去读取, 当read/recv返回值为0时表示读取完毕.再加上边沿模式(只调用一次epoll_wait函数)所以效率高
    设置非阻塞:
    1.open的时候设置参数;
    2.fcntl设置
//文件打开之后修改文件属性 先获胜设置的属性 flags
//获取flags:
 int flags = fcntl(fd, F_GETFL); 
//设置flags:
flags = flags | O_NONBLOCK;
fcntl(fd, F_SETFL, flags);

对应的三种模式的代码:

  1. 利用管道-父子进程之间通信实现前两种模式:
    切换在代码中注释的地方, 输出的格式如上文描述那样, 这里模拟的是10字节和5字节
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <error.h>
int main(int argc, char *argv[]) {
    char buf[10];

    //使用管道实现,管道创建需要一个数组,存放的一个对应读,一个对应写
    int pfd[2];
    //创建匿名管道
    pipe(pfd);

    //创建子进程
    pid_t pid = fork();

    if(pid == 0) { //子进程
        //不需要读操作,关闭读的文件描述符,确保管道单项传输数据
        close(pfd[0]);
        while(1) {
            int i = 0;
            for(i = 0; i < 10/2; i++) {
                buf[i] = 'a';
            }
            buf[i-1] = '\n';
            for(; i < 10; i++) {
                buf[i] = 'b';
            }
            buf[i-1] = '\n';
            //此时数组中存放的是aaaa\nbbbb\n
            //一次发送10字节
            write(pfd[1], buf, sizeof(buf));
            sleep(3);
        }
        close(pfd[1]);
    } else if(pid > 0) {//父进程

        close(pfd[1]);
        //创建epoll模型,指向根节点,句柄
        int efd = epoll_create(10);
        //将要监听的挂载到根节点上
        struct epoll_event event;
        //设置边沿触发如下:
        event.events = EPOLLIN | EPOLLET;
        /*
         * //默认就是水平触发
         * event.events = EPOLLIN;
         */
        event.data.fd = pfd[0];//写端
        epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event);

        struct epoll_event resevents[10];
        char readbuf[5];
        while(1) {
            int res = epoll_wait(efd, resevents, 10, -1);
            printf("res:%d\n", res);
            if(resevents[0].data.fd == pfd[0]) {
                int len = read(pfd[0], readbuf, 5);//一次读5字节
                write(STDOUT_FILENO, readbuf, len);
            }
        }
        close(pfd[0]);
        close(efd);
    } else {
        perror("fork error");
        exit(1);
    }
    return 0;
}
  1. 利用c/s模型实现的边沿阻塞触发, 这里做的是只对应一个客户端进行监听
#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 9000
int main(void) {
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, connfd;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];

    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;

    printf("Accepting connections ...\n");

    cliaddr_len = sizeof(cliaddr);
    connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
    printf("received from %s at PORT %d\n",
            inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
            ntohs(cliaddr.sin_port));

    // 创建红黑树根节点
    int efd = epoll_create(10);
    // 检测的事件设置
#if 0
    /* ET 边沿触发 */
    event.events = EPOLLIN | EPOLLET;     
#else
    /* 默认 LT 水平触发 */
    event.events = EPOLLIN;                 
#endif
    // 需要检测的文件描述符
    event.data.fd = connfd;
    epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);

    while (1) {
        res = epoll_wait(efd, resevent, 10, -1);

        printf("========res %d\n", res);
        if (resevent[0].data.fd == connfd) {
            len = read(connfd, buf, MAXLINE/2);        
            write(STDOUT_FILENO, buf, len);
        }
    }
    return 0;
}
  1. 同2一样只对客户端进行监听, 不过这里要注意的是不能再根据read的返回值去判断是客户端断开了连接还是读取失败还是读到的内容, 需要另外的思路去设计程序, 比如利用data里面的void *指针来做, 存放一个时间, 如果长时间没有联系, 则断开连接…或者利用心跳包
#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 8000
int main(void) {
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, connfd;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];

    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 efd = epoll_create(10);

    //event.events = EPOLLIN;
    printf("Accepting connections ...\n");
    cliaddr_len = sizeof(cliaddr);
    connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
    printf("received from %s at PORT %d\n",
            inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
            ntohs(cliaddr.sin_port));

    /* 修改connfd为非阻塞读 */
    int flag = fcntl(connfd, F_GETFL);          
    flag |= O_NONBLOCK;
    fcntl(connfd, F_SETFL, flag);

    /* ET 边沿触发,默认是水平触发 */
    event.events = EPOLLIN | EPOLLET;     
    event.data.fd = connfd;
    //将connfd加入监听红黑树
    epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);      

    while (1) {
        int len = 0;
        printf("epoll_wait begin\n");
        //最多10个, 阻塞监听
        int res = epoll_wait(efd, resevent, 10, -1);        
        printf("epoll_wait end res %d\n", res);

        if (resevent[0].data.fd == connfd) {
            // 非阻塞读, 轮询
            // epoll_wait 触发一次,剩余数据循环读取
            while ((len = read(connfd, buf, MAXLINE/2)) >0 ) {
                write(STDOUT_FILENO, buf, len);
            }
        }
    }
    return 0;
}
    原文作者:dab61956e53d
    原文地址: https://www.jianshu.com/p/59ab5ecbaa76
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞