【网络进阶】服务器模型Reactor与Proactor

news2024/11/24 6:28:34

文章目录

    • 1. Reactor模型
    • 2. Proactor模型
    • 3. 同步IO模拟Proactor模型

在高并发编程和网络连接的消息处理中,通常可分为两个阶段:等待消息就绪和消息处理。当使用默认的阻塞套接字时(例如每个线程专门处理一个连接),这两个阶段往往是合并的。因此,处理套接字的线程需要等待消息就绪,这在高并发场景下导致线程频繁地休眠和唤醒,从而影响 CPU 使用效率。

为提高高并发编程效率,可将等待消息就绪和消息处理两个阶段分开。换言之,等待消息就绪的代码段与处理消息的代码段应分离。这要求套接字必须为非阻塞,否则处理消息的代码段可能导致线程在条件不满足时进入等待状态。实现等待消息就绪阶段的方法是线程主动查询,或让单个线程等待所有连接。这就引入了 I/O 多路复用技术,它可以同时处理多个连接,尽管仍可能需要等待并导致线程休眠,但由于它可以监控所有连接,所以当线程被唤醒时,必定有一些连接已准备好进行处理。

高性能服务器程序通常需要处理三类事件:I/O 事件,定时事件以及信号。为实现高效的事件处理,有两种主要模型:Reactor 和 Proactor。这两种模型分别采用不同的策略来解决等待消息就绪和消息处理阶段的问题,以提高服务器在高并发环境下的性能表现。

1. Reactor模型

首先让我们回顾一下常规函数调用的机制:程序调用某个函数,函数执行,程序等待,然后函数将结果和控制权返回给程序,程序继续处理。与此不同,Reactor(反应堆)是一种事件驱动的机制。它颠覆了传统的事件处理流程:应用程序不再主动调用某个 API 完成处理,而是将处理逻辑逆置。在这种模式下,应用程序需要提供相应的接口并将其注册到 Reactor 上。当相应的事件发生时,Reactor 会主动调用应用程序注册的接口,这些接口通常被称为“回调函数”。通过这种方式,Reactor 实现了事件驱动编程,提高了程序的响应速度和效率。
在这里插入图片描述
Reactor 模式是处理并发 I/O 的一种常见模式,适用于同步 I/O。其核心思想是将所有待处理的 I/O 事件注册到一个中心 I/O 多路复用器上,同时让主线程/进程在多路复用器上阻塞。当有 I/O 事件到来或准备就绪(如文件描述符或 socket 可读、写)时,多路复用器返回并将事先注册的相应 I/O 事件分发到对应的处理器中。这样,Reactor 模式能够实现高效的并发 I/O 处理,提高程序在高并发环境下的性能表现。

Reactor模型有三个重要的组件:

  • Reactor:Reactor 是核心组件,负责管理事件循环和I/O事件分发。它接收客户端连接,监听I/O事件,并将这些事件分发给相应的事件处理器。Reactor可以是单线程或多线程的,根据具体应用场景和性能要求进行选择。
  • Handlers:事件处理器(Handlers)是用于处理特定I/O事件的对象。每个处理器通常对应一个客户端连接或一个资源(如文件、套接字等)。处理器负责处理与它们关联的事件,如读取数据、处理业务逻辑、发送响应等。处理器可以是同步或异步的,取决于具体的实现方式。
  • Demultiplexer:事件分离器(Demultiplexer)负责从操作系统中获取I/O事件,并将这些事件传递给Reactor。常见的事件分离器实现方式有 select, poll, epoll, kqueue 等。事件分离器使Reactor能够同时处理多个I/O事件,从而实现高并发性能。

在这里插入图片描述

