I/O模型的一些理解

I/O模型的一些理解

    • 一些基本的概念
      • 同步阻塞、同步非阻塞、异步阻塞、异步非阻塞
      • 总结概念
    • I/O模型
      • 一些例子
    • 从源头解释
      • 从TCP发送数据的流程说起
      • 阻塞I/O | 非阻塞I/O
      • I/O多路复用
      • 信号驱动I/O
      • 异步I/O
      • 再谈IO模型里面的同步异步

参考连接

参考链接

参考链接

一些基本的概念

  • 阻塞(blocking)、非阻塞(non-blocking):可以简单理解为需要做一件事能不能立即得到返回应答,比如read函数

  • 同步(synchronous)、异步(asynchronous): 你总是做完一件再去做另一件,不管是否需要时间等待,这就是同步(就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回,即此时不能做下一件事情);异步则反之,你可以同时做几件事,并非一定需要一件事做完再做另一件事(当一个异步过程调用发出后,调用者不能立刻得到结果,此时可以接着做其它事情)。同步可以理解为多个相互之间一定联系的并发执行的进程,有序执行,异步则没有约定一定的次序。

举个更加形象的例子,如果主线程上执行了一系列函数,某个函数A需要与相关线程交互,调用了另一个函数a时:

  • 如果立即去执行函数 a,这称为同步。

  • 如果没有去执行函数 a,而是将执行函数 a 的时机安排在未来的某个时间,然后马上继续执行函数 A,这称为异步。

  • 当执行函数 a 时,直至获得完整的资源之前,都暂停执行当前函数,这称为阻塞。

  • 当执行函数 a 时,立即获得瞬时的结果,然后马上继续执行当前函数。如果获得的瞬时资源不是完整的资源,之后周期性发送类似的请求,直至获得完整的资源,这称为非阻塞。

同步阻塞、同步非阻塞、异步阻塞、异步非阻塞

  • 同步阻塞:在需要某资源时马上发起请求,并暂停本线程之后的程序,直至获得所需的资源。

  • 同步非阻塞:在需要某资源时马上发起请求,且可以马上得到答复,然后继续执行之后的程序。但如果得到的不是完整的资源,之后将周期性地的请求,直至获得所需的资源。

  • 异步阻塞:在需要某资源时不马上发起请求,而安排一个以后的时间再发起请求。当到了那时发出请求时,将暂停本线程之后的程序,直至获得所需的资源。在获取资源之后,使用共享信号量、异步回调等方式将结果异步反馈。

  • 异步非阻塞:在需要某资源时不马上发起请求,而安排一个以后的时间再发起请求。当到了那时发出请求时,可以马上得到答复,然后继续执行之后的程序。但如果得到的不是完整的资源,之后将周期性地的请求。在最终获取到资源之后,使用共享信号量、异步回调等方式将结果异步反馈。

总结概念

  • 异步:把事情推到以后去做

  • 阻塞:专心做一件事情

  • 同步阻塞:马上专心做一件事情

  • 同步非阻塞:一边做一件事情,一边做另一件事情(一心二用)

  • 异步阻塞:把问题推到以后专心处理

  • 异步非阻塞:把问题推到以后时不时处理一下

I/O模型

IO (Input/Output,输入/输出)即数据的读取(接收)或写入(发送)操作,通常用户进程中的一个完整IO分为两阶段:用户进程空间<–>内核空间、内核空间<–>设备空间(磁盘、网络等)。IO有内存IO、网络IO和磁盘IO三种,通常我们说的IO指的是后两者。

针对网络I/O,可以分成两个阶段,准备阶段和操作阶段。

  • 准备阶段:判断是否能够操作(即等待数据是否可用),在内核进程完成的;
  • 操作阶段:执行实际的IO调用,数据从内核缓冲区拷贝到用户进程缓冲区。

比如对于一个read操作,它会经历下面两个阶段:

  • 等待数据准备,数据是否拷贝到内核缓冲区;

  • 将数据从内核拷贝到用户进程空间

重点理解上面这两个阶段

