【网络进阶】五种IO网络模型(一)

news2024/7/4 6:11:21

文章目录

    • 1. 阻塞IO
    • 2. 非阻塞IO

1. 阻塞IO

在Linux中,默认情况下,所有的套接字(socket)都是阻塞的。典型的读取操作流程如下:
在这里插入图片描述

当用户进程调用read系统调用时,内核开始执行I/O的第一个阶段,即准备数据。对于网络I/O来说,通常数据在一开始尚未到达(例如,尚未接收到一个完整的数据包),这时内核需要等待足够的数据到来。在用户进程这边,整个进程将被阻塞。当内核等到数据准备就绪,它会将数据从内核空间拷贝到用户空间,然后内核返回结果,用户进程才解除阻塞状态,重新运行起来。

因此,阻塞I/O的特点是在I/O执行的两个阶段(等待数据和拷贝数据)都被阻塞。

大多数程序员接触网络编程时,首先了解的是listen()、send()、recv()等阻塞型接口。使用这些接口可以方便地构建服务器/客户端模型。以下是一个简单的“一问一答”服务器示例:

在这里插入图片描述

绝大多数套接字接口都是阻塞型的。所谓阻塞型接口是指系统调用(通常是I/O接口)在没有返回调用结果的情况下让当前线程一直阻塞,只有当该系统调用获得结果或者超时出错时才返回。

实际上,除非特别指定,几乎所有的I/O接口(包括套接字接口)都是阻塞型的。这给网络编程带来了一个重大问题,即在调用send()的同时,线程将被阻塞,在此期间,线程将无法执行任何计算或响应任何网络请求。

一种简单的改进方案是在服务器端使用多线程(或多进程)。多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他连接。具体使用多进程还是多线程,并没有一个特定的模式。传统上,进程的开销远大于线程,因此,如果需要同时为大量客户端提供服务,不推荐使用多进程;如果单个服务执行体需要消耗较多的CPU资源,例如需要进行大规模或长时间的数据运算或文件访问,那么进程较为安全。通常,可以使用pthread_create()创建新线程,或使用fork()创建新进程。

假设对上述服务器/客户端模型提出更高要求,即让服务器同时为多个客户端提供一问一答服务,于是有了如下模型:

在这里插入图片描述

在上述线程/时间示例中,主线程持续等待客户端的连接请求。如果有连接,则创建新线程,并在新线程中提供与前例相同的问答服务。

许多初学者可能不明白为什么一个套接字可以多次accept。实际上,套接字的设计者可能为多客户端场景特意设计了这个功能,使得accept()能够返回一个新的套接字。以下是accept接口的原型:

int accept(int s, struct sockaddr *addr, socklen_t *addrlen);

输入参数s是从socket()、bind()和listen()中继承下来的套接字句柄值。执行完bind()和listen()后,操作系统已经开始在指定的端口监听所有连接请求。如果有请求,则将该连接请求加入请求队列。调用accept()接口正是从套接字s的请求队列中提取第一个连接信息,创建一个与s同类型的新套接字并返回句柄。新的套接字句柄将作为后续read()和recv()的输入参数。如果请求队列当前没有请求,则accept()将进入阻塞状态,直到有请求进入队列。

上述多线程服务器模型似乎完美地解决了为多个客户端提供问答服务的要求,但其实并非如此。如果要同时响应数百或数千个连接请求,则无论多线程还是多进程都会严重占据系统资源,降低系统对外界响应效率,而线程与进程本身也更容易进入假死状态。

许多程序员可能会考虑使用“线程池”或“连接池”。“线程池”旨在减少创建和销毁线程的频率,其维持一定合理数量的线程,并让空闲的线程重新承担新的执行任务。“连接池”维持连接的缓存池,尽量重用已有的连接,减少创建和关闭连接的频率。这两种技术都可以很好地降低系统开销,并广泛应用于许多大型系统,如Websphere、Tomcat和各种数据库等。但是,“线程池”和“连接池”技术也只是在一定程度上缓解了频繁调用I/O接口带来的资源占用。而且,所谓“池”始终有其上限,当请求大大超过上限时,“池”构成的系统对外界的响应并不比没有池的时候效果好多少。因此,在使用“池”时,必须考虑其面临的响应规模,并根据响应规模调整“池”。