事件处理的具体流程:

  • Reactor 接收客户端连接,将连接的相关I/O事件注册到事件分离器(Demultiplexer)上。同时,Reactor 将事件处理器(Handlers)与这些I/O事件关联起来。
  • 事件分离器(Demultiplexer)负责检测并收集操作系统中发生的I/O事件。当有I/O事件发生时,事件分离器将这些事件通知Reactor。
  • Reactor 将收到的I/O事件与之前注册的事件处理器(Handlers)进行匹配。找到匹配的处理器后,Reactor 将事件分发给相应的处理器。
  • 事件处理器(Handlers)收到分发的I/O事件后,执行相应的操作,如读取数据、处理业务逻辑、发送响应等。
  • 事件处理完成后,Reactor 可以根据需要更新处理器与I/O事件之间的关联,以便继续处理后续事件。

Reactor模型的优点:

  • 高并发性能:Reactor模式通过事件驱动的方式实现了高并发性能。事件分离器可以同时检测多个I/O事件,Reactor可以根据事件分离器的通知并行处理多个I/O事件,从而提高服务器的并发处理能力。
  • 良好的可扩展性:Reactor模式可以通过增加线程数量或使用多个Reactor实例来提高系统的可扩展性。在多核处理器环境下,可以利用多线程Reactor模式有效地分摊负载,进一步提高系统性能。
  • 资源利用率高:Reactor模式中,事件处理器只在有I/O事件发生时才执行操作。这样可以避免无效的轮询和资源浪费,提高系统资源的利用率。
  • 易于管理和维护:Reactor模式将事件处理器和I/O事件分离,使得事件处理逻辑更加清晰和易于管理。此外,Reactor模式还可以实现处理器的动态注册和注销,便于系统的维护和扩展。
  • 可适应不同场景:Reactor模式可以根据不同的应用场景和性能要求进行定制。例如,可以选择单线程或多线程Reactor,同步或异步处理器等,以满足特定应用的需求。

Reactor 模型在开发效率方面相较于直接使用 IO 复用有所提高。该模型通常采用单线程设计,目标是让单线程充分利用一颗 CPU 的全部资源。同时,它还带有一个优点,即在处理事件时,很多情况下无需考虑共享资源的互斥访问问题。然而,这个模型也存在明显的缺点。随着硬件的发展,摩尔定律不再适用,CPU 频率由于材料限制无法继续大幅提升,因此提升性能主要依赖于增加核数。在需要利用多核资源的程序中,Reactor 模型的表现可能会受到影响。

对于业务较简单的程序,如仅需访问提供并发访问的服务,可以直接启用多个反应堆,每个反应堆对应一颗 CPU 核心。在这种情况下,运行在各个反应堆上的请求互不相关,从而充分利用多核资源。例如,Nginx 这类的 HTTP 静态服务器就采用了这种策略。


在C++里使用Reactor模型实现的TCP回显服务器:

需要系统支持epoll,并且编译器支持C++11

#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <vector>
#include <map>
#include <unistd.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/socket.h>

#define MAX_EVENTS 10

class Reactor {
public:
    Reactor() {
        _epoll_fd = epoll_create1(0);
        if (_epoll_fd == -1) {
            perror("epoll_create1");
            exit(EXIT_FAILURE);
        }
    }

    ~Reactor() {
        close(_epoll_fd);
    }

    void add_fd(int fd, uint32_t events) {
        struct epoll_event event;
        event.data.fd = fd;
        event.events = events;
        if (epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, fd, &event) == -1) {
            perror("epoll_ctl");
            exit(EXIT_FAILURE);
        }
    }

    void del_fd(int fd) {
        if (epoll_ctl(_epoll_fd, EPOLL_CTL_DEL, fd, nullptr) == -1) {
            perror("epoll_ctl");
            exit(EXIT_FAILURE);
        }
    }

    void run() {
        std::vector<struct epoll_event> events(MAX_EVENTS);
        while (true) {
            int n = epoll_wait(_epoll_fd, events.data(), MAX_EVENTS, -1);
            if (n == -1) {
                perror("epoll_wait");
                exit(EXIT_FAILURE);
            }

            for (int i = 0; i < n; i++) {
                if (events[i].events & EPOLLIN) {
                    handle_input(events[i].data.fd);
                } else if (events[i].events & EPOLLOUT) {
                    handle_output(events[i].data.fd);
                }
            }
        }
    }

    virtual void handle_input(int fd) = 0;
    virtual void handle_output(int fd) = 0;

