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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容