针对上例中可能同时出现的数千甚至数万次客户端请求,“线程池”或“连接池”或许可以缓解部分压力,但不能解决所有问题。总之,多线程模型可以方便高效地解决小规模的服务请求,但面对大规模的服务请求,多线程模型也会遇到瓶颈。这时,可以尝试使用非阻塞接口来解决这个问题。

非阻塞I/O(Non-blocking I/O)或异步I/O(Asynchronous I/O)是另一种处理大量并发连接的方法。在这种模式下,I/O操作不会阻塞当前线程,而是立即返回。如果I/O操作尚未完成,系统调用将返回一个特殊的错误码,通知调用者稍后再试。这使得服务器能够在等待某个I/O操作完成的同时,处理其他客户端的请求。

实现非阻塞I/O的一种方法是使用事件驱动编程(Event-driven programming)。在这种模式下,服务器维护一个事件循环(Event loop),用于监听各种I/O事件,如新连接请求、数据到达等。当事件发生时,事件循环将调用相应的回调函数来处理事件。这样,服务器可以在一个单线程中处理多个客户端的请求,避免了多线程或多进程带来的开销。

在Linux中,有多种实现事件驱动编程的机制,如select、poll和epoll。这些机制允许程序员在一个线程中监视多个文件描述符(如套接字)的状态,并在某个文件描述符就绪时(如可读、可写)进行处理。这些机制的主要区别在于性能和可扩展性,其中epoll在处理大量并发连接时具有较好的性能。

总之,对于大规模并发连接的场景,非阻塞I/O和事件驱动编程提供了一种有效的解决方案。通过使用select、poll或epoll等机制,可以实现高性能、可扩展的服务器,避免了多线程或多进程带来的资源开销和管理复杂性。


C语言socket例子:

该例子是一个TCP服务器,它接受客户端的连接,接收客户端发送的消息,然后将消息原样返回给客户端。

server.c

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

#define BUF_SIZE 1024
#define SERVER_PORT 8080

int main() {
    int server_socket, client_socket;
    struct sockaddr_in server_addr, client_addr;
    char buffer[BUF_SIZE];
    socklen_t client_addr_size;

    // 创建服务器socket
    server_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (server_socket == -1) {
        perror("socket creation failed");
        exit(1);
    }

    // 设置服务器地址结构体
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(SERVER_PORT);

    // 绑定socket到服务器地址
    if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind failed");
        exit(1);
    }

    // 监听socket
    if (listen(server_socket, 5) == -1) {
        perror("listen failed");
        exit(1);
    }

    client_addr_size = sizeof(client_addr);
    while (1) {
        // 接受客户端连接(阻塞式)
        client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_addr_size);
        if (client_socket == -1) {
            perror("accept failed");
            exit(1);
        }

        // 接收客户端消息并将其返回(阻塞式)
        int read_size;
        while ((read_size = read(client_socket, buffer, BUF_SIZE)) != 0) {
            printf("从客户端收到的消息:%s\n", buffer);
            write(client_socket, buffer, read_size);
            memset(buffer, 0, sizeof(buffer));
        }

        close(client_socket);
    }

    close(server_socket);
    return 0;
}

client.c

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

#define BUF_SIZE 1024
#define SERVER_PORT 8080

