Linux socket编程(6):IO复用之select原理及例子

news2024/11/27 22:42:11

文章目录

  • 1 五种I/O模型
    • 1.1 阻塞I/O模型
    • 1.2 非阻塞I/O模型
    • 1.3 I/O复用模型
    • 1.4 信号驱动I/O模型
    • 1.5 异步I/O模型
  • 2 select函数
  • 3 select实战:实现多个套接字监听
    • 3.1 客户端
    • 3.2 服务端
    • 3.3 实验结果
    • 3.4 完整代码

在之前的网络编程中,我们遇到了一个问题:

  • 客户端需要一边监听来自stdin的键盘输入,一边监听来自服务端的消息

  • 服务端要一边获取来自客户端的消息,一边accept新的设备连接

也就是我们希望在一个或多个I/O条件准备就绪时,能够得到通知。在前面的文章中,我们使用Linux中的fork实现这些功能:利用fork实现服务端与多个客户端建立连接。但在Linux中还有一个I/O多路复用的概念,它由selectpoll函数实现,这篇文章就来介绍一下多路复用的概念。

1 五种I/O模型

通常,一个输入操作有两个阶段:

  1. 等待数据准备就绪:等待数据在网络上到达,当数据包到达时,它被复制到内核的缓冲区中
  2. 将数据从内核复制到进程:将准备好的数据从内核缓冲区复制到应用程序缓冲区

下面来了解一下Linux中的五种I/O模型:

1.1 阻塞I/O模型

I/O的最常见模型是阻塞I/O模型。默认情况下,所有套接字都是阻塞的。用户通过调用recv/recvfrom/read等函数阻塞等待数据的到来,这些函数称为系统调用,会从应用程序中切换到内核中运行,在得到了一定的数据后返回到应用程序。如下图所示:

在这里插入图片描述

在上图中,进程调用recvfrom,系统调用在数据到达时将数据复制到应用程序缓冲区,然后返回。或者发生错误时也会返回,最常见的错误是系统调用被信号中断。

1.2 非阻塞I/O模型

当一个套接字被设置为非阻塞时,recv/recvfrom/read会立即返回,有数据则返回数据,而没数据也不会等待条件满足,而是立即返回一个错误EWOULDBLOCK

在这里插入图片描述

对于前三次的recvfrom,没有要返回的数据,内核立即返回EWOULDBLOCK错误。 在第四次调用recvfrom时,一个数据报已准备好,它被复制到我们的应用程序缓冲区中,recvfrom成功返回。应用程序不断地轮询内核,以查看是否有某个操作准备就绪,这通常很占CPU的资源,一般很少使用。

当然除了用在读函数中,一个我实际中用到的例子是在connect函数中使用非阻塞。比如我们设备中有一个以太网,正常阻塞连接的话还需要以太网相关的驱动支持,如果在没插网线或者对端服务器不存在的情况下进行连接,根据不同内核代码的处理就会阻塞在connect函数十几二十秒再返回,而影响后面代码的执行。此时就可以设置为非阻塞,然后后续再监听套接字上是否有消息以判断是否连接成功。

1.3 I/O复用模型

I/O多路复用调用selectpoll,然后阻塞在这两个系统调用中,而不是在实际的I/O系统调用中阻塞。

在这里插入图片描述

select可以监听多个套接字,当select返回时,表示某个套接字可读/写,我们就可以执行相应的操作。

1.4 信号驱动I/O模型

内核在描述符准备就绪时通过SIGIO信号通知应用层。如下图所示:

在这里插入图片描述

  1. 激活信号驱动模型: 应用程序通过系统调用(如sigaction)向内核注册信号处理程序。信号处理程序是在特定事件发生时由内核调用的函数。对于信号驱动I/O,常用的信号包括SIGIOSIGURG
  2. 启用信号驱动套接字: 对于需要信号驱动的套接字,应用程序需要调用fcntl系统调用,将套接字设置为非阻塞,并启用FASYNC标志。这样,当套接字上的I/O事件发生时,内核会发送相应的信号给应用程序。
  3. 事件发生时的处理: 当套接字上发生I/O事件时,内核会生成相应的信号(如SIGIO)。这时,注册的信号处理程序将被调用。在信号处理程序中,应用程序可以执行必要的操作,比如读取数据、处理数据或通知主循环有关事件的发生。
  4. 异步通知: 信号驱动模型提供了一种异步通知的机制,使得应用程序可以继续执行其他任务而无需等待事件的发生。这与阻塞I/O模型不同,后者需要在等待事件时阻塞应用程序。