UNP中提到,5种IO模型分别是阻塞IO模型、非阻塞IO模型、IO复用模型、信号驱动的IO模型、异步IO模型;前4种为同步IO操作,只有异步IO模型是异步IO操作。

同步I/O向应用程序通知的是I/O就绪事件,异步I/O向应用程序通知的是I/O完成事件

1、阻塞IO:在准备阶段即同步阻塞,应用进程调用I/O操作时阻塞,只有等待要操作的数据准备好,并复制到应用进程的缓冲区中才返回;

2、非阻塞IO:当应用进程要调用的I/O操作会导致该进程进入阻塞状态时,该I/O调用返回一个错误,一般情况下,应用进程需要利用轮询的方式来检测某个操作是否就绪。数据就绪后,实际的I/O操作会等待数据复制到应用进程的缓冲区中以后才返回;

3、IO复用:多路IO共用一个同步阻塞接口,任意IO可操作都可激活IO操作,这是对阻塞IO的改进(主要是select和poll、epoll,关键是能实现同时对多个IO端口进行监听)。此时阻塞发生在select/poll的系统调用上,而不是阻塞在实际的I/O系统调用上。IO多路复用的高级之处在于:它能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select等函数就可以返回。

4、信号驱动IO:注册一个IO信号事件,在数据可操作时通过SIGIO信号通知线程,这应该算是一种异步机制;

以上四种模型在第一阶段即判断是否可操作阶段各不相同,但一旦数据可操作,则切换到同步阻塞模式下执行IO操作,所以都算是同步IO。

5、异步IO: 应用进程通知内核开始一个异步I/O操作,并让内核在整个操作(包含将数据从内核复制到应该进程的缓冲区)完成后通知应用进程。

在UNP里面,根据上述两个阶段,可以进行归类

  • 阻塞IO:在两个阶段上面都是阻塞的;

  • 非阻塞IO:在第1阶段,程序不断的轮询直到数据准备好,第2阶段还是阻塞的;

  • IO复用:在第1阶段,当一个或者多个IO准备就绪时,通知程序,第2阶段还是阻塞的,在第1阶段还是轮询实现的,只是所有的IO都集中在一个地方,这个地方进行轮询;

  • 信号IO:当数据准备完毕的时候,信号通知程序数据准备完毕,第2阶段阻塞;

  • 异步IO:1,2都不阻塞
    在这里插入图片描述

一些例子

阻塞、非阻塞、I/O多路复用:socket可以设置为阻塞和非阻塞两种,结合select、poll、epoll可以实现I/O多路复用

信号I/O

client.cpp

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>volatile sig_atomic_t io_ready = 0;
int sockfd = -1;void io_handler(int sig) {io_ready = 1;
}int main()
{struct sigaction sa;char buffer[1024];// 创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);// 向服务器(特定的IP和端口)发起请求struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));           // 每个字节都用0填充serv_addr.sin_family = AF_INET;                     // 使用IPv4地址serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 具体的IP地址serv_addr.sin_port = htons(1234);                   // 端口connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));// 设置超时时间为5秒struct timeval timeout;timeout.tv_sec = 5;timeout.tv_usec = 0;setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout));// 设置套接字为非阻塞模式int flags = fcntl(sockfd, F_GETFL, 0);if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1){perror("fcntl");close(sockfd);exit(EXIT_FAILURE);}// 设置信号处理函数sa.sa_handler = io_handler;sa.sa_flags = 0;sigemptyset(&sa.sa_mask);if (sigaction(SIGIO, &sa, NULL) == -1){perror("sigaction");close(sockfd);exit(EXIT_FAILURE);}// 设置套接字的所有者if (fcntl(sockfd, F_SETOWN, getpid()) == -1){perror("fcntl F_SETOWN");close(sockfd);exit(EXIT_FAILURE);}// 启用信号驱动 I/Oif (fcntl(sockfd, F_SETFL, O_ASYNC) == -1){perror("fcntl F_SETFL");close(sockfd);exit(EXIT_FAILURE);}// 等待接收数据while (!io_ready){// 模拟进程正在执行其他任务std::cout << "模拟进程正在执行其他任务"<< std::endl;sleep(1);}// 接收数据ssize_t len = recv(sockfd, buffer, sizeof(buffer) - 1, 0);if (len == -1){perror("recv");close(sockfd);exit(EXIT_FAILURE);}buffer[len] = '\0';printf("Received: %s\n", buffer);// 关闭套接字close(sockfd);return 0;
}