int main() {
    int client_socket;
    struct sockaddr_in server_addr;
    char buffer[BUF_SIZE];

    // 创建客户端socket
    client_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (client_socket == -1) {
        perror("socket creation failed");
        exit(1);
    }

    // 设置服务器地址结构体
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    server_addr.sin_port = htons(SERVER_PORT);

    // 连接到服务器(阻塞式)
    if (connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("connect failed");
        exit(1);
    }

    while (1) {
        printf("请输入消息:");
        fgets(buffer, BUF_SIZE, stdin);
        buffer[strlen(buffer) - 1] = '\0';
        // 向服务器发送消息
        write(client_socket, buffer, strlen(buffer));

        // 接收服务器响应(阻塞式)
        int read_size = read(client_socket, buffer, BUF_SIZE - 1);
        if (read_size == -1) {
            perror("read failed");
            exit(1);
        }

        buffer[read_size] = '\0';
        printf("从服务器接收到的消息:%s\n", buffer);
    }

    close(client_socket);
    return 0;
}

在这里插入图片描述

2. 非阻塞IO

在Linux下,可以通过设置套接字使其变为非阻塞。对于一个非阻塞套接字进行读操作时,流程如下:

在这里插入图片描述

从上述过程中可以看出,当用户进程发起read操作时,如果内核中的数据尚未准备好,它不会阻塞用户进程,而是立即返回一个错误。从用户进程的角度看,发起一个read操作后,不需要等待,而是马上得到一个结果。用户进程判断结果为错误时,知道数据还没有准备好,于是可以再次发起read操作。一旦内核中的数据准备好了,并再次收到用户进程的系统调用,它会立即将数据拷贝到用户内存,然后返回。因此,在非阻塞I/O中,用户进程实际上需要不断主动询问内核数据是否准备好。

在非阻塞状态下,recv()接口被调用后立即返回,返回值表示不同的含义。例如,在本例中:

  • recv()返回值大于0,表示接收数据完毕,返回值即为接收到的字节数
  • recv()返回值为0,表示连接已经正常断开
  • recv()返回值为-1且errno等于EAGAIN,表示recv操作尚未完成
  • recv()返回值为-1且errno不等于EAGAIN,表示recv操作遇到系统错误errno。

非阻塞接口与阻塞接口的显著差异在于,调用后立即返回。可以使用以下函数将某个文件描述符fd设置为非阻塞状态:

fcntl(fd, F_SETFL, O_NONBLOCK);

下面将展示一个仅使用一个线程,但能够同时检测多个连接中的数据是否到达,并接收数据的模型:

在这里插入图片描述

可以看到,服务器线程可以通过循环调用recv()接口,在单个线程内实现对所有连接的数据接收工作。但是,这种模型并不推荐使用。因为循环调用recv()会大幅度提高CPU占用率;此外,在这个方案中,recv()更多地起到检测“操作是否完成”的作用,实际上操作系统提供了更为高效的检测“操作是否完成”的接口,如select()多路复用模式,可以一次检测多个连接是否活跃。使用select()等多路复用技术,能够更高效地处理并发连接,减少CPU占用,提高服务器性能。


C语言socket例子:

可以使用fcntl()函数将socket设置为非阻塞模式,并使用select()函数在读/写操作上实现异步。下面同样是一个TCP服务器,它接受客户端的连接,接收客户端发送的消息,然后将消息原样返回给客户端。

server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <sys/select.h>

#define BUF_SIZE 1024
#define SERVER_PORT 8080