private:
    int _epoll_fd;
};

class EchoReactor : public Reactor {
public:
    EchoReactor(int listen_fd) : _listen_fd(listen_fd) {
        add_fd(_listen_fd, EPOLLIN);
    }

    void handle_input(int fd) override {
    if (fd == _listen_fd) {
        struct sockaddr_in addr;
        socklen_t addrlen = sizeof(addr);
        int conn_fd = accept(_listen_fd, (struct sockaddr *)&addr, &addrlen);
        if (conn_fd == -1) {
            perror("accept");
            exit(EXIT_FAILURE);
        }

        make_socket_non_blocking(conn_fd);
        add_fd(conn_fd, EPOLLIN);
    } else {
        char buf[1024];
        ssize_t n = read(fd, buf, sizeof(buf));
        if (n <= 0) {
            if (n < 0) perror("read");
            close(fd);
            del_fd(fd);
        } else {
            _out_buffers[fd] = std::string(buf, n);
            del_fd(fd);
            add_fd(fd, EPOLLOUT);
        }
    }
}

void handle_output(int fd) override {
    auto it = _out_buffers.find(fd);
    if (it != _out_buffers.end()) {
        ssize_t n = write(fd, it->second.c_str(), it->second.size());
        if (n <= 0) {
            if (n < 0) perror("write");
            close(fd);
            del_fd(fd);
        } else {
            it->second.erase(0, n);
            if (it->second.empty()) {
                del_fd(fd);
                add_fd(fd, EPOLLIN);
            }
        }
    }
}

private:
    int make_socket_non_blocking(int sfd) {
        int flags = fcntl(sfd, F_GETFL, 0);
        if (flags == -1) {
            perror("fcntl");
            return -1;
        }

        flags |= O_NONBLOCK;
        if (fcntl(sfd, F_SETFL, flags) == -1) {
            perror("fcntl");
            return -1;
        }

        return 0;
    }

    int _listen_fd;
    std::map<int, std::string> _out_buffers;
};

int main(int argc, char *argv[]) {
    if (argc != 2) {
        std::cerr << "Usage: " << argv[0] << " <port>" << std::endl;
        exit(EXIT_FAILURE);
    }

    int port = std::stoi(argv[1]);

    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    int optval = 1;
    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    if (listen(listen_fd, SOMAXCONN) == -1) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    EchoReactor echo_reactor(listen_fd);
    echo_reactor.run();

    return 0;
}

使用telnet测试:
在这里插入图片描述

2. Proactor模型

在众多网络编程模型中,Reactor模型因其高并发性能、资源利用率高等优点而受到广泛关注。Reactor模型基于事件驱动的原理,通过Reactor、事件处理器(Handlers)和事件分离器(Demultiplexer)三个核心组件的协同作用,有效地处理大量并发连接和I/O操作。然而,在某些高负载场景下,Reactor模型可能会遇到事件处理瓶颈的问题,这使得我们需要寻求其他解决方案来进一步提升系统性能。

Proactor模型正是在这种背景下应运而生的一种高性能网络编程模型。与Reactor模型类似,Proactor模型同样采用了事件驱动的方式,但在事件处理的实现上有所不同。Proactor模型将异步I/O操作与事件处理器相结合,使得I/O操作可以在后台执行,从而进一步减少阻塞和提高系统性能。在Proactor模型中,事件处理的主要责任由操作系统承担,而应用程序则专注于处理业务逻辑。这种分工使得Proactor模型能够更好地应对高负载场景,为构建高性能网络服务器提供了一种有力的解决方案。

在这里插入图片描述