server.cpp

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <iostream>
int main()
{// 创建套接字int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);// 将套接字和IP、端口绑定struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));           // 每个字节都用0填充serv_addr.sin_family = AF_INET;                     // 使用IPv4地址serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 具体的IP地址serv_addr.sin_port = htons(1234);                   // 端口// 设置端口复用int opt = 1;setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr));// 进入监听状态,等待用户发起请求listen(serv_sock, 20);// 接收客户端请求struct sockaddr_in clnt_addr;socklen_t clnt_addr_size = sizeof(clnt_addr);int clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &clnt_addr_size);// 打印收到的客户端地址端口信息std::cout << "Client address: " << inet_ntoa(clnt_addr.sin_addr) << ", port: " << ntohs(clnt_addr.sin_port) << std::endl;sleep(5);for (int i = 0; i < 1; i++){// 向客户端发送数据char str[] = "hello client!!!";write(clnt_sock, str, sizeof(str));}sleep(10000);// 关闭套接字close(clnt_sock);close(serv_sock);return 0;
}

上面的例子中,server就只做一件事,监听客户端连接,一旦建立以后,等待5s再发送数据

而client首先绑定了信号处理函数,当套接字有数据可以读的时候,内核会发出SIGIO信号给进程,然后执行信号处理函数。将io_ready变量设置为1,表示可以读取数据了。然后,主循环中的进程可以跳出循环,执行接收数据的操作。

一般来说都会设置套接字是非阻塞的,但我感觉这里recv实际上已经不会阻塞了?因为肯定是有了数据才会执行到这一步的

从源头解释

从TCP发送数据的流程说起

以两个应用程序通讯为例,我们来了解一下当“A”向"B" 发送一条消息,简单来说会经过如下流程:

第一步:应用A把消息发送到 TCP发送缓冲区。

第二步: TCP发送缓冲区再把消息发送出去,经过网络传递后,消息会发送到B服务器的TCP接收缓冲区。

第三步:B再从TCP接收缓冲区去读取属于自己的数据。

在这里插入图片描述

阻塞I/O | 非阻塞I/O

我们把视角切换到上面图中的第三步, 也就是应用B从TCP缓冲区中读取数据。

在这里插入图片描述

思考一个问题

因为应用之间发送消息是间断性的,也就是说在上图中TCP缓冲区还没有接收到属于应用B该读取的消息时,那么此时应用B向TCP缓冲区发起读取申请,TCP接收缓冲区是应该马上告诉应用B 现在没有你的数据,还是说让应用B在这里等着,直到有数据再把数据交给应用B。

把这个问题应用到第一个步骤也是一样,应用A在向TCP发送缓冲区发送数据时,如果TCP发送缓冲区已经满了,那么是告诉应用A现在没空间了,还是让应用A等待着,等TCP发送缓冲区有空间了再把应用A的数据访拷贝到发送缓冲区。

什么是阻塞IO

如果上面的问题你已经思考过了,那么其实你已经明白了什么是阻塞IO了,所谓阻塞IO就是当应用B发起读取数据申请时,在内核数据没有准备好之前,应用B会一直处于等待数据状态,直到内核把数据准备好了交给应用B才结束。

术语描述:在应用调用recvfrom读取数据时,其系统调用直到数据包到达且被复制到应用缓冲区中或者发送错误时才返回,在此期间一直会等待,进程从调用到返回这段时间内都是被阻塞的称为阻塞IO;

流程:

1、应用进程向内核发起recfrom读取数据。

2、准备数据报(应用进程阻塞)。

3、将数据从内核负责到应用空间。