int main() {
    int server_socket, client_socket;
    struct sockaddr_in server_addr, client_addr;
    char buffer[BUF_SIZE];
    socklen_t client_addr_size;
    fd_set read_fds, temp_fds;
    int max_fd;

    // 创建服务器socket
    server_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (server_socket == -1) {
        perror("socket creation failed");
        exit(1);
    }

    // 设置服务器地址结构体
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(SERVER_PORT);

    // 绑定socket到服务器地址
    if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind failed");
        exit(1);
    }

    // 监听socket
    if (listen(server_socket, 5) == -1) {
        perror("listen failed");
        exit(1);
    }

    // 设置服务器socket为非阻塞模式
    int flags = fcntl(server_socket, F_GETFL, 0);
    fcntl(server_socket, F_SETFL, flags | O_NONBLOCK);

    FD_ZERO(&read_fds);
    FD_SET(server_socket, &read_fds);
    max_fd = server_socket;

    client_addr_size = sizeof(client_addr);
    while (1) {
        temp_fds = read_fds;

        // 使用select()函数监控文件描述符集合
        if (select(max_fd + 1, &temp_fds, NULL, NULL, NULL) == -1) {
            perror("select failed");
            exit(1);
        }

        for (int i = 0; i <= max_fd; i++) {
            if (FD_ISSET(i, &temp_fds)) {
                if (i == server_socket) {
                    // 接受客户端连接(非阻塞式)
                    client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_addr_size);
                    if (client_socket == -1) {
                        perror("accept failed");
                        exit(1);
                    }

                    // 将客户端socket添加到文件描述符集合
                    FD_SET(client_socket, &read_fds);
                    if (client_socket > max_fd) {
                        max_fd = client_socket;
                    }
                } else {
                    // 接收客户端消息并将其返回(非阻塞式)
                    int read_size = read(i, buffer, BUF_SIZE - 1);
                    if (read_size <= 0) {
                        close(i);
                        FD_CLR(i, &read_fds);
                    } else {
                        write(i, buffer, read_size);
                    }
                }
            }
        }
    }

    close(server_socket);
    return 0;
}

client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <sys/select.h>

#define BUF_SIZE 1024
#define SERVER_PORT 8080

int main() {
    int client_socket;
    struct sockaddr_in server_addr;
    char buffer[BUF_SIZE];
    fd_set read_fds, temp_fds;
    int max_fd;

    // 创建客户端socket
    client_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (client_socket == -1) {
        perror("socket creation failed");
        exit(1);
    }

    // 设置服务器地址结构体
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    server_addr.sin_port = htons(SERVER_PORT);

    // 连接到服务器(阻塞式)
    if (connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("connect failed");
        exit(1);
    }

    // 设置客户端socket为非阻塞模式
    int flags = fcntl(client_socket, F_GETFL, 0);
    fcntl(client_socket, F_SETFL, flags | O_NONBLOCK);

    FD_ZERO(&read_fds);
    FD_SET(client_socket, &read_fds);
    FD_SET(STDIN_FILENO, &read_fds);
    max_fd = client_socket;

    while (1) {
        temp_fds = read_fds;

        // 使用select()函数监控文件描述符集合
        if (select(max_fd + 1, &temp_fds, NULL, NULL, NULL) == -1) {
            perror("select failed");
            exit(1);
        }

        if (FD_ISSET(STDIN_FILENO, &temp_fds)) {
            printf("请输入消息:");
            fgets(buffer, BUF_SIZE, stdin);
            buffer[strlen(buffer) - 1] = '\0';

            // 向服务器发送消息
            write(client_socket, buffer, strlen(buffer));
        }

        if (FD_ISSET(client_socket, &temp_fds)) {
            // 接收服务器响应(非阻塞式)
            int read_size = read(client_socket, buffer, BUF_SIZE - 1);
            if (read_size <= 0) {
                close(client_socket);
                exit(1);
            }

            buffer[read_size] = '\0';
            printf("从服务器接收到的消息:%s\n", buffer);
        }
    }

    close(client_socket);
    return 0;
}

在这里插入图片描述

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

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

相关文章

智慧医院智能化系统设计与能耗管理产品选型

摘要&#xff1a;结合某知名大型三甲综合医院项目的智能化系统设计&#xff0c;提出智慧医院智能化系统的技术解决方案&#xff0c;阐述智慧医院智能化系统方案的总体架构、建设目标、设计宗旨、典型应用及各智能化子系统的设计方案。 关键词&#xff1a;智慧医院&#xff1b;智…