1.5 异步I/O模型

异步I/O由POSIX规范定义,它允许应用程序启动一个I/O操作,而无需等待这个操作完成。相比于阻塞I/O,异步I/O能够在I/O操作进行的同时执行其他任务,而不必一直等待数据的读取或写入完成。请参见下图示例:

在这里插入图片描述

  1. POSIX异步I/O函数: 在POSIX标准中,异步I/O由一组函数组成,这些函数的名称通常以aio_lio_开头,如aio_readaio_write等。
  2. 启动异步操作: 应用程序通过调用异步I/O函数来启动I/O操作,通常需要指定文件描述符、缓冲区指针、缓冲区大小、文件偏移等参数。
  3. 通知机制: 异步I/O操作的关键特点是通知机制。应用程序可以指定在I/O操作完成时如何通知它,通常是通过信号、回调函数或事件通知等方式。
  4. 立即返回: 异步I/O函数通常是非阻塞的,它们在启动I/O操作后会立即返回,而不会等待操作完成。这使得应用程序能够继续执行其他任务。
  5. 适用场景: 异步I/O常用于需要处理大量并发I/O操作的情况,例如网络服务器或高性能的文件处理应用程序。通过异步I/O,程序能够更有效地利用系统资源,提高响应性能。

同步I/O(前四种都是同步I/O)操作会导致请求的进程被阻塞,直到该I/O操作完成。而异步I/O操作不会导致请求的进程被阻塞。

2 select函数

这里主要介绍一下select在网络编程的使用。select函数可以等待多个事件中的任何一个发生,并且仅在其中一个或多个事件发生时唤醒进程。这意味着我们告诉内核我们对哪些描述符感兴趣(用于读取、写入或异常条件),以及等待的时间有多长。我们可以使用select监听任何描述符,而不仅限于套接字。

int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,
           const struct timeval *timeout);
  1. maxfdp1 :这是待测试的最大文件描述符值加一。它指定了被测试的文件描述符的范围。例如,如果最大的文件描述符是N,那么maxfdp1的值应该是N + 1。这个参数告诉select要检查多少个文件描述符。
  2. readset :这是一个指向fd_set结构的指针,用于指定希望监视其可读性的文件描述符集合。如果readset中的任何一个文件描述符变得可读,select将返回。
  3. writeset: 类似于readset,这是一个指向fd_set结构的指针,用于指定希望监视其可写性的文件描述符集合。如果writeset中的任何一个文件描述符变得可写,select将返回。
  4. exceptset: 同样是一个指向fd_set结构的指针,用于指定希望监视其异常条件的文件描述符集合。如果exceptset中的任何一个文件描述符发生异常,select将返回。
  5. timeout:这是一个指向struct timeval结构的指针,表示等待的最长时间。如果timeoutNULLselect将一直等待,直到有文件描述符就绪或出错。如果timeout不为NULL,则指定等待的最大时间,当超过这个时间后,即使没有文件描述符就绪,select也将返回。

select函数在成功时返回就绪文件描述符的总数,如果超时返回0,如果出错返回-1。可以通过检查readsetwritesetexceptset中的具体位来确定哪些文件描述符已经就绪。

这里的fd_set需要使用以下几个API来设置:

void FD_ZERO(fd_set *fdset);         /* 清除fdset中的所有位 */
void FD_SET(int fd, fd_set *fdset);  /* 置fdset中的某个位 */
void FD_CLR(int fd, fd_set *fdset);  /* 清除fdset中的某个位 */
int FD_ISSET(int fd, fd_set *fdset); /* 判断某个位是否被置 */

3 select实战:实现多个套接字监听

这里就来实现一个服务端和客户端的模型,从代码中来深入理解select函数的使用。

3.1 客户端