Proactor模型有三个重要的组件:

  • Proactor:Proactor是核心组件,负责管理事件循环和I/O事件分发。它接收客户端连接,监听I/O事件,并将这些事件分发给相应的事件处理器。与Reactor模型相比,Proactor将大部分I/O操作的处理交给操作系统,从而进一步减少阻塞和提高系统性能。
  • Asynchronous Handlers:异步事件处理器(Asynchronous Handlers)是用于处理特定I/O事件的对象。每个处理器通常对应一个客户端连接或一个资源(如文件、套接字等)。与Reactor模型中的事件处理器不同,Proactor模型中的处理器通过异步I/O操作与事件处理相结合,使得I/O操作可以在后台执行,进一步提高系统性能。
  • Asynchronous Operation Processor:异步操作处理器(Asynchronous Operation Processor)是负责执行实际的异步I/O操作的组件。它与事件处理器协同工作,将I/O操作的执行与事件处理解耦,进一步减少阻塞和提高系统性能。
  • Completion Dispatcher:完成分发器(Completion Dispatcher)负责在异步I/O操作完成后通知Proactor。当操作系统完成一个异步I/O操作时,它会将完成通知发送给Completion Dispatcher,而后者则将通知传递给Proactor。这样,Proactor可以将事件分发给相应的事件处理器,处理业务逻辑。

在这里插入图片描述

事件处理的具体流程:

  • Proactor 接收客户端连接,并将连接的相关I/O事件注册到异步事件处理器(Asynchronous Handlers)上。
  • 异步事件处理器(Asynchronous Handlers)启动异步I/O操作。此时,I/O操作的执行由异步操作处理器(Asynchronous Operation Processor)负责,与事件处理器解耦,使得I/O操作可以在后台执行。
  • 异步操作处理器(Asynchronous Operation Processor)与操作系统协同工作,执行实际的异步I/O操作。这样,事件处理器可以在等待I/O操作完成的过程中处理其他任务,进一步提高系统性能。
  • 当操作系统完成一个异步I/O操作时,它会将完成通知发送给完成分发器(Completion Dispatcher)。
  • 完成分发器(Completion Dispatcher)收到通知后,将通知传递给Proactor。此时,Proactor将事件分发给相应的异步事件处理器(Asynchronous Handlers)。
  • 异步事件处理器(Asynchronous Handlers)收到分发的I/O事件后,执行相应的操作,如读取数据、处理业务逻辑、发送响应等。

从上述处理流程中,我们可以发现 Proactor 模型的最大特点是采用异步 I/O。所有的 I/O 操作都由系统提供的异步 I/O 接口执行,工作线程仅负责处理业务逻辑。在 Proactor 模型中,用户函数启动一个异步文件操作并将其注册到多路复用器上。多路复用器关注的是异步读操作是否完成,而不是文件是否可读或可写。异步操作由操作系统完成,用户程序无需关心。当操作系统完成读文件操作,即将数据复制到用户先前提供的缓冲区后,便通知多路复用器相关操作已完成。多路复用器随后调用相应的处理程序处理数据。

尽管 Proactor 增加了编程的复杂度,但它提高了工作线程的效率。Proactor 可在系统态优化读写操作,利用 I/O 并行能力,从而实现高性能单线程模型。在 Windows 上,由于缺乏类似 epoll 的机制,因此采用 IOCP 支持高并发。由于操作系统进行了良好的优化,Windows 更常使用基于完成端口的 Proactor 模型实现服务器。而在 Linux 上,虽然 2.6 内核引入了 aio 接口,但实际效果并不理想。aio 的出现主要是为了解决 poll 性能不佳的问题,但实际测试表明,epoll 的性能高于 poll+aio,且 aio 无法处理 accept。因此,Linux 主要还是以 Reactor 模型为主。

