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

文章很长,而且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 :

免费赠送 :《尼恩Java面试宝典》 持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备
免费赠送 经典图书:《Java高并发核心编程(卷1)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷2)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷3)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:尼恩Java面试宝典 最新版 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 资源宝库: Java 必备 百度网盘资源大合集 价值>10000元 加尼恩领取


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

下面的内容,来自于《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出现的时候,用户进程很容易进行判断和做出对应的处理:如果不是发生错误,那么就是有数据报到达了。

说明:本文会以pdf格式持续更新,更多最新尼恩3高pdf笔记,请从下面的链接获取:语雀 或者 码云

事件注册的步骤

举个例子。发起一个异步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,并安装一个信号处理函数,进程继续运行并不阻塞。

说明:本文会持续更新,更多最新尼恩3高笔记PDF,请从下面的链接获取:语雀 或者 码云

信号驱动IO缺点:

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

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

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

posted @ 2021-10-23 17:31  疯狂创客圈  阅读(1516)  评论(0编辑  收藏  举报