4、复制完成后,返回成功提示。

在这里插入图片描述
什么是非阻塞IO

我敢保证如果你已经理解了阻塞IO,那么必定已经知道了什么是非阻塞IO。按照上面的思路,所谓非阻塞IO就是当应用B发起读取数据申请时,如果内核数据没有准备好会即刻告诉应用B,不会让B在这里等待。

术语:非阻塞IO是在应用调用recvfrom读取数据时,如果该缓冲区没有数据的话,就会直接返回一个EWOULDBLOCK错误,不会让应用一直等待中。在没有数据的时候会即刻返回错误标识,那也意味着如果应用要读取数据就需要不断的调用recvfrom请求,直到读取到它要的数据为止。即需要自己实现一个轮询,比如while?又比如epoll ET时候必须设置非阻塞,判断EAGAIN
在这里插入图片描述

流程:

1、应用进程向内核发起recvfrom读取数据。

2、没有数据报准备好,即刻返回EWOULDBLOCK错误码。

3、应用进程向内核发起recvfrom读取数据。

4、已有数据包准备好就进行一下 步骤,否则还是返回错误码。

5、将数据从内核拷贝到用户空间。

6、完成后,返回成功提示。

I/O多路复用

如果你已经明白了非阻塞IO的工作模式,那么接下来我们继续了解IO复用模型的产生原因和思路。

思考一个问题:

我们还是把视角放到应用B从TCP缓冲区中读取数据这个环节来。如果在并发的环境下,可能会N个人向应用B发送消息,这种情况下我们的应用就必须创建多个线程去读取数据,每个线程都会自己调用recvfrom 去读取数据。那么此时情况可能如下图:

在这里插入图片描述
如上图一样,并发情况下服务器很可能一瞬间会收到几十上百万的请求,这种情况下应用B就需要创建几十上百万的线程去读取数据,同时又因为应用线程是不知道什么时候会有数据读取,为了保证消息能及时读取到,那么这些线程自己必须不断的向内核发送recvfrom 请求来读取数据;

那么问题来了,这么多的线程不断调用recvfrom 请求数据,先不说服务器能不能扛得住这么多线程,就算扛得住那么很明显这种方式是不是太浪费资源了,线程是我们操作系统的宝贵资源,大量的线程用来去读取数据了,那么就意味着能做其它事情的线程就会少。

所以,有人就提出了一个思路,能不能提供一种方式,可以由一个线程监控多个网络请求(我们后面将称为fd文件描述符,linux系统把所有网络请求以一个fd来标识),这样就可以只需要一个或几个线程就可以完成数据状态询问的操作,当有数据准备就绪之后再分配对应的线程去读取数据,这么做就可以节省出大量的线程资源出来,这个就是IO复用模型的思路。

在这里插入图片描述
正如上图,IO复用模型的思路就是系统提供了一种函数可以同时监控多个fd的操作,这个函数就是我们常说到的select、poll、epoll函数,有了这个函数后,应用线程通过调用select函数就可以同时监控多个fd,select函数监控的fd中只要有任何一个数据状态准备就绪了,select函数就会返回可读状态,这时询问线程再去通知处理数据的线程,对应线程此时再发起recvfrom请求去读取数据。

术语描述:进程通过将一个或多个fd传递给select,阻塞在select操作上,select帮我们侦测多个fd是否准备就绪,当有fd准备就绪时,select返回数据可读状态,应用程序再调用recvfrom读取数据。

在这里插入图片描述
总结:复用IO的基本思路就是通过slect或poll、epoll 来监控多fd ,来达到不必为每个fd创建一个对应的监控线程,从而减少线程资源创建的目的。

信号驱动I/O

复用IO模型解决了一个线程可以监控多个fd的问题,但是select是采用轮询的方式来监控多个fd的,通过不断的轮询fd的可读状态来知道是否有可读的数据,而无脑的轮询就显得有点暴力,因为大部分情况下的轮询都是无效的,所以有人就想,能不能不要我总是去问你是否数据准备就绪,能不能我发出请求后等你数据准备好了就通知我,所以就衍生了信号驱动IO模型。