在不使用操作系统提供的异步 I/O 接口的情况下,确实可以通过 Reactor 模拟 Proactor。区别在于:利用异步接口可以使用系统提供的读写并行能力,而在模拟情况下,这需要在用户态实现。具体的做法包括以下步骤:

  1. 注册读事件(同时提供一段缓冲区)。
  2. 事件分离器等待可读事件。
  3. 事件到来时,激活分离器,分离器立即读取数据并写入缓冲区,然后调用事件处理器。
  4. 事件处理器处理数据,删除事件(需要再用异步接口注册)。

值得注意的是,Boost.asio 库采用的是 Proactor 模型。然而,在 Linux 平台上,Boost.asio 使用 epoll 实现的 Reactor 来模拟 Proactor,并额外开辟一个线程来完成读写调度。这种做法在一定程度上结合了两种模型的优势,实现了较高的性能和灵活性。


在C++里使用Proactor模型实现的TCP回显服务器:

这个示例基于Boost.Asio库,它是一个跨平台的C++库,用于编写基于异步I/O操作的网络和低级I/O程序,使用前请确保你已经正确安装,并配置好共享库文件。

#include <iostream>
#include <memory>
#include <utility>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

class session : public std::enable_shared_from_this<session> {
public:
    session(tcp::socket socket) : socket_(std::move(socket)) {}

    void start() { read(); }

private:
    void read() {
        auto self(shared_from_this());
        socket_.async_read_some(boost::asio::buffer(data_, max_length),
            [this, self](boost::system::error_code ec, std::size_t length) {
                if (!ec) {
                    write(length);
                }
            });
    }

    void write(std::size_t length) {
        auto self(shared_from_this());
        boost::asio::async_write(socket_, boost::asio::buffer(data_, length),
            [this, self](boost::system::error_code ec, std::size_t /*length*/) {
                if (!ec) {
                    read();
                }
            });
    }

    tcp::socket socket_;
    enum { max_length = 1024 };
    char data_[max_length];
};

class server {
public:
    server(boost::asio::io_context& io_context, short port)
        : acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) {
        accept();
    }

private:
    void accept() {
        acceptor_.async_accept(
            [this](boost::system::error_code ec, tcp::socket socket) {
                if (!ec) {
                    std::make_shared<session>(std::move(socket))->start();
                }
                accept();
            });
    }

    tcp::acceptor acceptor_;
};

int main(int argc, char* argv[]) {
    try {
        if (argc != 2) {
            std::cerr << "Usage: echo_server <port>\n";
            return 1;
        }

        boost::asio::io_context io_context;
        server s(io_context, std::atoi(argv[1]));
        io_context.run();
    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << "\n";
    }

    return 0;
}

使用telnet测试:
在这里插入图片描述

3. 同步IO模拟Proactor模型

在这里插入图片描述

  1. 主线程将 socket 上的读就绪事件注册到 epoll 内核事件表中。
  2. 主线程调用 epoll_wait 等待 socket 上的数据可读事件。
  3. 当 socket 上有数据可读时,epoll_wait 通知主线程。主线程循环读取 socket 上的数据,直到无更多数据可读,然后将读取到的数据封装为请求对象并插入请求队列。
  4. 在请求队列上等待的某个工作线程被唤醒,获取请求对象并处理客户请求,然后在 epoll 内核事件表中注册 socket 上的写就绪事件。
  5. 主线程调用 epoll_wait 等待 socket 的可写事件。
  6. 当 socket 可写时,epoll_wait 通知主线程。主线程将服务器处理客户请求的结果写入 socket。

两种模式的相同之处在于它们都涉及对某个 I/O 事件的通知(即通知某个模块,这个 I/O 操作可以进行或已经完成)。在结构上,两者也有共同点:demultiplexor 负责提交 I/O 操作(异步)、查询设备是否可操作(同步),当条件满足时,回调注册的处理函数。

它们的不同之处在于:在 Proactor(异步)情况下,回调注册的处理函数时,表示 I/O 操作已经完成;而在 Reactor(同步)情况下,回调注册的处理函数时,表示 I/O 设备可以进行某个操作(可读或可写),此时注册的处理函数开始提交操作。


