epoll函数之边缘触发、水平触发、非阻塞IO模式

2023-05-16

1 边沿触发与水平触发

这两个实际是由模拟电路上的高低电频来的,低电压(0)到高电压(1)为上升沿,高电压(1)到低电压(0)为下降沿,高电压(1)到高电压(1)为水平沿。水平沿对应水平触发,上升、下降沿对应边沿触发

  • 边沿触发:epoll ET模式

  • 水平触发:epoll LT模式

通俗的说,当客户端向服务端写数据,假如1000B的数据,服务端第一次读了500B后,还剩下500B在缓冲区中,假如此时能触发epoll_wait函数的监听读事件,就叫水平触发,假如此时epoll_wait函数不触发,那么就叫边沿触发。

代码演示

如下客户端与服务端之间的演示。客户端先写五个字节’aaaa\n’,再写’bbbb\n’五个字节共十个字节一次发给服务端,然后睡眠5秒;服务端将event.events=属性设置EPOLLIN时为水平触发,此时服务端每次读5个字节,读完5个字节后,剩下的5个字节会存在缓冲区,因为此时为水平触发,所以epoll_wait函数仍然会触发读监听事件,会再次调用然后将这5个字节读出来,最后打印结果为:

aaaa
bbbb
//隔5秒后
cccc
dddd
...//每次间隔5秒

此时服务端将将event.events=属性设置改为EPOLLIN | EPOLLET,将会开启边沿触发模式,此时服务端读走5个字符后,缓冲区内还剩5个字符,epoll_wait函数也不会被调用去将这五个字符读出,只会等到5秒后客户端在发来数据时再调用,将缓冲区内的5个字符读出。此时的打印结果为:

aaaa
//隔5秒后
bbbb
//隔5秒后
cccc
...

服务端

#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <ctype.h>
#include <sys/epoll.h>

#define IP "127.0.0.1"
#define PORT 8888
#define READNUM 10