于是信号驱动IO不是用循环请求询问的方式去监控数据就绪状态,而是在调用sigaction时候建立一个SIGIO的信号联系,当内核数据准备好之后再通过SIGIO信号通知线程数据准备好后的可读状态,当线程收到可读状态的信号后,此时再向内核发起recvfrom读取数据的请求,因为信号驱动IO的模型下应用线程在发出信号监控后即可返回,不会阻塞,所以这样的方式下,一个应用线程也可以同时监控多个fd。

在这里插入图片描述
术语描述:首先开启套接口信号驱动IO功能,并通过系统调用sigaction执行一个信号处理函数,此时请求即刻返回,当数据准备就绪时,就生成对应进程的SIGIO信号,通过信号回调通知应用线程调用recvfrom来读取数据。

在这里插入图片描述

总结: IO复用模型里面的select虽然可以监控多个fd了,但select其实现的本质上还是通过不断的轮询fd来监控数据状态, 因为大部分轮询请求其实都是无效的,所以信号驱动IO意在通过这种建立信号关联的方式,实现了发出请求后只需要等待数据就绪的通知即可,这样就可以避免大量无效的数据状态轮询操作

异步I/O

其实经过了上面两个模型的优化,我们的效率有了很大的提升,但是我们当然不会就这样满足了,有没有更好的办法,通过观察我们发现,不管是IO复用还是信号驱动,我们要读取一个数据总是要发起两阶段的请求,第一次发送select请求,询问数据状态是否准备好,第二次发送recevform请求读取数据。

思考一个问题:

也许你一开始就有一个疑问,为什么我们明明是想读取数据,而却非得要先发起一个select询问数据状态的请求,然后再发起真正的读取数据请求,能不能有一种一劳永逸的方式,我只要发送一个请求我告诉内核我要读取数据,然后我就什么都不管了,然后内核去帮我去完成剩下的所有事情?

当然既然你想得出来,那么就会有人做得到,有人设计了一种方案,应用只需要向内核发送一个read 请求,告诉内核它要读取数据后即刻返回;内核收到请求后会建立一个信号联系,当数据准备就绪,内核会主动把数据从内核复制到用户空间,等所有操作都完成之后,内核会发起一个通知告诉应用,我们称这种一劳永逸的模式为异步IO模型。

在这里插入图片描述
术语描述: 应用告知内核启动某个操作,并让内核在整个操作完成之后,通知应用,这种模型与信号驱动模型的主要区别在于,信号驱动IO只是由内核通知我们合适可以开始下一个IO操作,而异步IO模型是由内核通知我们操作什么时候完成。

在这里插入图片描述
**总结:**异步IO的优化思路是解决了应用程序需要先后发送询问请求、发送接收数据请求两个阶段的模式,在异步IO的模式下,只需要向内核发送一次请求就可以完成状态询问和数拷贝的所有操作。

再谈IO模型里面的同步异步

我们通常会说到同步阻塞IO、同步非阻塞IO,异步IO几种术语,通过上面的内容,那么我想你现在肯定已经理解了什么是阻塞什么是非阻塞了,所谓阻塞就是发起读取数据请求的时,当数据还没准备就绪的时候,这时请求是即刻返回,还是在这里等待数据的就绪,如果需要等待的话就是阻塞,反之如果即刻返回就是非阻塞。

我们区分了阻塞和非阻塞后再来分别下同步和异步,在IO模型里面如果请求方从发起请求到数据最后完成的这一段过程中都需要自己参与,那么这种我们就称为同步请求;反之,如果应用发送完指令后就不再参与过程了,只需要等待最终完成结果的通知,那么这就属于异步。

我们再看同步阻塞、同步非阻塞,他们不同的只是发起读取请求的时候一个请求阻塞,一个请求不阻塞,但是相同的是,他们都需要应用自己监控整个数据完成的过程。而为什么只有异步非阻塞 而没有异步阻塞呢,因为异步模型下请求指定发送完后就即刻返回了,没有任何后续流程了,所以它注定不会阻塞,所以也就只会有异步非阻塞模型了。从常规意义上看,异步阻塞不存在。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/777589.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ChatGPT与传统搜索引擎的区别:智能对话与关键词匹配的差异