在C++中,我们可以使用Boost.Asio库来使用步同步I/O模拟Proactor模型。以下是使用Boost.Asio实现Proactor模型的步骤:

  1. 安装Boost库:首先,确保已经安装了Boost库,并将其包含在项目中。Boost.Asio是Boost库的一部分。
  2. 包含所需的头文件:
    #include <boost/asio.hpp>
    #include <boost/bind.hpp>
    #include <iostream>
    #include <vector>
    
  3. 创建异步回调函数:为了模拟Proactor模型,我们需要创建异步回调函数,该函数在异步操作完成时被调用。
    void handle_read(const boost::system::error_code& error, std::size_t bytes_transferred) {
        if (!error) {
            std::cout << "Read: " << bytes_transferred << " bytes" << std::endl;
        } else {
            std::cerr << "Error: " << error.message() << std::endl;
        }
    }
    
  4. 使用Boost.Asio创建异步操作:使用Boost.Asio创建一个异步读取操作,并将其与异步回调函数关联。
    boost::asio::io_context io_context;
    boost::asio::ip::tcp::socket socket(io_context);
    boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::address::from_string("127.0.0.1"), 8080);
    
    socket.async_connect(endpoint, boost::bind(&handle_read, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
    
  5. 运行I/O上下文:运行I/O上下文以处理异步操作。
    io_context.run();
    

完整的代码如下:

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <vector>

void handle_read(const boost::system::error_code& error, std::size_t bytes_transferred) {
    if (!error) {
        std::cout << "Read: " << bytes_transferred << " bytes" << std::endl;
    } else {
        std::cerr << "Error: " << error.message() << std::endl;
    }
}

int main() {
    try {
        boost::asio::io_context io_context;
        boost::asio::ip::tcp::socket socket(io_context);
        boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::address::from_string("127.0.0.1"), 8080);

        socket.async_connect(endpoint, boost::bind(&handle_read, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));

        io_context.run();
    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }

    return 0;
}

测试使用的TCP服务器来接受上面客户端的连接:

#include <boost/asio.hpp>
#include <iostream>
#include <array>
#include <memory>

class session : public std::enable_shared_from_this<session> {
public:
    session(boost::asio::ip::tcp::socket socket) : socket_(std::move(socket)) {}

    void start() { read(); }

private:
    void read() {
        auto self(shared_from_this());
        socket_.async_read_some(boost::asio::buffer(data_),
                                [this, self](boost::system::error_code ec, std::size_t length) {
                                    if (!ec) {
                                        read();
                                    }
                                });
    }

    boost::asio::ip::tcp::socket socket_;
    std::array<char, 1024> data_;
};

class server {
public:
    server(boost::asio::io_context& io_context, short port)
        : acceptor_(io_context, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)) {
        accept();
    }

private:
    void accept() {
        acceptor_.async_accept([this](boost::system::error_code ec, boost::asio::ip::tcp::socket socket) {
            if (!ec) {
                std::make_shared<session>(std::move(socket))->start();
            }

            accept();
        });
    }

    boost::asio::ip::tcp::acceptor acceptor_;
};

int main() {
    try {
        boost::asio::io_context io_context;
        server srv(io_context, 8080);
        io_context.run();
    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }

    return 0;
}

makefile:

.PHONY:clean
all: client.cc server.cc
        g++ client.cc -o client -I/usr/local/include -L/usr/local/lib -lpthread -std=c++11
        g++ server.cc -o server -I/usr/local/include -L/usr/local/lib -lpthread -std=c++11
clean:
        rm -f client server

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

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

相关文章

Ubantu docker学习笔记(八)私有仓库

文章目录 一、建立HTTPS链接1.在仓库服务器上获取TLS证书1.1 生成证书颁发机构证书1.2 生成服务器证书1.3 利用证书运行仓库容器 2.让私有仓库支持HTTPS3.客户端端配置 二、基本身份验证三、对外隐藏仓库服务器3.1 在服务器端3.2 在客户端进行 四、仓库可视化 在前面的学习中&a…

