Linux C++ 网络编程学习系列(5)——多路IO之epoll边沿触发

多路IO之epoll边沿触发+非阻塞

  1. 源码地址:https://github.com/whuwzp/linuxc/tree/master/epoll_ET_LT_NOBLOCK_example
  2. 源码说明:
    • server.cpp: 监听127.1:6666,功能是将收到的数据打印到屏幕

1. 概要

这里没有用socket文件描述符,而是用了管道,关于管道的知识可以参考: https://www.bilibili.com/video/av41308301?p=13。

//设置边沿触发
evt.data.fd = pfd[0];
evt.events  = EPOLLIN | EPOLLET; //边沿
// evt.events  = EPOLLIN;                   //水平(默认)
epoll_ctl(epfd, EPOLL_CTL_ADD, pfd[0], &evt); 

//设置非阻塞
int flag = 0;
flag     = fcntl(pfd[0], F_GETFL);
flag |= O_NONBLOCK;  //设置为非阻塞
fcntl(pfd[0], F_SETFL, flag);

//后面也有直接用以下设置的方法
fcntl(pfd[0], F_SETFL, O_NONBLOCK);

1.1 边沿触发和水平触发

假设一个情景: 接收了100字节的数据到fd的接收缓冲区, epoll_wait返回,说fd有数据,于是开始处理,但是只从缓冲区中read了10字节(所以缓冲区中还有90字节), 那么下一次调用epoll_wait时(假设fd没有新的数据到),是否还要返回fd有信号?

  • 边沿触发: 不返回了, 只有新数据来才返回
  • 水平触发: 仍然返回, 直接缓冲区还有数据就返回

1.2 边沿触发的优势缺点

优势:

  1. 不会像水平触发傻瓜式一直触发epoll_wait返回
  2. 边沿触发自定义更方便,有新数据了告诉我,至于我是否处理了怎么处理都不用管,当然这种难度相对大

缺点: 万一没有处理完数据,就会出问题了

1.3 边沿触发缺点的改进方法

int flag = 0;
        flag     = fcntl(pfd[0], F_GETFL);
        flag |= O_NONBLOCK;  //设置为非阻塞
        fcntl(pfd[0], F_SETFL, flag);

while ((len = (int)read(pfd[0], buf, 3)) > 0) { //体现非阻塞
    write(STDOUT_FILENO, buf, (size_t)len);
}

while read>0确保read完所有数据, 因为在非阻塞模式下,如果缓冲区中没有数据了read也会返回, 关于read在非阻塞的返回值可以搜索.

ssize_t Read(int fildes, void *buf, size_t nbyte) {
readagain:
    ssize_t ret = read(fildes, buf, nbyte);
    if (ret == -1) {
        if (errno == EINTR) {
            goto readagain;
        } else if (errno == EWOULDBLOCK || errno == EAGAIN) { //在非阻塞模式下,这个是正常的,表示没数据可读了
            return ret;
        } else {
            perror_exit("read failed");
        }
    }
    return ret;
}

2. 核心代码

这是用另一种文件描述符证明.

#include "include/wrap.h"
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
    int  pfd[2];
    int  pid     = 0;
    char buf[3]  = {0};
    char buf1[3] = {0};
    int  len     = 0;

    pipe(pfd);
    pid = fork();

    if (pid == -1) { // error
        perror_exit("fork failed");
    }

    if (pid == 0) { // child
        close(pfd[0]);
        int j = 0;
        while (true) {
            sprintf(buf1, "%d
", j++);
            write(pfd[1], buf1, 3);
            sprintf(buf1, "%d
", j++);
            write(pfd[1], buf1, 3);
            sleep(3);
        }
        close(pfd[1]);
    } else { // parent
        close(pfd[1]);
        int                epfd = 0;
        struct epoll_event evts[1];
        struct epoll_event evt;

        int flag = 0;
        flag     = fcntl(pfd[0], F_GETFL);
        flag |= O_NONBLOCK;  //设置为非阻塞
        fcntl(pfd[0], F_SETFL, flag);

        epfd        = epoll_create(1);
        evt.data.fd = pfd[0];
        evt.events  = EPOLLIN | EPOLLET; //边沿
        // evt.events  = EPOLLIN;                   //水平(默认)
        epoll_ctl(epfd, EPOLL_CTL_ADD, pfd[0], &evt);
        while (true) {
            epoll_wait(epfd, evts, 1, -1);
            if (evts[0].data.fd == pfd[0]) {
                while ((len = (int)read(pfd[0], buf, 3)) > 0) { //体现非阻塞
                    write(STDOUT_FILENO, buf, (size_t)len);
                }
            }
        }
        close(pfd[0]);
    }

    return 0;
}
  • 水平触发: (不设置不阻塞, 用EPOLLIN, 不用while read),需要调用两次epoll_wait
  • 边沿触发: 只调用一次epoll_wait

3. 参考网址

  1. https://www.bilibili.com/video/av53016117?p=66
  2. https://www.bilibili.com/video/av41308301?p=13
原文地址:https://www.cnblogs.com/whuwzp/p/linux_cpp_network_4_epoll_EPOLLET.html