mybatis3源码篇(2)——执行流程

mybatis 版本&#xff1a;v3.3.0 文章目录 执行流程MapperProxyFactoryMapperProxyMapperMethodexecuteconvertArgsToSqlCommandParamResultHandler SqlSessionExecutor&#xff08;执行器&#xff09;StatementHandler&#xff08;声明处理器&#xff09;ParameterHandler&…

【设计模式】我对设计模式的C语言解读(下)

书接上回 由于内容太多&#xff0c;编辑器太卡了&#xff0c;所以分P了 上P在这里 目录 书接上回备忘录模式观察者模式 备忘录模式 备忘录模式的介绍: https://refactoringguru.cn/design-patterns/memento 备忘录模式的C实现: https://refactoringguru.cn/design-patterns/m…

【数据挖掘与商务智能决策】第十三章 数据降维之PCA 主成分分析

13.1.2 PCA主成分分析代码实现 1.二维空间降维Python代码实现 import numpy as np X np.array([[1, 1], [2, 2], [3, 3]]) Xarray([[1, 1],[2, 2],[3, 3]])# 也可以通过pandas库来构造数据&#xff0c;效果一样 import pandas as pd X pd.DataFrame([[1, 1], [2, 2], [3, 3…

二分查找【数组】

⭐前言⭐ ※※※大家好&#xff01;我是同学〖森〗&#xff0c;一名计算机爱好者&#xff0c;今天让我们进入复习模式。若有错误&#xff0c;请多多指教。更多有趣的代码请移步Gitee &#x1f44d; 点赞 ⭐ 收藏 &#x1f4dd;留言 都是我创作的最大的动力&#xff01; 题目 70…

接口测试用例设计思路

&#xff08;我的公众号“墨石测试攻略”&#xff0c;关注获取软件测试相关知识及整套接口测试实战项目&#xff01;&#xff09; 接口测试用例的设计&#xff0c;从功能测试角度来说&#xff1a;首先需要分析接口文档。 现在很多公司都使用swagger来管理接口。swagger中可以…

fMRI时间序列振幅和相位对功能连接分析的影响

导读 目的&#xff1a;fMRI领域的一些研究使用瞬时相位(IP)表征(源自BOLD时间序列的解析表征)考察了脑区之间的同步性。本研究假设来自不同脑区的瞬时振幅(IA)表征可以为脑功能网络提供额外的信息。为此&#xff0c;本研究探索了静息态BOLD fMRI信号的这种表征&#xff0c;用于…

SpringBoot AnnotationFormatterFactory接口+自定义注解实现类型转换

参考资料 自定义AnnotationFormatterFactory实现注解方式类型转换Spring MVC 基于AnnotationFormatterFactory接口实现自定义的规则 目录 一. 前期准备1.1. 自定义转换标记注解1.2 入参form 二. 实现AnnotationFormatterFactory接口&#xff0c;构建格式化Factory2.1 code补全…

【7】一篇文章学习 Linux 中一些硬核的常用知识

目录 一、systemctl二、软链接三、日期&#xff08;date 命令&#xff09;四、Linux 的时区(1) 修改时区(2) ntp 五、IP 地址六、主机名七、域名解析八、配置 Linux 的固定 IP 地址(1) 在 VMwareWorkstation 中配置 IP 地址网关和网段&#xff08;IP 地址的范围&#xff09;(2)…

[陇剑杯 2021]之Misc篇(NSSCTF)刷题记录④

NSSCTF-Misc篇-[陇剑杯 2021] jwt&#xff1a;[陇剑杯 2021]jwt&#xff08;问1&#xff09;[陇剑杯 2021]jwt&#xff08;问2&#xff09;[陇剑杯 2021]jwt&#xff08;问3&#xff09;[陇剑杯 2021]jwt&#xff08;问4&#xff09;[陇剑杯 2021]jwt&#xff08;问5&#xff0…