数据库三范式与反范式详解

&#x1f3c6;今日学习目标&#xff1a; &#x1f340;数据库三范式与反范式详解 ✅创作者&#xff1a;林在闪闪发光 ⏰预计时间&#xff1a;30分钟 &#x1f389;个人主页&#xff1a;林在闪闪发光的个人主页 &#x1f341;林在闪闪发光的个人社区&#xff0c;欢迎你的加入: 林…

阿里云服务器通用算力u1性能测评CPU处理器网络PPS

阿里云服务器u1通用算力型Universal实例高性价比&#xff0c;CPU采用Intel(R) Xeon(R) Platinum&#xff0c;主频是2.5 GHz&#xff0c;云服务器U1实例的基准vCPU算力与5代企业级实例持平&#xff0c;最高vCPU算力与6代企业级实例持平&#xff0c;提供2c-32c规格和1:1/2/4/8丰富…

贪心算法讲解

文章目录 1. 贪心算法的概念2. 讲解贪心 1. 贪心算法的概念 贪心算法是&#xff1a;用一种局部最功利的标准&#xff0c;总是做出当前看来是最好的选择。如果局部最优解可以得出全局最优解&#xff0c;说明贪心假设成立&#xff0c;否则就失败。 举个例子&#xff1a; 这里有…

尚融宝26-投标

目录 一、需求 &#xff08;一&#xff09;投资人投标 &#xff08;二&#xff09;流程 二、标的详情 &#xff08;一&#xff09;需求 &#xff08;二&#xff09;后端 &#xff08;三&#xff09;前端 三、计算收益 &#xff08;一&#xff09;四种还款方式 &#…