引言 随着互联网的快速发展&#xff0c;信息的获取变得比以往任何时候都更加便捷。在数字化时代&#xff0c;人们对于获取准确、及时信息的需求愈发迫切。传统搜索引擎通过关键词匹配的方式为用户提供了大量的信息&#xff0c;然而&#xff0c;这种机械式的检索方式有时候并不…

react+vite+antD+reduce+echarts项目完整记录

reactviteantDreduceecharts项目完整记录 之前写前端项目&#xff0c;都是用的vue&#xff0c;从最开始的vue2到后来的vue3&#xff0c;断断续续写了3年&#xff0c;打包工具也从webpack转到了vite&#xff0c;全局数据管理工具从vuex转到了pinia。总体而言&#xff0c;vue3对…

GNU Radio之OFDM Carrier Allocator底层C++实现

文章目录 前言一、OFDM Carrier Allocator 简介二、底层 C 实现1、make 函数2、ofdm_carrier_allocator_cvc_impl 函数3、calculate_output_stream_length 函数4、work 函数5、~ofdm_carrier_allocator_cvc_impl 函数 三、OFDM 数据格式 前言 OFDM Carrier Allocator 是 OFDM …

微信小程序更换头像的功能

微信小程序开发&#xff0c;个人中心中更换头像的更能使用频率很高&#xff0c;这里记录下实现方式&#xff1a; <view class"setting-list avatar-container"><text>头像</text><view class"avatar"><button hover-class"…

ssm 房屋销售管理系统开发mysql数据库web结构java编程计算机网页源码eclipse项目

一、源码特点 ssm 房屋销售管理系统是一套完善的信息系统&#xff0c;结合springMVC框架完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模…

文件上传失败原因分析与解决

图片文件上传失败 问题描述&#xff1a;在前端开发时&#xff0c;需要通过表单元素上传图片或其他文本&#xff0c;但是上传不成功&#xff0c;后端接口也没问题 html <!--onChange用来绑定数据 handleUpload用来提交数据--><form onSubmit{handleUpload}><…

TitanIDE与传统 IDE 比较

与传统IDE的比较 TitanIDE 和传统 IDE 属于不同时代的产物&#xff0c;在手工作坊时代&#xff0c;一切都是那么的自然&#xff0c;开发者习惯 Windows 或 MacOS 原生 IDE。不过&#xff0c;随着时代的变迁&#xff0c;软件行业已经步入云原生时代&#xff0c;TitanIDE 是顺应…

PhpStorm 2023 for Mac/Win:开启PHP集成开发新纪元,让编程更高效更智能

在数字时代的浪潮中&#xff0c;PHP作为一种广泛应用的服务器端脚本语言&#xff0c;其重要性不言而喻。而要在PHP的世界里游刃有余&#xff0c;一款强大的集成开发环境&#xff08;IDE&#xff09;是必不可少的。PhpStorm 2023&#xff0c;正是这样一款能够助您一臂之力的编程…

Github万星项目lobe-chat,连接GPT4GPTs,平替chatgpt-plus

简介 Lobe Chat - 一个开源、高性能的聊天机器人框架&#xff0c;支持语音合成、多模态和可扩展的函数调用插件系统。支持一键免费部署您的私人 ChatGPT/LLM Web 应用程序。 项目地址&#xff1a; GitHub - lobehub/lobe-chat: &#x1f92f; Lobe Chat - an open-source, mo…

【React】onClick点击事件传参的4种方式

记录React onClick 点击事件传参的 4 种方式 方式一&#xff1a;使用内联箭头函数 import React, { MouseEvent } from "react";function App() {const handleClick (event: MouseEvent<HTMLButtonElement>, name: string) > {console.log(event)console.…

2024蓝桥杯每日一题(背包2)