int main()
{
    int sfd = socket(AF_INET,SOCK_STREAM,0);

    struct sockaddr_in serv_addr;
    bzero(&serv_addr,sizeof (serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    inet_pton(AF_INET,IP,&serv_addr.sin_addr.s_addr);
    socklen_t serv_len = sizeof (serv_addr);
    bind(sfd,(struct sockaddr*)&serv_addr,serv_len);

    listen(sfd,128);

    struct sockaddr_in clie_addr;
    socklen_t clie_len = sizeof (clie_addr);
    int cfd = accept(sfd,(struct sockaddr*)&clie_addr,&clie_len);
    char acceptbuf[BUFSIZ];
    printf("%s connected, port = %d\n",inet_ntop(AF_INET,&clie_addr.sin_addr.s_addr,acceptbuf,sizeof (acceptbuf))
           ,ntohs(clie_addr.sin_port));

    //创建监听的红黑树
    int epfd = epoll_create(10);

    struct epoll_event event;
    //event.events = EPOLLIN;//水平触发
    event.events = EPOLLIN|EPOLLET;//边沿触发
    event.data.fd = cfd;
    epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&event);

    while(1){
        struct epoll_event outevent[10];
        //返回的就是直接满足监听条件的文件描述符个数
        int nready = epoll_wait(epfd,outevent,sizeof (outevent),-1);
        for(int i = 0; i < nready; i++){
            //如果监听的不是读事件 就跳过
            if(!(outevent[i].events & EPOLLIN))
                continue;
            char buf[BUFSIZ];
            memset(buf,0,sizeof (buf));
            if(outevent[i].data.fd == cfd){
                int len = read(cfd,buf,READNUM/2);
                write(STDOUT_FILENO,buf,len);
            }
        }
    }
    close(sfd);
    return 0;
}


客户端

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>

#define PORT 8888
#define IP "127.0.0.1"

int main()
{
    int sfd = socket(AF_INET,SOCK_STREAM,0);

    struct sockaddr_in clie_addr;
    bzero(&clie_addr,sizeof (clie_addr));
    clie_addr.sin_family = AF_INET;
    clie_addr.sin_port = htons(PORT);
    inet_pton(AF_INET,IP,&clie_addr.sin_addr.s_addr);
    socklen_t clie_len = sizeof (clie_addr);
    connect(sfd,(struct sockaddr*)&clie_addr,clie_len);

    char buf[BUFSIZ];
    memset(buf,0,sizeof (buf));
    char ch = 'a';

    while(1){
        int i = 0;
        for(; i < 5; i++){
            buf[i] = ch;
        }
        buf[i-1] = '\n';
        ch++;
        for(;i<10;i++){
            buf[i] = ch;
        }
        buf[i-1] = '\n';
        ch++;
        write(sfd,buf,10);
        sleep(5);
    }

    return 0;
}

边沿触发与水平触发对比

情况一

当客户端与服务端通信时,假如每次要发5000B的数据,假设这5000B数据中前50B数据包含了剩下的4950B数据的信息

  • 此情况下边沿触发好过水平触发,因为读50B就可以做出判定剩下的4950B数据对客户端有没有用,需不需要读,不需要就不读,清空缓冲区即可
  • 水平触发即便知道剩下的数据不需要也要一直读完才行,会拖慢程序执行效率。

情况二

在使用readn函数要读取500B数据时,假设此时服务端只发来了200B数据,readn函数会发生阻塞,等待剩下300B数据的到来,此时如果采用边沿触发模式

  • readn会在下一次数据到来前阻塞,而readn函数阻塞等待,导致epoll_wait函数无法监听下次数据的到来,readn又在阻塞等下次数据到来,最后的结果就是形成“死锁”。

此时可以使用fcntl函数设置readn函数为不阻塞。这也就引出了第三种模型epoll非阻塞IO模型。

2 epoll非阻塞IO模型

代码实例

客户端同上,服务端有所修改

/*
 * 边缘触发与水平触发对比
*/
#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <ctype.h>
#include <sys/epoll.h>
#include <fcntl.h>

#define IP "127.0.0.1"
#define PORT 8888
#define READNUM 10

int main()
{
    int sfd = socket(AF_INET,SOCK_STREAM,0);

    struct sockaddr_in serv_addr;
    bzero(&serv_addr,sizeof (serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    inet_pton(AF_INET,IP,&serv_addr.sin_addr.s_addr);
    socklen_t serv_len = sizeof (serv_addr);
    bind(sfd,(struct sockaddr*)&serv_addr,serv_len);

    listen(sfd,128);



    //创建监听的红黑树
    int epfd = epoll_create(10);
    struct epoll_event event;
    //event.events = EPOLLIN;
    event.events = EPOLLIN|EPOLLET;


    struct sockaddr_in clie_addr;
    socklen_t clie_len = sizeof (clie_addr);
    int cfd = accept(sfd,(struct sockaddr*)&clie_addr,&clie_len);
    char acceptbuf[BUFSIZ];
    printf("%s connected, port = %d\n",inet_ntop(AF_INET,&clie_addr.sin_addr.s_addr,acceptbuf,sizeof (acceptbuf))
           ,ntohs(clie_addr.sin_port));

	//通过fcntl将文件属性设置为非阻塞
    int flag = fcntl(cfd,F_GETFL);
    flag |= O_NONBLOCK;
    fcntl(cfd,F_SETFL,flag);

    event.data.fd = cfd;
    //将cfd加入到红黑树中监听
    epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&event);

    while(1){
        struct epoll_event outevent[10];
        //返回的就是直接满足监听条件的文件描述符个数
        int nready = epoll_wait(epfd,outevent,sizeof (outevent),-1);
        for(int i = 0; i < nready; i++){
            //如果监听的不是读事件 就跳过
            if(!(outevent[i].events & EPOLLIN))
                continue;
            char buf[BUFSIZ];
            memset(buf,0,sizeof (buf));
            int len = 0;
            if(outevent[i].data.fd == cfd){
            //使用while语句,当缓冲区还有数据时会直接读完,减少了重复调用epoll_wait的次数。
            //且此处即使使用readn函数一次读不够且不会发生阻塞,因为使用fcntl函数改变属性为非阻塞了
                while((len = read(cfd,buf,READNUM/2)) > 0)
                    write(STDOUT_FILENO,buf,len);
            }
        }
    }
    close(sfd);
    return 0;
}

运行结果:

aaaa
bbbb
//隔5秒后
cccc
dddd
...//每次间隔5秒

采用边沿触发模式,通过fcntl函数修改文件属性为非阻塞,相当于使用边沿触发实现了水平触发的效果。这种方法相比单纯的水平触发与边沿触发模式都要好。相比较于单纯的水平触发,调用epoll_wait函数的次数少了,增加了代码的执行效率。相比较于单纯的边沿触发,增加了灵活性,且有效避免了“死锁”的出现。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

epoll函数之边缘触发、水平触发、非阻塞IO模式 的相关文章

  • java获取两个字符串中最大相同字串

    span class token keyword import span span class token keyword static span java span class token punctuation span lang sp
  • jdk提供的4种注解

    元注解 xff1a 对现有的注解进行解释说明的注解 Retention xff1a 指定所修饰的Annotation的生命周期 xff1a SOURCE CLASS xff08 默认行为 xff09 RUNTIME xff0c 只有声明为R
  • Java中List接口常用方法

    List除了从Collection集合继承的方法外 xff0c List集合里添加了一些根据索引来操作集合元素的方法 xff1a 序号返回值函数作用1voidadd int index Object ele 在index位置插入ele元素2
  • 对Java中Class类的理解并获取Class实例

    关于java lang Class类的理解 类的加载过程 xff1a 程序经过javac exe命令以后 xff0c 会生成一个或多个字节码文件 class结尾 接着我们使用java exe命令对某个字节码文件进行解释运行 相当于将某个字节
  • 类加载器ClassLoader及应用

    类加载器ClassLoader span class token keyword public span span class token keyword class span span class token class name Cla
  • 创建运行时类的对象、调用运行时类的指定结构以及获取运行时类的完整结构

    创建两个类 一个注解 一个接口 用来准备测试 span class token keyword public span span class token keyword class span span class token class n
  • Lambda表达式基本使用的六种情况

    举例 xff1a o1 o2 gt Integer compare o1 o2 格式 xff1a lambda操作符 或 箭头操作符 左边 xff1a lambda形参列表 xff08 其实就是接口中的抽象方法的形参列表 xff09 右边
  • DBeaver登录Mysql所犯的低级错误

    1 简介 DBeaver是免费和开源 xff08 GPL xff09 为开发人员和数据库管理员通用数据库工具 2 安装DBeaver 3 DBeave连接MySQL 3 1 在DBeaver界面的左上角 xff0c 找到加号 xff0c 选
  • sql基础

    DB xff1a 数据库 database xff1a 存储数据的 仓库 它保存了一系列有组织的数据 DBMS xff1a 数据库管理系统 Database Management System 数据库是通过DBMS创建和操作的容器 SQL
  • SQL之DQL语言

    1 基础查询 语法 xff1a select 查询列表 from 表名 其中 xff1a 表中的字段 常量值 表达式 函数 查询字段 xff1a select 字段 多个字段中间逗号隔开 from 表名 查询常量值 xff1a select
  • Ubuntu中安装.deb格式的软件包

    使用dpkg命令进行安装 dpkg命令常用格式如下 xff1a sudo dpkg I iptux deb 查看iptux deb软件包的详细信息 xff0c 包括软件名称 版本以及大小等 xff08 其中 I等价于 info xff09
  • SQL之DDL(数据定义语言)

    1 库的管理 创建库 create database if not exists 库名 更改库的字符集 alter database 库名 character set gbk utf 8 删除库 drop database if exist
  • SQL之事务级别

    事务 xff1a 一个或一组sql语句组成一个执行单元 xff0c 这个执行单元要么全部执行 xff0c 要么全部不执行 事务的ACID属性 xff1a 原子性 原子性是指事务是一个不可分割的工作单位 xff0c 事务中的操作要么都发生 x
  • 动态数组的创建、插入、按值删除、按位值删除、销毁(C语言实现)

    dynamic h文件 xff1a span class token macro property span class token directive keyword pragma span once span span class to
  • 统计字母字符串中字符出现的次数(C语言实现)

    span class token macro property span class token directive keyword define span CRT SECURE NO WARNINGS span span class to
  • C语言实现strlen()函数

    方式一 xff1a span class token macro property span class token directive keyword define span CRT SECURE NO WARNINGS span spa
  • C语言gets()、fgets()、puts()、fputs()、strlen()函数

    gets 函数 xff1a 从屏幕获取一个字符串 xff0c 返回字符串的首地址 可以获取带有空格的字符串 不安全 char gets char s 参数 xff1a 用来存储字符串的空间地址 返回值 xff1a 返回实际获取到的字符串首地
  • tcp三次握手与四次挥手的简化流程

    控制字段 xff1a SYN 1 xff1a 请求建立连接控制字段 ACK 1 xff1a 数据信息确认控制字段 FIN 1 xff1a 请求断开连接控制字段 tcp三次握手过程 xff1a 第一次握手 xff1a 发送SYN请求建立连接控
  • 转载130个原文网站,原文链接:https://blog.csdn.net/qq_43901693/article/details/100606828

    64 130 余个相见恨晚的超实用网站 文末没有公众号 xff0c 只求 点赞 43 关注 文章目录 130 余个相见恨晚的超实用网站搞学习找书籍冷知识 黑科技写代码资源搜索小工具导航页 xff08 工具集 xff09 看视频学设计搞文档找
  • TCP的十一种状态集

    TCP三次握手 xff1a 5种状态 最开始两台主机都处于关闭状态 closed 服务端将相应服务开启 服务端状态 xff1a closed gt listen客户端向服务端发起连接请求 客户端状态 xff1a closed gt syn

随机推荐