基于 A* 搜索算法来优化无线传感器节点网络的平均电池寿命(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 A*&#xff08;念做&#xff1a;A Star&#xff09;算法是一种很常用的路径查找和图形遍历算法。它有较好的性能和准确度。本文…

一篇带你快速入门DDD领域驱动设计

一、什么是领域驱动 领域驱动设计 Domain-Driven Design&#xff0c;简称DDD。软件对于行业并没有这么高的要求&#xff0c;他本身就是帮助其他行业更好的发展&#xff0c;赋能其他行业的。各个行业都有软件的身影&#xff0c;但是他们的业务场景是不同的&#xff0c;所以就需…

【MYSQL】数据类型和约束

目录 数据类型 1.数值类型 1.1.位--类型bit(M) 1.2. 整数类型--tinyint&#xff0c;smallint&#xff0c;int&#xff0c;bigint 1.3.小数类型--float、decimal 2.字符类型--char、varchar 3.日期类型--datetime、timestamp 4.string类型--enum和set mysql的约束 1.空…

Mybatis 知识总结2(基于注解的增删改查操作)

3.3 MyBatis 增删改查&#xff08;注解方式&#xff09; MyBatis 的增删改查是最基础最核心的功能&#xff0c;需要重点掌握。 需求说明 对员工信息进行增删改查操作。 查询&#xff08;查询结果分页展示后续实现&#xff09; 根据主键ID查询根据条件查询 新增更新删除 根据主…

海思芯片(hi3536av100)启动模式选择

1、支持多种模式可配置 (1)支持BootRom启动 (2)支持从 SPI NOR Flash 启动 (3)支持从 SPI NAND Flash 启动 (4)支持从 eMMC 启动 (5)支持 PCIe 从片启动 2、启动方式的选择 BOOT_SEL[1:0]SFC_EMMC_BOOT_MODEMODE000SPI Nor Flash 3 Byte001SPI Nor Flash 4 Byte010SPI Nand Fl…

2005-2020全国及各省家庭承包耕地流转总面积及经营耕地面积

2005-2020全国及各省家庭承包耕地流转总面积及经营耕地面积 1、时间&#xff1a;时间&#xff1a;2005-2020年 2、范围&#xff1a;包括全国及30个省份不包括西藏 3、指标包括&#xff1a;家庭承包耕地流转总面积(亩)、家庭承包经营耕地面积(亩) 4、来源&#xff1a;农村经…

荔枝派Zero(全志V3S) tftp下载 kernel 和 nfs 挂载文件系统

文章目录 前言一、U-Boot 适配 Ethernet1、配置 U-Boot2、修改 dts 文件3、编译4、烧写到 SD 卡5、测试<1>、查看启动打印信息<2>、ping 测试 二、Kernel 适配 Ethernet1、配置 kernel2、修改 dts 文件3、编译4、拷贝到 SD 卡5、测试<1>、启动网络接口&#…

【嵌入式环境下linux内核及驱动学习笔记-(7-内核 I/O)-多路复用】

目录 2、多路复用2.1 函数select相关2.1.1 应用层select()2.1.2 FD_ZERO2.1.3 FD_SET2.1.4 FD_ISSET 2.2 函数poll相关2.2.1 poll函数 2.3 驱动层 函数2.4 实例 接上篇&#xff0c;继续内核 I/O的五种模式的解读。 2、多路复用 select&#xff0c;poll&#xff0c;epoll都是IO…

常见的接口优化技巧思路

一、背景 针对老项目&#xff0c;去年做了许多降本增效的事情&#xff0c;其中发现最多的就是接口耗时过长的问题&#xff0c;就集中搞了一次接口性能优化。本文将给小伙伴们分享一下接口优化的通用方案。 二、接口优化方案总结 1.批处理 批量思想&#xff1a;批量操作数据…

windows如何确认服务器上程序端口是否正常

方式1&#xff1a;ping命令 ping命令说明 ping命令是个使用频率极高的网络诊断工具&#xff0c;在Windows、Unix和Linux系统下均适用。它是TCP/IP协议的一部分&#xff0c;用于确定本地主机是否能与另一台主机交换数据报。根据返回的信息&#xff0c;我们可以推断TCP/IP参数设…

类和对象 -上(C++)

目录 认识面向过程和面向对象 类的引入 类的定义 语法&#xff1a; 类的两种定义方式&#xff1a; 成员变量命名规则建议 类的访问限定符及封装 访问限定符 C 中 class 和 struct 的区别&#xff1f; 封装 类的作用域 类的实例化 类对象模型 如何计算类对象的大小 结构体的内存…

想保护你的网站?用Python来生成验证码图片

前言 随着互联网的发展&#xff0c;我们越来越多地依赖于网站和应用程序&#xff0c;而这些网站和应用程序也面临着各种各样的安全威胁&#xff0c;其中之一就是用户可能会通过脚本攻击你的网站。为了缓解这些安全风险&#xff0c;一个常见的做法是在用户进行操作时&#xff0…

关于电信设备进网许可制度若干改革举措的通告

Q&#xff1a;3月1日后&#xff0c;不再实行进网许可管理的11种电信设备是否还需要继续申请和使用标志&#xff1f; A&#xff1a;3月1日起&#xff0c;对不再实行进网许可管理的11种电信设备停止核发进网许可标志&#xff0c;已申请的标志可在证书有效期内继续使用。 Q&#…

应用启动时aerospike客户端查询rt高原因

在应用刚起步时&#xff0c;发到预发测试或者生产小部分流量进来时&#xff0c;发现aerospike的rt特别高&#xff0c;在流量稍微大点时&#xff0c;rt恢复正常。基本可以断定客户端存在预热问题。 应用没有设置连接池配置&#xff0c;因此check下默认配置 可以看到&#xff0…

c++类 笔记(陆续更新该文档)

派生类 #include <iostream> using namespace std; class Box{private://类私有&#xff0c;只有成员可以调用 也就是说你不可以通过box1.a来调用 ,这些变量其实你默认不用写private 这个变量&#xff0c;只要放在最上面他默认就是 私有int a1;protected://protected&am…