洗地性价比高的是哪款?性价比高的洗地机推荐

在如今人工智能随处可见的时代&#xff0c;洗地机已经成为了我们家庭清洁的得力助手&#xff0c;它用高效便捷的清洁方式&#xff0c;对于地面的灰尘或者地板之间的缝隙里的细小垃圾&#xff0c;能够快速清理&#xff0c;省时省力。然而&#xff0c;对于很多消费者来说&#xf…

一文带你学会如何写一份糟糕透顶的简历

我们每个人几乎都会面对找工作这件事&#xff0c;而找工作或者说求职首先就是要写一份简历。今天狗哥将以一个不同的视角带你写一份无与伦比&#xff0c;糟糕透顶的求职简历&#xff0c;说实话&#xff0c;其实几年前&#xff0c;我就是这么写的。 目录 1. 文件名 2. 基本信…

AttributeError: ‘ChatGLMModel‘ object has no attribute ‘prefix_encoder‘

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

基于蛋白-配体复合物药效团药物设计(Pharmacophore)

基于蛋白-配体复合物药效团药物设计&#xff08;Pharmacophore&#xff09; step 1.蛋白-配体复合物准备 点击File-->Import Structures导入之前已经下载好的1IEP.pdb&#xff08;Abl蛋白和Imatinib的晶体复合物&#xff09; 蛋白准备&#xff1a;点击Tasks--->Protei…

18、越狱

一、越狱 1.1 越狱 通过iOS系统安全启动链漏洞,从而禁止掉信任链中负责验证的组件.拿到iOS系统最大权限Root权限 1.2 iOS系统安全启动链 当启动一台iOS设备时,系统首先会从只读的ROM中读取初始化指令,也就是系统的引导程序(事实上所有的操作系统启动时都要经过这一步,只是过程…

Pytorch深度学习笔记(十一)卷积神经网络CNN

目录 1.概述 2.单通道卷积 3.多通道卷积 4.卷积层常见的参数 5.代码实现&#xff08;卷积神经网络训练MNIST数据集&#xff09; 推荐课程&#xff1a;10.卷积神经网络&#xff08;基础篇&#xff09;_哔哩哔哩_bilibili 1.概述 全连接神经网络&#xff1a;完全由线性层串…

最佳实践|如何写出简单高效的 Flink SQL?

摘要&#xff1a;本文整理自阿里巴巴高级技术专家、Apache Flink PMC 贺小令&#xff0c;在 Flink Forward Asia 2022 生产实践专场的分享。本篇内容主要分为三个部分&#xff1a; 1. Flink SQL Insight 2. Best Practices 3. Future Works Tips&#xff1a;点击「阅读原文」查…

android之 Launcher改造仿桌面排版的效果

一&#xff0c;背景 1.1 新接手一个灯光控制项目&#xff0c;其页面和效果还是比交复杂的&#xff0c;其中一个功能就是仿苹果桌面来排版灯具&#xff0c;支持拖拽&#xff0c;分组&#xff0c;分页。 拖动图标的时候判断是否空白位置还是已经有占位了&#xff0c;有的话就把…

pikachu靶场-RCE

RCE漏洞概述 可以让攻击者直接向后台服务器远程注入操作系统命令或者代码&#xff0c;从而控制后台系统。 远程系统命令执行 命令执行漏洞&#xff08;Command Execution&#xff09;即黑客可以直接在Web应用中执行系统命令&#xff0c;从而获取敏感信息或者拿下shell权限 更…

Linux离线状态下安装cuda、cudnn、cudatoolkit

目录 1. 下载与安装说明2. CUDA安装3. cuDNN安装4. cudatoolkit安装5. 测试安装成功 1. 下载与安装说明 工具包下载地址 CUDA历史版本下载地址&#xff1a;https://developer.nvidia.com/cuda-toolkit-archivecuDNN历史版本下载地址&#xff1a;https://developer.nvidia.com/r…