Linux C++ 网络编程学习系列(5)——多路IO之epoll边沿触发
2024-09-02 02:25:55
多路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\n", j++);
write(pfd[1], buf1, 3);
sprintf(buf1, "%d\n", 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. 参考网址
最新文章
- 在IIS下部署Thinkphp项目,验证码不能显示的解决办法
- C/C++:提升_头文件的使用
- sql date时间加减几天几小时
- [IT学习]一些有用的工具
- 水晶报表(web)表格信息展示
- GDB使用
- js和css分别实现透明度的动画实现
- CentOS如何查看端口是被哪个应用/进程占用
- python比较两个列表
- C# 使用Sqlite 如何返回统计行数
- Repeater控件的嵌套使用
- wing带你玩转自定义view系列(3)模仿微信下拉眼睛
- 在Eclipse上安装Activiti插件
- 子类化QTreeWidgetItem实现增加Item的属性
- 【C#】Skip和Tack方法实现分页
- ajax 多个参数问题,如何既能表单序列化获取,又能加参数,加全部代码
- USB学习笔记连载(十五):USB固件更新以及安装驱动
- 一张图了解SSH端口转发
- [转] 用协议分析工具学习TCP/IP
- <;mvc:annotation-driven/>;的作用