备战2024年蓝桥杯 -- 每日一题 Python大学A组 试题一&#xff1a;包子凑数 试题二&#xff1a;砝码称重 试题三&#xff1a;倍数问题 试题一&#xff1a;包子称重 【题目描述】 小明几乎每天早晨都会在一家包子铺吃早餐。他发现这家包子铺有 N 种蒸笼&#xf…

R语言做两次分类,再做两两T检验,最终输出均值和pvalue

1.输入文件&#xff1a; 2.代码&#xff1a; setwd("E:/R/Rscripts/rG4相关绘图")# 加载所需的库 library(tidyverse)# 读取CSV文件 data <- read.csv("box-cds-ABD-不同类型rg4-2.csv", stringsAsFactors FALSE)# 组合Type1和Type2&#xff1a;通过…

iOS —— 初识KVO

iOS —— 初始KVO KVO的基础1. KVO概念2. KVO使用步骤注册KVO监听实现KVO监听销毁KVO监听 3. KVO基本用法4. KVO传值禁止KVO的方法 注意事项&#xff1a; KVO的基础 1. KVO概念 KVO是一种开发模式&#xff0c;它的全称是Key-Value Observing (观察者模式) 是苹果Fundation框架…

UE小:基于UE5的两种Billboard material(始终朝向相机材质)

本文档展示了两种不同的效果&#xff0c;分别是物体完全朝向相机和物体仅Z轴朝向相机。通过下面的演示和相关代码&#xff0c;您可以更加直观地理解这两种效果的差异和应用场景。 1. 完全朝向相机效果 此效果下&#xff0c;物体将完全面向相机&#xff0c;不论相机在哪个角度…

STM32学习和实践笔记(3): 使用库函数点亮LED后的学习总结

依照教程&#xff0c;做完了第一个试验&#xff0c;使用库函数点亮LED&#xff0c;如下: 总结一下一些要点&#xff1a; 一&#xff0c;要记得指明各头文件的查找路径&#xff0c;方法如下图&#xff1a; 二&#xff0c;使用库函数来编程相当方便高效&#xff0c;要学会查找对…

八大技术趋势案例(云计算大数据)

科技巨变,未来已来,八大技术趋势引领数字化时代。信息技术的迅猛发展,深刻改变了我们的生活、工作和生产方式。人工智能、物联网、云计算、大数据、虚拟现实、增强现实、区块链、量子计算等新兴技术在各行各业得到广泛应用,为各个领域带来了新的活力和变革。 为了更好地了解…

QT控件之输入窗口控件

Qt Designer窗口部件提供的面板中&#xff0c;提供了16种输入部件 &#xff08;1&#xff09;QComboBox继承QWidget类&#xff0c;被QFontComboBox类继承。通常用于用户显示选项列表的 方法&#xff0c;这种方法占用最少的屏幕空间。 &#xff08;2&#xff09;QFontComboBox继…

CI/CD实战-jenkins结合ansible

配置主机环境 在jenkins上断开并删除docker1节点 重新给master添加构建任务 将server3&#xff0c;server4作为测试主机&#xff0c;停掉其上后面的docker 在server2&#xff08;jenkins&#xff09;主机上安装ansible 设置jenkins用户到目标主机的免密 给测试主机创建用户并…

百度智能小程序源码系统简洁版 SEO关键词排名推广优化 带完整的安装代码包以及搭建教程

移动互联网的快速发展&#xff0c;小程序以其轻量级、无需下载、即用即走的特点&#xff0c;迅速成为了各大平台争相推广的重要产品形态。百度智能小程序作为百度生态下的重要一环&#xff0c;凭借其强大的流量入口和丰富的功能组件&#xff0c;为开发者提供了广阔的创作空间。…

设计模式之外观模式解析

外观模式 1&#xff09;概述 1.问题 在软件开发中&#xff0c;为完成一项较为复杂的功能&#xff0c;一个客户类需要和多个业务类交互&#xff0c;而这些需要交互的业务类经常会作为一个整体出现&#xff0c;由于涉及到的类比较多&#xff0c;导致使用时代码较为复杂。 2.作…