先给自己打个广告,本人的微信公众号正式上线了,搜索:张笑生的地盘,主要关注嵌入式软件开发,股票基金定投,足球等等,希望大家多多关注,有问题可以直接留言给我,一定尽心尽力回答大家的问题
**
一 why
**
考虑这样一种场景,假设某个文件描述符发生了可读事件,一次可读的文件大小为1000Bytes,但是呢,实际上我们每次只会从这个文件描述符中读取500 Bytes;显然这个时候,该文件中还会剩下500个Bytes,这个时候,我们应该如何去读这个文件描述符?或者说此时epoll_wait函数会有正确的返回值吗(假设我们设置epoll_wait为永久等待方式)。
回答上面的问题,就需要引入epoll的触发方式了,分别是水平触发和边沿触发。
**
二 what
**
epoll水平触发: 只要监听的文件描述符中有数据,就会触发epoll_wait有返回值,这是默认的epoll_wait的方式;
epoll边沿触发 : 只有监听的文件描述符的读/写事件发生,才会触发epoll_wait有返回值;
通过epoll_ctl函数,设置该文件描述符的触发状态即可
//水平触发
evt.events = EPOLLIN; // LT 水平触发 (默认) EPOLLLT
evt.data.fd = pfd[0];
//边沿触发
evt.events = EPOLLIN | EPOLLET; // ET 边沿触发
evt.data.fd = pfd[0];
三 how
代码思想
(1) 创建一个无名管道,供父进程和子进程间通信
(2) 创建一个子进程,向管道中写入数据
(3) 父进程读取管道数据
(1) epoll水平触发代码如下
#include "stdio.h"
#include "stdlib.h"
#include <string.h>
#include <unistd.h>
#include "sys/types.h"
#include <sys/epoll.h>
#define MAXLINE 10
int main(void)
{
pid_t pid;
int epfd = -1, i, rval;
int pfd[2];
char buf[MAXLINE], ch = 'a';
//创建一个无名管道
pipe(pfd);
pid = fork();
if (pid == 0) { // child process
close(pfd[0]);
while (1) {
// aaaa\n
for (i = 0; i < MAXLINE / 2; i++) {
buf[i] = ch;
}
buf[i-1] = '\n';
ch++;
// bbbb\n
for (; i < MAXLINE; i++) {
buf[i] = ch;
}
buf[i-1] = '\n';
ch++;
// aaaa\nbbbb\n
write(pfd[1], buf, sizeof(buf));
sleep(5);
}
close(pfd[1]);
} else if (pid > 0) { // parent process
struct epoll_event evt;
struct epoll_event evts[10];
int res, len;
close(pfd[1]);
epfd = epoll_create(10);
if (epfd < 0) {
perror("epoll_create error");
}
evt.events = EPOLLIN; // LT 水平触发 (默认)
evt.data.fd = pfd[0];
rval = epoll_ctl(epfd, EPOLL_CTL_ADD, pfd[0], &evt);
if (rval < 0) {
perror("epoll_ctl error");
return 0;
}
while (1) {
memset(buf, 0, sizeof(buf));
res = epoll_wait(epfd, evts, 10, -1);
printf("res %d\n", res);
if (res < 0) {
perror("epoll_wait error");
return 0;
}
if (evts[0].data.fd == pfd[0]) {
len = read(pfd[0], buf, MAXLINE/2);
write(STDOUT_FILENO, buf, len);
}
}
close(pfd[0]);
}
while(1);
return 0;
}
在子进程中,我们一次向管道写入10个字符数据,为"aaaa\nbbbb\n";每隔5s写入10个字符数据;
在父进程中,我们从管道中一次读取5个字符数据,因为我们采用的是水平触发方式,因此在5s的周期内,会先读取5个字符数据,读完之后,因为文件描述符中仍然有数据,epoll_wait会立即返回,会继续读取接下来的5个数据,试验现象就是会先打印如下:
隔5s之后,继续打印"cccc\ndddd\n",如下
在这种情况下,管道中的数据如下,每次子进程写完管道之后,父进程会立即将管道中的数据读出来,在5s周期以内的剩余时间内,管道中的数据都为空
(2) epoll边沿触发代码
#include "stdio.h"
#include "stdlib.h"
#include <string.h>
#include <unistd.h>
#include "sys/types.h"
#include <sys/epoll.h>
#define MAXLINE 10
int main(void)
{
pid_t pid;
int epfd = -1, i, rval;
int pfd[2];
char buf[MAXLINE], ch = 'a';
//创建一个无名管道
pipe(pfd);
pid = fork();
if (pid == 0) { // child process
close(pfd[0]);
while (1) {
// aaaa\n
for (i = 0; i < MAXLINE / 2; i++) {
buf[i] = ch;
}
buf[i-1] = '\n';
ch++;
// bbbb\n
for (; i < MAXLINE; i++) {
buf[i] = ch;
}
buf[i-1] = '\n';
ch++;
// aaaa\nbbbb\n
write(pfd[1], buf, sizeof(buf));
sleep(5);
}
close(pfd[1]);
} else if (pid > 0) { // parent process
struct epoll_event evt;
struct epoll_event evts[10];
int res, len;
close(pfd[1]);
epfd = epoll_create(10);
if (epfd < 0) {
perror("epoll_create error");
}
evt.events = EPOLLIN | EPOLLET; // ET 边沿触发
evt.data.fd = pfd[0];
rval = epoll_ctl(epfd, EPOLL_CTL_ADD, pfd[0], &evt);
if (rval < 0) {
perror("epoll_ctl error");
return 0;
}
while (1) {
memset(buf, 0, sizeof(buf));
res = epoll_wait(epfd, evts, 10, -1);
printf("res %d\n", res);
if (res < 0) {
perror("epoll_wait error");
return 0;
}
if (evts[0].data.fd == pfd[0]) {
len = read(pfd[0], buf, MAXLINE/2);
write(STDOUT_FILENO, buf, len);
}
}
close(pfd[0]);
}
while(1);
return 0;
}
因为此时采用的是边沿触发方式,当父进程先读完管道中的5个字符后,由于子进程没有立即向管道中写入字符(间隔5s后才会第二次写入),所以此时父进程会先读到5个字符"aaaa\n",
隔5s之后,再读到5个字符"bbbb\n"
在这种情况下,管道中的数据如下,显然随着时间越来越长,管道中的数据会越来越多。