客户端需要能够监听标准输入stdin的消息,然后转发个服务端;还需要监听服务端的套接字,以接收服务端发来的消息。代码如下:

fd_set readfds;
while (1)
{
    FD_ZERO(&readfds);  				//清空读描述符
    FD_SET(STDIN_FILENO, &readfds);		//设置标准输入描述符
    FD_SET(clientSocket, &readfds);		//设置要监听的(服务端的)套接字
	/* 由于输入描述符为0,所以这里maxfdp1就为clientSocket+1 */
    select(clientSocket + 1, &readfds, NULL, NULL, NULL);//无限阻塞到有消息
	/* 判断哪个套接字上有消息 */
    if (FD_ISSET(STDIN_FILENO, &readfds)) {//标准输入有消息:发送给服务端
        fgets(buffer, BUFFER_SIZE, stdin);
        send(clientSocket, buffer, strlen(buffer), 0);
    }

    if (FD_ISSET(clientSocket, &readfds)) {//服务端套接字有消息:接收并打印出来
        memset(buffer, 0, sizeof(buffer));
        recv(clientSocket, buffer, BUFFER_SIZE, 0);
        printf("Server: %s", buffer);
    }
}
  • STDIN_FILENO是(通常为0)一个在头文件 <unistd.h> 中定义的宏,用于表示标准输入文件描述符的。除此之外还有标准输出的描述符STDOUT_FILENO(通常为1)和标准错误的描述符STDERR_FILENO(通常为2)。

3.2 服务端

服务端则是一边要accept新的客户端连接请求,一边接收来自客户端的消息并回显回去。代码如下:

#define BUFFER_SIZE 1024
int serverSocket, clientSockets[BUFFER_SIZE], maxSockets, activity, i, valread;
//serverSocket为服务端自身的套接字,代码略
maxSockets = serverSocket;
memset(clientSockets, 0, sizeof(clientSockets));//记录建立连接的客户端套接字,0表示没有使用
while (1)
{
    FD_ZERO(&readfds);					//清空读描述符
    FD_SET(serverSocket, &readfds);		//设置服务端套接字,用于accept客户端连接请求
	/* 设置所有已连接上的客户端的套接字 */
    for (i = 0; i < MAX_CLIENTS; i++)
    {
        int clientSocket = clientSockets[i];
        if (clientSocket > 0)
        {
            FD_SET(clientSocket, &readfds);
            if (clientSocket > maxSockets)//更新maxfdp1
                maxSockets = clientSocket;
        }
    }
	/* 监听套接字 */
    activity = select(maxSockets + 1, &readfds, NULL, NULL, NULL);
	/* 有新的客户端连接请求,这里accept它们 */
    if (FD_ISSET(serverSocket, &readfds))
    {
        int newSocket;
        socklen_t addrlen = sizeof(address);
        if ((newSocket = accept(serverSocket, (struct sockaddr*)&address, &addrlen)) < 0)
        {
            perror("Accept failed");
            exit(EXIT_FAILURE);
        }

        printf("New connection, socket fd is %d, ip is : %s, port : %d\n", newSocket, inet_ntoa(address.sin_addr), ntohs(address.sin_port));
		/* 找一个数组中没使用的项目填入 */
        for (i = 0; i < MAX_CLIENTS; i++)
        {
            if (clientSockets[i] == 0)
            {
                clientSockets[i] = newSocket;
                break;
            }
        }
    }
	/* 处理所有连接上的客户端的消息 */
    for (i = 0; i < MAX_CLIENTS; i++)
    {
        int clientSocket = clientSockets[i];
        if (FD_ISSET(clientSocket, &readfds))
        {
            valread = read(clientSocket, buffer, BUFFER_SIZE);
            /* 返回0表示客户端断开连接,这里也断开连接 */
            if (valread == 0)
            {
                // Client disconnected
                printf("Host disconnected, socket fd is %d\n", clientSocket);
                close(clientSocket);
                clientSockets[i] = 0;
            }
            /* 将收到的客户端的消息回显给客户端 */
            else
            {
                buffer[valread] = '\0';
                printf("Received: %s", buffer);
                send(clientSocket, buffer, strlen(buffer), 0);
            }
        }
    }
}

