• 事件驱动IO模式(图解+秒懂+史上最全)


    文章很长,建议收藏起来,慢慢读! Java 高并发 发烧友社群:疯狂创客圈 奉上以下珍贵的学习资源:


    推荐:入大厂 、做架构、大力提升Java 内功 的 精彩博文

    入大厂 、做架构、大力提升Java 内功 必备的精彩博文 2021 秋招涨薪1W + 必备的精彩博文
    1:Redis 分布式锁 (图解-秒懂-史上最全) 2:Zookeeper 分布式锁 (图解-秒懂-史上最全)
    3: Redis与MySQL双写一致性如何保证? (面试必备) 4: 面试必备:秒杀超卖 解决方案 (史上最全)
    5:面试必备之:Reactor模式 6: 10分钟看懂, Java NIO 底层原理
    7:TCP/IP(图解+秒懂+史上最全) 8:Feign原理 (图解)
    9:DNS图解(秒懂 + 史上最全 + 高薪必备) 10:CDN图解(秒懂 + 史上最全 + 高薪必备)

    Java 面试题 30个专题 , 史上最全 , 面试必刷 阿里、京东、美团... 随意挑、横着走!!!
    1: JVM面试题(史上最强、持续更新、吐血推荐) 2:Java基础面试题(史上最全、持续更新、吐血推荐
    3:架构设计面试题 (史上最全、持续更新、吐血推荐) 4:设计模式面试题 (史上最全、持续更新、吐血推荐)
    17、分布式事务面试题 (史上最全、持续更新、吐血推荐) 一致性协议 (史上最全)
    29、多线程面试题(史上最全) 30、HR面经,过五关斩六将后,小心阴沟翻船!
    9.网络协议面试题(史上最全、持续更新、吐血推荐) 更多专题, 请参见【 疯狂创客圈 高并发 总目录

    SpringCloud 精彩博文
    nacos 实战(史上最全) sentinel (史上最全+入门教程)
    SpringCloud gateway (史上最全) 更多专题, 请参见【 疯狂创客圈 高并发 总目录

    特别说明:本文所属书籍已经更新啦,最新内容以书籍为准(书籍也免费送哦)

    下面的内容,来自于《Java高并发核心编程卷1》一书,此书的最新电子版,已经免费赠送,大家找尼恩领取即可。

    而且,《Java高并发核心编程卷1》的电子书,会不断优化和迭代。最新一轮的迭代,增加了 消息驱动IO模型的内容,这是之前没有的,使得在 Java NIO 底层原理这块,书的内容变得非常全面。
    另外,如果出现内容需要更新,到处要更新的话,工作量会很大,所以后续的更新,都会统一到电子书哦。

    在这里插入图片描述

    在这里插入图片描述

    信号驱动IO的简介

    在信号驱动IO模型中,用户线程通过IO事件的回调函数注册,来避免IO时间查询的阻塞。

    具体的做法是,用户进程预先在内核中设置一个回调函数,当某个事件发生时,内核使用信号(SIGIO)通知进程运行回调函数。 然后用户线程会继续执行,在信号回调函数中调用IO读写操作来进行实际的IO请求操作。

    信号驱动IO的基本流程

    信号驱动IO的基本流程是:

    用户进程通过系统调用,向内核注册SIGIO信号的owner进程和以及进程内的回调函数。内核IO事件发生后(比如内核缓冲区数据就位)后,通知用户程序,用户进程通过read系统调用,将数据复制到用户空间,然后执行业务逻辑。

    在这里插入图片描述

    信号驱动IO模型,每当套接字发生IO事件时,系统内核都会向用户进程发送SIGIO事件,所以,一般用于UDP传输,在TCP套接字的开发过程中很少使用,原因是SIGIO信号产生得过于频繁,并且内核发送的SIGIO信号,并没有告诉用户进程发生了什么IO事件。

    但是在UDP套接字上,通过SIGIO信号进行下面两个事件的类型判断即可:

    1 数据报到达套接字

    2 套接字上发上一部错误

    因此,在SIGIO出现的时候,用户进程很容易进行判断和做出对应的处理:如果不是发生错误,那么就是有数据报到达了。

    事件注册的步骤

    举个例子。发起一个异步IO的read读操作的系统调用,流程如下:

    (1)设置SIGIO信号的信号处理回调函数。

    (2)设置该套接口的属主进程,使得套接字的IO事件发生时,系统能够将SIGIO信号传递给属主进程,也就是当前进程。

    (3)开启该套接口的信号驱动I/O机制,通常通过使用fcntl方法的F_SETFL操作命令,使能(enable)套接字的 O_NONBLOCK非阻塞标志和O_ASYNC异步标志完成。

    完成以上三步,用户进程就完成了事件回调处理函数的设置。当文件描述符上有事件发生时,SIGIO 的信号处理函数将被触发,然后便可对目标文件描述符执行 I/O 操作。

    关于以上三步的详细介绍,具体如下:

    第一步:

    设置SIGIO信号的信号处理回调函数。Linux中通过 sigaction() 来完成。参考的代码如下:

    
       // 注册SIGIO事件的回调函数
    
       sigaction(SIGIO, &act, NULL); 
    
    

    sigaction函数的功能是检查或修改与指定信号相关联的处理动作(可同时两种操作),函数的原型如下:

    
    int sigaction(int signum, const struct sigaction *act,
    
                         struct sigaction *oldact);
    
    

    对其中的参数说明如下:

    1 signum参数指出要捕获的信号类型

    2 act参数指定新的信号处理方式

    3 oldact参数输出先前信号的处理方式(如果不为NULL的话)。

    该函数是Linux系统的一个基础函数,不是为信号驱动IO特供的。在信号驱动IO的使用场景中,signum的值为常量 SIGIO。

    第二步:

    设置该套接口的属主进程,使得套接字的IO事件发生时,系统能够将SIGIO信号传递给属主进程,也就是当前进程。属主进程是当文件描述符上可执行 I/O 时,会接收到通知信号的进程或进程组。

    为文件描述符的设置IO事件的属主进程,通过 fcntl() 的 F_SETOWN 操作来完成,参考的代码如下:

    
    fcntl(fd,F_SETOWN,pid)
    

    当参数pid 为正整数时,代表了进程 ID 号。当参数pid 为负整数时,它的绝对值就代表了进程组 ID 号。

    第三步:

    开启该套接口的信号驱动IO机制,通常通过使用fcntl方法的F_SETFL操作命令,使能(enable)套接字的 O_NONBLOCK非阻塞标志和O_ASYNC异步标志完成。参考的代码如下:

    int flags = fcntl(socket_fd, F_GETFL, 0);
    
        flags |= O_NONBLOCK;  //设置非阻塞
    
        flags |= O_ASYNC;    //设置为异步
    
        fcntl(socket_fd, F_SETFL, flags );
    
    

    这一步通过 fcntl() 的 F_SETFL 操作来完成,O_NONBLOCK为非阻塞标志,O_ASYNC为信号驱动 I/O的标志。

    使用事件驱动IO进行UDP通信应用的开发,参考的代码如下(C代码):

    
    int socket_fd = 0;
    
     
    
    //事件的处理函数
    
    void do_sometime(int signal) {
    
        struct sockaddr_in cli_addr;
    
        int clilen = sizeof(cli_addr);
    
        int clifd = 0;
    
     
    
        char buffer[256] = {0};
    
        int len = recvfrom(socket_fd, buffer, 256, 0, (struct sockaddr *)&cli_addr,
    
                           (socklen_t)&clilen);
    
        printf("Mes:%s", buffer);
    
        
    
        //回写
    
        sendto(socket_fd, buffer, len, 0, (struct sockaddr *)&cli_addr, clilen);
    
    }
    
     
    
    int main(int argc, char const *argv[]) {
    
        socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
    
        struct sigaction act;
        act.sa_flags = 0;
    
        act.sa_handler = do_sometime;
    
        // 注册SIGIO事件的回调函数
        sigaction(SIGIO, &act, NULL); 
    
        struct sockaddr_in servaddr;
    
        memset(&servaddr, 0, sizeof(servaddr));
    
        servaddr.sin_family = AF_INET;
    
        servaddr.sin_port = htons(8888);
    
        servaddr.sin_addr.s_addr = INADDR_ANY;
    
     
    
        //第二步为文件描述符的设置 属主
    
        //设置将要在socket_fd上接收SIGIO的进程
    
        fcntl(socket_fd, F_SETOWN, getpid());
    
     
    
        //第三步:使能套接字的信号驱动IO
    
        int flags = fcntl(socket_fd, F_GETFL, 0);
    
        flags |= O_NONBLOCK;  //设置非阻塞
    
        flags |= O_ASYNC;    //设置为异步
    
        fcntl(socket_fd, F_SETFL, flags );
    
     
    
        bind(socket_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    
        while (1) sleep(1); //死循环
    
        close(socket_fd);
    
        return 0;
    
    }
    

    当套件字的IO事件发生时,回调函数被执行,在回调函数中,用户进行执行数据复制即可。

    信号驱动IO优势:

    用户进程在等待数据时,不会被阻塞,能够用户进程的效率。具体来说:在信号驱动式I/O模型中,应用程序使用套接口进行信号驱动I/O,并安装一个信号处理函数,进程继续运行并不阻塞。

    信号驱动IO缺点:

    1 在大量IO事件发生时,可能会由于处理不过来,而导致信号队列溢出。

    2 对于处理UDP套接字来讲,对于信号驱动I/O是有用的。可是,对于TCP而言,由于致使SIGIO信号通知的条件为数众多,进行IO信号进一步区分的成本太高,信号驱动的I/O方式近乎无用。

    3 信号驱动IO可以看成是一种异步IO,可以简单理解为系统进行用户函数的回调。但是,信号驱动IO的异步特性,又做的不彻底。信号驱动IO仅仅在IO事件的通知阶段,是异步的,但是,在将数据从内核缓冲区复制到用户缓冲区这个过程,用户进程是阻塞的、同步的。

  • 相关阅读:
    使用docker搭建nginx集群,实现负载均衡
    机器重启以后,主从出现报错:ERROR 1872 (HY000): Slave failed to initialize relay log info structure from the repos
    Slave_SQL_Running: No mysql同步故障解决方法
    docker搭建MySQL主从集群
    Linux下如何查看版本信息
    Docker中使用CentOS7镜像
    在 CentOS 上安装及使用 VirtualBox
    Docker Machine 安装使用教程
    main主函数
    is and ==
  • 原文地址:https://www.cnblogs.com/crazymakercircle/p/15449091.html
Copyright © 2020-2023  润新知