多路IO之epoll边沿触发+非阻塞
- 源码地址:https://github.com/whuwzp/linuxc/tree/master/epoll_ET_LT_NOBLOCK_example
- 源码说明:
- 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 边沿触发的优势缺点
优势:
- 不会像水平触发傻瓜式一直触发epoll_wait返回
- 边沿触发自定义更方便,有新数据了告诉我,至于我是否处理了怎么处理都不用管,当然这种难度相对大
缺点: 万一没有处理完数据,就会出问题了
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