3.3 实验结果

运行服务端程序,然后打开一个客户端程序发送hello,再打开一个客户端程序发送hi!,实验效果如下:

在这里插入图片描述

3.4 完整代码

客户端

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

#define BUFFER_SIZE 1024

int main() {
    int clientSocket;
    struct sockaddr_in serverAddress;
    fd_set readfds;
    char buffer[BUFFER_SIZE];

    // Create client socket
    if ((clientSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }

    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(8888);
	serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1");

    // Connect to server
    if (connect(clientSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) < 0) {
        perror("Connection Failed");
        exit(EXIT_FAILURE);
    }

    printf("Connected to server\n");

    while (1) {
        FD_ZERO(&readfds);
        FD_SET(STDIN_FILENO, &readfds);
        FD_SET(clientSocket, &readfds);

        select(clientSocket + 1, &readfds, NULL, NULL, NULL);

        if (FD_ISSET(STDIN_FILENO, &readfds)) {
            // Read from stdin and send to server
            fgets(buffer, BUFFER_SIZE, stdin);
            send(clientSocket, buffer, strlen(buffer), 0);
        }

        if (FD_ISSET(clientSocket, &readfds)) {
            // Read from server and print
            memset(buffer, 0, sizeof(buffer));
            recv(clientSocket, buffer, BUFFER_SIZE, 0);
            printf("Server: %s", buffer);
        }
    }

    return 0;
}

服务端

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

#define MAX_CLIENTS 10
#define BUFFER_SIZE 1024

int main() {
    int serverSocket, clientSockets[MAX_CLIENTS], maxSockets, activity, i, valread;
    int opt = 1;
    struct sockaddr_in address;
    fd_set readfds;
    char buffer[BUFFER_SIZE];

    // Create server socket
    if ((serverSocket = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }

    // Set socket options
    if (setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("Setsockopt failed");
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(8888);

    // Bind the socket
    if (bind(serverSocket, (struct sockaddr*)&address, sizeof(address)) < 0) {
        perror("Bind failed");
        exit(EXIT_FAILURE);
    }

    // Listen for incoming connections
    if (listen(serverSocket, MAX_CLIENTS) < 0) {
        perror("Listen failed");
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port 8888\n");

    maxSockets = serverSocket;
    memset(clientSockets, 0, sizeof(clientSockets));

    while (1) {
        FD_ZERO(&readfds);
        FD_SET(serverSocket, &readfds);

        for (i = 0; i < MAX_CLIENTS; i++) {
            int clientSocket = clientSockets[i];
            if (clientSocket > 0) {
                FD_SET(clientSocket, &readfds);
                if (clientSocket > maxSockets) {
                    maxSockets = clientSocket;
                }
            }
        }

        activity = select(maxSockets + 1, &readfds, NULL, NULL, NULL);

        if (FD_ISSET(serverSocket, &readfds)) {
            // Handle new connection
            int newSocket;
            socklen_t addrlen = sizeof(address);
            if ((newSocket = accept(serverSocket, (struct sockaddr*)&address, &addrlen)) < 0) {
                perror("Accept failed");
                exit(EXIT_FAILURE);
            }

            printf("New connection, socket fd is %d, ip is : %s, port : %d\n", newSocket, inet_ntoa(address.sin_addr), ntohs(address.sin_port));

            for (i = 0; i < MAX_CLIENTS; i++) {
                if (clientSockets[i] == 0) {
                    clientSockets[i] = newSocket;
                    break;
                }
            }
        }

        for (i = 0; i < MAX_CLIENTS; i++) {
            int clientSocket = clientSockets[i];
            if (FD_ISSET(clientSocket, &readfds)) {
                // Handle data from client
                valread = read(clientSocket, buffer, BUFFER_SIZE);
                if (valread == 0) {
                    // Client disconnected
                    printf("Host disconnected, socket fd is %d\n", clientSocket);
                    close(clientSocket);
                    clientSockets[i] = 0;
                } else {
                    // Echo received message back to client
                    buffer[valread] = '\0';
                    printf("Received: %s", buffer);
                    send(clientSocket, buffer, strlen(buffer), 0);
                }
            }
        }
    }

    return 0;
}

这里都没有判断select函数的返回值,我们最好也判断一下

if (activity == -1) {
    // 错误发生
    if (errno == EINTR) 
        continue;//继续下一次select
    perror("select");
    exit(EXIT_FAILURE);
} else if (activity == 0) {
    // 超时
} else {
    // 有文件描述符准备好
}
  • select返回值-1且errnoEINTR时,表示select被中断。这通常是由于接收到信号而导致的中断。在这种情况下,你可以选择重新调用select

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

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

相关文章

【nowcoder】BM3 链表中的节点每k个一组翻转

题目&#xff1a; 题目分析&#xff1a; 题目解析转载&#xff1a; 代码实现&#xff1a; package BMP3;import java.util.List;class ListNode {int val;ListNode next null;public ListNode(int val) {this.val val;} } public class BM3 {/*** 代码中的类名、方法名、参…

LINUX入门篇【9】----进程篇【1】----进程的初步认识和理解---进程的标识符以及对应的系统调用函数

前言&#xff1a; 从而本章开始&#xff0c;我们将进行进程的正式学习和讲解&#xff0c;进程是我们的程序驱动最重要的一环&#xff0c;可以说&#xff0c;进程几乎承载着一个程序在冯诺依曼体系和操作系统交互的全部&#xff0c;因此&#xff0c;学好进程是我们下一步系统化…

接口测试快速入门 以飞致云平台为例

飞致云电商API地址系统来自飞致云项目。接口API地址&#xff1a;https://gz.fit2cloud.com/swagger-ui.html 飞致云电商系统接口文档 V1.0&#xff1a;见 有道云笔记 该网站可以做接口测试练习。快速了解如何测试接口&#xff0c;如何做关联 系统基地址&#xff1a;https://g…

Flutter | 设置顶部状态栏的显示、隐藏、半透明灰色显示

【Flutter】设置顶部状态栏的显示、隐藏、半透明灰色显示 设置方法&#xff1a; // 这种模式不现实状态栏 SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); // 这种模式显示状态栏 SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); // 修…

HarmonyOS开发者工具DevEco Studio-汉化

HarmonyOS DevEco Studio 简介 下载安装及汉化 打开开发者工具 安装语言包重启 然后设置页搜索“chinese”&#xff0c;选中中文语言包&#xff0c;点击后面的install&#xff1b; 或者 汉化按照IDEA的汉法风格&#xff0c;需要安装插件重启就可以汉化&#xff0c;步骤为&…

【JavaScript】alert的使用方法 | 超详细

alert作用效果 alert&#xff08;&#xff09;方法用于显示带有一条指定消息和一个确认的按钮的警告框。 alert使用方法 方法一&#xff1a;直接写在script标签内 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"&…

RT-DETR论文阅读笔记(包括YOLO版本训练和官方版本训练)

论文地址&#xff1a;RT-DETR论文地址 代码地址&#xff1a;RT-DETR官方下载地址 大家如果想看更详细训练、推理、部署、验证等教程可以看我的另一篇博客里面有更详细的介绍 内容回顾&#xff1a;详解RT-DETR网络结构/数据集获取/环境搭建/训练/推理/验证/导出/部署 目录 一…

2023/11/26总结

一些学习记录&#xff1a; 在对数据库进行一系列操作的时候&#xff0c;遇到一个问题&#xff0c;在插入数据的时候&#xff0c;我数据库对应的是自增id&#xff0c;但是插入后想获取到这个id去使用。我以为是不可以马上获取的&#xff0c;然后看到 项目进度 购物车&#xff…

【计算机网络笔记】多路访问控制(MAC)协议——轮转访问MAC协议

系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能&#xff08;1&#xff09;——速率、带宽、延迟 计算机网络性能&#xff08;2&#xff09;…

C++前缀和算法:统计美丽子字符串

题目 给你一个字符串 s 和一个正整数 k 。 用 vowels 和 consonants 分别表示字符串中元音字母和辅音字母的数量。 如果某个字符串满足以下条件&#xff0c;则称其为 美丽字符串 &#xff1a; vowels consonants&#xff0c;即元音字母和辅音字母的数量相等。 (vowels * cons…

光线追踪-Peter Shirley的RayTracingInOneWeekend系列教程(book1-book3)代码分章节整理

自己码完了一遍了&#xff0c;把代码分章节整理了一下&#xff0c;可以按章节独立编译&#xff0c;运行, 也可以直接下载编译好的release版本直接运行。 项目地址&#xff1a; Github: https://github.com/disini/RayTracingInOneWeekendChaptByChapt ​ ​ ​ ​

Rust语言入门教程(八) - 引用与借用

上一章的内容中我们讨论了Rust的所有权系统&#xff0c;当我们不想移动值的所有权时&#xff0c;我们可以使用引用和借用&#xff0c;而这正是本章想要讨论的问题。 引用&#xff08;References&#xff09; 引用允许你访问或修改数据而无需获取数据的所有权。在 Rust 中&…

Camtasia Studio2024专业的屏幕录制和视频剪辑软件

Camtasia2024专业的屏幕录制和视频剪辑软件3000多万专业人士在全球范围内使用Camtasia展示产品&#xff0c;教授课程&#xff0c;培训他人&#xff0c;以更快的速度和更吸引人的方式进行沟通和屏幕分享。使您在Windows和Mac上进行录屏和剪辑创作专业外观的视频变得更为简单。 …

【刷题笔记】加油站||符合思维方式

加油站 文章目录 加油站1 题目描述2 思路3 解题方法 1 题目描述 https://leetcode.cn/problems/gas-station/ 在一条环路上有 n 个加油站&#xff0c;其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车&#xff0c;从第 i 个加油站开往第 i1 个加油站需要消…

Blender 连续 5 天遭受大规模 DDoS 攻击

Blender 发布公告指出&#xff0c;在2023年11月18日至23日期间&#xff0c;blender.org 网站遭受了持续的分布式拒绝服务&#xff08;DDoS&#xff09;攻击&#xff0c;攻击者通过不断发送请求导致服务器超载&#xff0c;使网站运营严重中断。此次攻击涉及数百个 IP 地址的僵尸…

高并发系统:它的通用设计方法是什么?

Java全能学习面试指南&#xff1a;https://javaxiaobear.cn 我们知道&#xff0c;高并发代表着大流量&#xff0c;高并发系统设计的魅力就在于我们能够凭借自己的聪明才智设计巧妙的方案&#xff0c;从而抵抗巨大流量的冲击&#xff0c;带给用户更好的使用体验。这些方案好似能…

电子学会C/C++编程等级考试2021年09月(三级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:余数相同问题 已知三个正整数 a,b,c。 现有一个大于1的整数x,将其作为除数分别除a,b,c,得到的余数相同。 请问满足上述条件的x的最小值是多少? 数据保证x有解。输入: 一行,三个不大于1000000的正整数a,b,c,两个整数…

ChatGPT初体验:注册、API Key获取与ChatAPI调用详解

自从2022年10月&#xff0c;ChatGPT诞生以后&#xff0c;实际上已经改变了很多&#xff01;其火爆程度简直超乎想象&#xff0c;一周的时间用户过百万&#xff0c;两个月的时间用户过亿。 目前ChatGPT4已经把2023年4月以前的人类的知识都学习到了&#xff0c;在软件工程里面&am…

因子分析例题(多元统计分析期末复习)

例一 设某客观现象可用 X {X} X( X 1 {X_1} X1​&#xff0c; X 2 {X_2} X2​&#xff0c; X 3 {X_3} X3​)’ 来描述&#xff0c;在因子分析时&#xff0c;从约相关阵出发计算特征值为 λ 1 {λ_1} λ1​1.754&#xff0c; λ 2 {λ_2} λ2​1&#xff0c; λ 3 {λ_3} λ3​…

3.数据结构

3.1 数据结构分类 常见的数据结构包括数组、链表、栈、队列、哈希表、树、堆、图&#xff0c;它们可以从“逻辑结构”和“物理结构”两个维度进行分类。 3.1.1逻辑结构&#xff1a;线性与非线性 逻辑结构揭示了数据元素之间的逻辑关系。在数组和链表中&#xff0c;数据按照…