基于epoll实现Reactor服务器

了解epoll底层逻辑

在我们调用epoll_create的时候会创建出epoll模型,这个模型也是利用文件描述类似文件系统的方式控制该结构。

在我们调用epoll_create的时候,就会在内核管理中创建一个epoll模型,并且建管理模块地址给file结构体,file结构体也是连接在管理所有file结构体的数据结构中

所以epoll也会给进程管理的files返回一个file地址保存在file_array中,并且将该地址在array中的下标值返回给上层。

这样以同一的方式管理epoll模型。所以这就是epoll模型的好处,这和select和poll的方式不同,这两种并不使用文件描述符

select还需要自己维护一个关心事件的fd的数组,然后再select结束以后,遍历该数组中的fd和输入输出型参数fd_set做查询关系(FD_ISSET),这其实是非常不方便的,在发生事件我们都需要遍历全部关心的事件,查看事件是否发生。并且因为是输入输出型(fd_set)参数,在响应后,之前设置的监视事件失效,所以每次监视事件前,都需要重新输入所有需要监听的事件这是非常不方便的事情

poll在select上做了升级,不再需要额外的数组保存而是使用pollfd结构体保存fd和关心事件,但是在响应后我们依旧需要遍历所有关心的事件,假设1w个被监控的事件只有1个得到了响应,我们却需要遍历1w个事件一个一个检查是否响应,这是低效的算法。

并且在操作系统中poll和epoll搭建的服务器关心的事件会被一直遍历查询是否被响应,哪怕1w个关心事件只有一个响应是第一个,剩下的9999个事件我们也得查看其是否被响应。

我们不应该在响应得到后遍历所有的事件,操作系统也应该轮询的检查所有监控事件被响应,这是低效的2个做法,这就是epoll出现的意义,他的出现解决了这些繁杂的问题,并且在接口使用上做了极大的优化。他利用红黑树来管理需要监视程序员需要关心的事件和利用准备队列构建另一个结构,该结构保存了本次等待得到的所有有响应的事件。

epoll模型介绍

 

创建epoll模型:调用epoll_create,在文件描述符表添加一个描述符,生成对应的文件结构体结构体保存对应生成eventpoll结构体的地址,该结构中有rbr(监视事件红黑树),rdllist(就绪事件队列)等等。        

添加一个fd到epoll中:调用epoll_ctl,通过epollfd在进程文件描述符表中找到对应的file,然后在对应的文件结构体中的标识符将特定指针强转为eventpoll,访问rbr,增加新结点在树中,并且添加对应的回调函数到对应fd的文件结构体中。

接收并读取报文:网卡设备得到数据,发送设备中断给cpu,cpu根据接收到的中断号,在中断向量表中查找设备驱动提供的接口回调,将数据从网卡中读取到OS层的file文件结构体中,然后经过部分协议解析到TCP解析后,根据端口找到对应的进程,在进程中依靠五元组和fd的映射关系找到对应的file结构体,然后将网卡file的数据拷贝到对应服务器链接的file下的缓冲区中,并且调用其传入的callback函数传入fd通知epoll模型,有数据来临。这个时候我们的epoll在自己的rb树中依靠fd找到对应结点,并且其是否是自己所关心的事件,找到并且是我们的事件,就会取出其rb中的fd和响应的事件做拼接(一个结点监视一个fd的多个事件,发生响应并不是发生全部响应,一般都是一个响应,这个时候就需要将响应的事件和fd做结合,而不是全部事件和fd做结合)构建ready结点反应给上层。

诚然在我们放入事件和拿出响应事件的过程中并不是原子的查找,比如访问ready结点操作系统可能在构建,而我们在拿出,这里就会造成执行流混乱的局面,所以这里是需要进程锁的,保证执行流正常。

庆幸的是,我们的设计者大佬们已将帮我们锁好了,我们用就好了。

LT和ET的区别

LT的工作模式:

  1. 当epoll检测到socket上事件就绪的时候, 可以不立刻进行处理. 或者只处理一部分.
  2. 由于只读了1K数据, 缓冲区中还剩1K数据, 在第二次调用 epoll_wait 时, epoll_wait 仍然会立刻返回并通知socket读事件就绪.
  3. 直到缓冲区上所有的数据都被处理完, epoll_wait 才不会立刻返回.
  4. 支持阻塞读写和非阻塞读写

ET的工作模式:

  1. 当epoll检测到socket上事件就绪时, 必须立刻处理.
  2. 虽然只读了1K的数据, 缓冲区还剩1K的数据, 在第二次调用 epoll_wait 的时候, epoll_wait 不会再返回了. 也就是说, ET模式下, 文件描述符上的事件就绪后, 只有一次处理机会,所以需要一次性读取完毕.
  3. ET的性能比LT性能更高( epoll_wait 返回的次数少了很多). Nginx默认采用ET模式使用epoll.
  4. 只支持非阻塞的读写

二者对比

  • LT是 epoll 的默认行为. 使用 ET 能够减少 epoll 触发的次数. 但是代价就是强逼着程序猿一次响应就绪过程中就把 所有的数据都处理完.
  • 相当于一个文件描述符就绪之后, 不会反复被提示就绪, 看起来就比 LT 更高效一些. 但是在 LT 情况下如果也能做到 每次就绪的文件描述符都立刻处理, 不让这个就绪被重复提示的话, 其实性能也是一样的.
  • 另一方面, ET 的代码复杂程度更高了.

ps:使用 ET 模式的 epoll, 需要将文件描述设置为非阻塞. 这个不是接口上的要求, 而是 "工程实践" 上的要求,毕竟我们需要一次性读取全部数据,在最后一次不能读取的时候会阻塞在接口处。

插件组合

创建多个类:Epoll类、Sock类、Connection类、Log类

Epoll类

用来为我们保存并管理epoll模型。

static const unsigned int epoll_event_size_default = 64;
class Epoll
{
public:
    Epoll(unsigned int epoll_event_size = epoll_event_size_default)
        : _epoll_event_size(epoll_event_size)
    {
        _epoll_fd = epoll_create(254);
        if (_epoll_fd == -1)
        {
            Log()(Fatal, "epoll_create fail:");
            exit(-1);
        }
        _epoll_event = new epoll_event[_epoll_event_size];
    }
    struct epoll_event *bind_ready_ptr()
    {
        return _epoll_event;
    }
    int EpollCtl(int op, int fd, int event)
    {
        struct epoll_event ev;
        ev.data.fd = fd;
        ev.events = event;
        int status = epoll_ctl(_epoll_fd, op, fd, &ev);

        return status == 0;
    }

    int EpollWait(int timeout)
    {
        int n = epoll_wait(_epoll_fd, _epoll_event, _epoll_event_size, timeout);
        return n;
    }
    int fds_numb()
    {
        return _epoll_event_size;
    }

private:
    int _epoll_fd;
    struct epoll_event *_epoll_event;
    unsigned int _epoll_event_size;
};

该类管理着,epoll模型文件描述符,_epoll_event第一个就绪结点地址、最大可以接收的 _epoll_event_size.

注意这里的_epoll_event,并不是实际在epoll模型中的自由结点,而是该自由结点将重要信息拷贝到我们传入的这个空间中。

传入的event_size是告诉epoll模型我最多只能拷贝这么多个结点信息,还有就下次再说了,返回值是本次拷贝数量n。

Sock类

替我们来链接新链接的类

class Sock
{
public:
    Sock(int gblock = 5)
        : _listen_socket(socket(AF_INET, SOCK_STREAM, 0)), _gblock(gblock)
    {
        int opt = 1;
        setsockopt(_listen_socket, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof opt);
    }

    int get_listen_sock()
    {
        return _listen_socket;
    }
    void Sock_bind(const std::string &ip = "0.0.0.0", uint16_t port = 8080)
    {
        sockaddr_in self;
        bzero(&self, sizeof(self));
        self.sin_family = AF_INET;
        self.sin_addr.s_addr = inet_addr(ip.c_str());
        self.sin_port = htons(port);
        if (0 > bind(_listen_socket, (sockaddr *)&self, sizeof(self)))
        {
            log(Fatal, "bind 致命错误[%d]", __TIME__);
            exit(1);
        }
    }
    void Sock_connect(const char *ip, const char *port)
    {
        struct sigaction s;
        sockaddr_in server;
        bzero(&server, sizeof(server));
        server.sin_family = AF_INET;
        inet_aton(ip, &server.sin_addr);
        server.sin_port = htons(atoi(port));
        connect(_listen_socket, (sockaddr *)&server, sizeof(server));
    }
    void Sock_listen()
    {
        if (listen(_listen_socket, _gblock) > 0)
        {
            log(Fatal, "listen 致命错误[%d]", __TIME__);
            exit(2);
        }
    }

    int Sock_accept(std::string *ip, uint16_t *port)
    {
        sockaddr_in src;
        bzero(&src, sizeof(src));
        socklen_t srclen = sizeof(src);
        int worksocket = accept(_listen_socket, (sockaddr *)&src, &srclen);
        if (worksocket < 0)
        {

            log(Fatal, "link erron 链接失败");
            return -1;
        }
        *ip = inet_ntoa(src.sin_addr);
        *port = ntohs(src.sin_port);
        return worksocket;
    }

    ~Sock()
    {
        if (_listen_socket >= 0)
            close(_listen_socket);
    }

private:
    int _listen_socket;
    const int _gblock;
};

围绕着_listen_socket来操作的类

Log类

就是个日志没啥

class Log
{
public:
    Log()
    {
        std::cout<<"create log...\n"<<std::endl;
        printMethod = Screen;
        path = "./log/";
    }
    void Enable(int method)
    {
        printMethod = method;
    }
    std::string levelToString(int level)
    {
        switch (level)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        default:
            return "None";
        }
    }



    void printLog(int level, const std::string &logtxt)
    {
        switch (printMethod)
        {
        case Screen:
            std::cout << logtxt << std::endl;
            break;
        case Onefile:
            printOneFile(LogFile, logtxt);
            break;
        case Classfile:
            printClassFile(level, logtxt);
            break;
        default:
            break;
        }
    }
    void printOneFile(const std::string &logname, const std::string &logtxt)
    {
        std::string _logname = path + logname;
        std::cout<<_logname<<std::endl;
        int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"
        if (fd < 0)
        {
            perror("fail:");
            return;
        }
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }
    void printClassFile(int level, const std::string &logtxt)
    {
        std::string filename = LogFile;
        filename += ".";
        filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"
        printOneFile(filename, logtxt);
    }

    ~Log()
    {
    }
    void operator()(int level, const char *format, ...)
    {
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);
        char leftbuffer[SIZE];
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
                 ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
                 ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

        va_list s;
        va_start(s, format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);

        // 格式:默认部分+自定义部分
        char logtxt[SIZE * 2];
        snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);

        // printf("%s", logtxt); // 暂时打印
        printLog(level, logtxt);
    }

private:
    int printMethod;
    std::string path;
};

Connection类

using func_t = std::function<void(Connection *)>;
class Connection
{
public:
    Connection(int sock, void *tsvr = nullptr) : _fd(sock), _tsvr(tsvr)
    {
        time_t _lasttime = (time_t)time(0);
    }
    bool SetCallBack(func_t recv_cb, func_t send_cb, func_t except_cb)
    {
        _recv_cb = recv_cb;
        _send_cb = send_cb;
        _except_cb = except_cb;
    }

    int _fd;
    int _events;
    // 三个回调方法,表征的就是对_sock进行特定读写对应的方法
    func_t _recv_cb;
    func_t _send_cb;
    func_t _except_cb;
    // 接收缓冲区&&发送缓冲区
    std::string _inbuffer; // 暂时没有办法处理二进制流,文本是可以的
    std::string _outbuffer;
    int _lasttime = 0;

    std::string _client_ip;
    uint16_t _client_port;

    // 设置对epTcpServer的回值指针
    void *_tsvr;
};

管理任何链接描述符(包括listen)的链接类,保存某个链接监视的读写异常事件,并且保存这些事件发生后对应的调用方法,并且每个事件设置读写应用层缓冲区,并且采用回值指针(在写入数据后采用该指针通知上层下次该链接修改采用监视事件条件。

服务器代码

#pragma once
#include "Log.hpp"
#include "sock.hpp"
#include "Epoll.hpp"
#include "Protocol.hpp"
#include <unordered_map>
#include <cassert>
#include <vector>
static const std::uint16_t server_port_defaut = 8080;
static const std::string server_ip_defaut = "0.0.0.0";
static const int READONE = 1024;

#define CLIENTDATA conn->_client_ip.c_str(),conn->_client_port

using callback_t = std::function<void(Connection *, std::string &)>;

class epTcpServer
{
    static const std::uint16_t default_port = 8080;
    static const std::uint16_t default_revs_num = 128;

public:
    epTcpServer(int port = default_port, int revs_num = default_revs_num)
        : _port(port), _epoll(default_revs_num), _revs_num(revs_num)
    {
        _sock.Sock_bind();
        _sock.Sock_listen();
        _listen = _sock.get_listen_sock();

        AddConnection(_listen, std::bind(&epTcpServer::Accept, this, std::placeholders::_1), nullptr, nullptr);

        _revs = _epoll.bind_ready_ptr();
        cout << "debug 1" << endl;
    }
    void Dispather(callback_t cb)
    {
        _cb = cb;
        while (true)
        {
            LoopOnce();
        }
    }
    void EnableReadWrite(Connection *conn, bool readable, bool writeable)
    {
        uint32_t events = ((readable ? EPOLLIN : 0) | (writeable ? EPOLLOUT : 0));
        bool res = _epoll.EpollCtl(EPOLL_CTL_MOD, conn->_fd, events);
        assert(res); // 更改成if
    }

private:
    void LoopOnce()
    {
        int n = _epoll.EpollWait(-1);
        log(Info,"The number of links in this response :%d",n);
        for (int i = 0; i < n; i++)
        {
            int sock = _revs[i].data.fd;
            uint32_t revents = _revs[i].events;
            log(Info, "Accessible fd:%d", sock);
            bool status = IsConnectionExists(sock);
            if (!status)
            {
                log(Error, "There is no such data in the hash sock:%d", sock);
                continue;
            }
            
            if (revents & EPOLLIN)
            {
                if (_Connection_hash[sock]->_recv_cb != nullptr)
                {
                    _Connection_hash[sock]->_recv_cb(_Connection_hash[sock]);
                }
            }
            status = IsConnectionExists(sock);
            if (revents & EPOLLOUT)
            {
                if (!status)
                {
                    log(Warning, "in read closs sock:%d", sock);
                    continue;
                }
                if (_Connection_hash[sock]->_send_cb != nullptr)
                    _Connection_hash[sock]->_send_cb(_Connection_hash[sock]);
            }
        }
    }

    bool IsConnectionExists(int sock)
    {
        auto iter = _Connection_hash.find(sock);
        if (iter == _Connection_hash.end())
            return false;
        else
            return true;
    }

    void Accept(Connection *conn)
    {
        while (1)
        {
            std::string ip;
            uint16_t port;
            int work = _sock.Sock_accept(&ip, &port);
            if (work < 0)
            {
                if (errno == EAGAIN || errno == EWOULDBLOCK)
                    break;
                else if (errno == EINTR) // 信号中断
                    continue;            // 概率非常低
                else
                {
                    // accept失败
                    log(Warning, "accept error, %d : %s", errno, strerror(errno));
                    break;
                }
            }

            Connection *ret = AddConnection(work, std::bind(&epTcpServer::Read, this, std::placeholders::_1),
                                            std::bind(&epTcpServer::Write, this, std::placeholders::_1),
                                            std::bind(&epTcpServer::Except, this, std::placeholders::_1));
            ret->_client_ip = ip;
            ret->_client_port = port;
            log(Info, "accept success && TcpServer success clinet[%s|%d]", ret->_client_ip.c_str(), ret->_client_port);
        }
    }

    void Read(Connection *conn)
    {
        int cnt = 0;
        while (1)
        {
            char buffer[READONE] = {0};
            int n = recv(conn->_fd, buffer, sizeof(buffer) - 1, 0);
            if (n < 0)
            {
                if (errno == EAGAIN || errno == EWOULDBLOCK)
                    break; // 正常的
                else if (errno == EINTR)
                    continue;
                else
                {
                    log(Error, "recv error, %d : %s", errno, strerror(errno));
                    conn->_except_cb(conn);
                    return;
                }
            }
            else if (n == 0)
            {
                log(Debug, "client[%s|%d] quit, server close [%d]", CLIENTDATA, conn->_fd);
                conn->_except_cb(conn);
                return;
            }
            else
            {
                buffer[n] = 0;
                conn->_inbuffer += buffer;
            }
        }
        log(Info,"The data obtained from the client[%s|%d] is:%s",CLIENTDATA,conn->_inbuffer.c_str());
        std::vector<std::string> messages;
        SpliteMessage(conn->_inbuffer, &messages);
        for (auto &msg : messages)
            _cb(conn, msg);
    }
    void Write(Connection *conn)
    {
        printf("write back to client[%s|%d]:%s", conn->_client_ip.c_str(), conn->_client_port, conn->_outbuffer.c_str());
        while (true)
        {
            ssize_t n = send(conn->_fd, conn->_outbuffer.c_str(), conn->_outbuffer.size(), 0);
            if (n > 0)
            {
                conn->_outbuffer.erase(0, n);
                if (conn->_outbuffer.empty())
                    break;
            }
            else
            {
                if (errno == EAGAIN || errno == EWOULDBLOCK)
                    break;
                else if (errno == EINTR)
                    continue;
                else
                {
                    log(Error, "send error, %d : %s", errno, strerror(errno));
                    conn->_except_cb(conn);
                    break;
                }
            }
        }
        if (conn->_outbuffer.empty())
            EnableReadWrite(conn, true, false);
        else
            EnableReadWrite(conn, true, true);
    }
    void Except(Connection *conn)
    {
        if (!IsConnectionExists(conn->_fd))
            return;
        // 1. 从epoll中移除
        bool res = _epoll.EpollCtl(EPOLL_CTL_DEL, conn->_fd, 0);
        assert(res); // 要判断
        // 2. 从我们的unorder_map中移除
        _Connection_hash.erase(conn->_fd);
        // 3. close(sock);
        close(conn->_fd);
        // 4. delete conn;
        delete conn;

        log(Debug, "Excepter 回收完毕,所有的异常情况");
    }

    Connection *AddConnection(int sock, func_t recv_cb, func_t send_cb, func_t except_cb, int sendevent = 0)
    {
        SetNonBlock(sock);
        Connection *conn = new Connection(sock, this);
        conn->SetCallBack(recv_cb, send_cb, except_cb);

        _epoll.EpollCtl(EPOLL_CTL_ADD, sock, EPOLLIN | EPOLLET | sendevent);

        _Connection_hash[sock] = conn;
        return conn;
    }

    bool SetNonBlock(int sock)
    {
        int fl = fcntl(sock, F_GETFL);
        if (fl < 0)
            return false;
        fcntl(sock, F_SETFL, fl | O_NONBLOCK);
        return true;
    }

private:
    int _listen;
    int _port;
    int _revs_num;

    zjy::Sock _sock;
    zjy::Epoll _epoll;

    std::unordered_map<int, Connection *> _Connection_hash;

    callback_t _cb;
    struct epoll_event *_revs;
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python数据分析基础

前言 2023年10月以来&#xff0c;一位在商学院就读的可爱同学遇上了一门课——python数据分析&#xff0c;并遇到了许多问题&#xff0c;找上了我&#xff0c;就此&#xff0c;我也开始了学习之路&#xff0c;虽然很浅显&#xff0c;但这些东西对部门同学来说也是受用的&#…

TypeScript中的单件设计模式

基本概念 &#xff08;1&#xff09; 了解设计模式 设计模式通俗的讲&#xff0c;就是一种更好的编写代码方案&#xff0c;打个比喻&#xff1a;从上海到武汉&#xff0c;你可以选择做飞机&#xff0c;做轮船&#xff0c;开车&#xff0c;骑摩托车多种方式&#xff0c;把出行…

短视频购物系统源码:构建创新购物体验的技术深度解析

短视频购物系统作为电商领域的新宠&#xff0c;其背后的源码实现是其成功的关键。本文将深入探讨短视频购物系统的核心技术和源码设计&#xff0c;以揭示其如何构建创新购物体验的技术奥秘。 1. 技术架构与框架选择 短视频购物系统的源码首先考虑的是其技术架构。常见的选择…

ExoPlayer架构详解与源码分析(10)——H264Reader

系列文章目录 ExoPlayer架构详解与源码分析&#xff08;1&#xff09;——前言 ExoPlayer架构详解与源码分析&#xff08;2&#xff09;——Player ExoPlayer架构详解与源码分析&#xff08;3&#xff09;——Timeline ExoPlayer架构详解与源码分析&#xff08;4&#xff09;—…

AI大模型:启动参数总结整理

虽然通过调整启动大模型的参数&#xff0c;对生成效果的提升是有限的&#xff0c;但适当的调整&#xff0c;还是能满足一些常用的场景的~ 一. 【max_length】 令牌生成最大数 用于控制生成文本的最大长度&#xff0c;默认为 20。它的值对应于输入提示的长度加上max_new_token…

10_企业架构NOSQL数据库之MongoDB

企业架构NOSQL数据库之MongoDB 学习目标和内容 1、能够简单描述MongoDB的使用特点 2、能够安装配置启动MongoDB 3、能够使用命令行客户端简单操作MongoDB 4、能够实现基本的数据操作 5、能够实现MongoDB基本安全设置 6、能够操作安装php的MongoDB扩展 一、背景描述及其方案设计…

【AI】以大厂PaaS为例,看人工智能技术方案服务能力的方向(1/2)

目录 一、深度学习能力 二、计算框架 2.1 语音 2.2 OCR 2.3 人脸/体识别 2.4 图像审核 2.5 图像识别 2.6 视频 2.7 自然语言理解 2.8 知识图谱 今天以百度智能云为例&#xff0c;梳理下人工智能技术方案服务能力&#xff0c;主要有哪些方向的应用和拓展。 纯属学习&…

JDK 9 模块化系统 (Module System) 和 多版本兼容 Jar (Multi-Release Jar)

博文目录 文章目录 Module System原因JDK 模块化模块描述文件关键字 启用模块化测试结论 Multi-Release jar (MRJAR)原因原理结论用 IDEA 创建多版本兼容 Jar项目结构pom.xml测试 Module System 原因 Java 9引入了模块化系统的主要原因是为了解决Java平台面临的复杂性和可维…

OpenCV图像相似性比对算法

背景 在做图像处理或者计算机视觉相关的项目的时候&#xff0c;很多时候需要我们对当前获得的图像和上一次的图像做相似性比对&#xff0c;从而找出当前图像针对上一次的图像的差异性和变化点&#xff0c;这需要用到OpenCV中的一些图像相似性和差异性的比对算法&#xff0c;在O…

C练习题13

单项选择题(本大题共20小题,每小题2分,共40分。在每小题给出的四个备选项中,选出一个正确的答案,并将所选项前的字母填写在答题纸的相应位置上。) 1.结构化程序由三种基本结构组成、三种基本结构组成的算法是() A.可以完成任何复杂的任务 B. 只能完成部分复杂的任务 C. 只能完…

打破常规思维:Scrapy处理豆瓣视频下载的方式

概述 Scrapy是一个强大的Python爬虫框架&#xff0c;它可以帮助我们快速地开发和部署各种类型的爬虫项目。Scrapy提供了许多方便的功能&#xff0c;例如请求调度、数据提取、数据存储、中间件、管道、信号等&#xff0c;让我们可以专注于业务逻辑&#xff0c;而不用担心底层的…

TrustZone之物理地址空间

除了两个安全状态外&#xff0c;该体系结构还提供了两个物理地址空间&#xff1a;安全和非安全。 在非安全状态下&#xff0c;虚拟地址始终转换为非安全物理地址。这意味着在非安全状态下的软件只能看到非安全资源&#xff0c;但永远看不到安全资源。如图所示&#xff1a; 在安…

大数据Doris(三十三):Doris高级设置

文章目录 Doris高级设置 一、增大内存

准确!!!在 CentOS 8 上配置 PostgreSQL 14 的主从复制

在 CentOS 8 上配置 PostgreSQL 14 的主从复制&#xff0c;并设置 WAL 归档到特定路径 /home/postgres/archive 的步骤如下&#xff1a; 主服务器配置&#xff08;主机&#xff09; 配置 PostgreSQL&#xff1a; 编辑 postgresql.conf 文件&#xff1a; vim /data/postgres/p…

Java二阶知识点总结(一)Maven

一、Maven概念 Maven是一个项目管理工具&#xff0c;其主要作用有2点 依赖管理&#xff1a;管理项目依赖的各种jar包自动构建&#xff1a;项目构建的过程&#xff0c;从编译、测试、运行、打包到安装的过程可以一键执行 二、Maven工程的目录结构 src/main/java&#xff1a;…

H5ke13-1浏览器处理异常

window对应的error没有event对象 window对应的error他接收三个参数,msg,url,行号 return false return true 1就不会返回错误 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>Title</title>&…

使用 MITRE ATTCK® 框架缓解网络安全威胁

什么是MITRE ATT&CK框架 MITRE Adversarial Tactics&#xff0c; Techniques&#xff0c; and Common Knowledge&#xff08;ATT&CK&#xff09;是一个威胁建模框架&#xff0c;用于对攻击者用来入侵企业、云和工业控制系统&#xff08;ICS&#xff09;并发起网络攻击…

探索人工智能领域——每日20个名词详解【day10】

目录 前言 正文 总结 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Filotimo__✍️原创&#xff0c;首发于CSDN&#x1f4da;。 &#x1f4e3;如需转载&#xff0c;请事先与我联系以…

正则表达式(5):常用符号

正则表达式&#xff08;5&#xff09;&#xff1a;常用符号 小结 本博文转载自 在本博客中&#xff0c;”正则表达式”为一系列文章&#xff0c;如果你想要从头学习怎样在Linux中使用正则&#xff0c;可以参考此系列文章&#xff0c;直达链接如下&#xff1a; 在Linux中使用正…

Leetcode刷题详解——最长湍流子数组

1. 题目链接&#xff1a;978. 最长湍流子数组 2. 题目描述&#xff1a; 给定一个整数数组 arr &#xff0c;返回 arr 的 最大湍流子数组的长度 。 如果比较符号在子数组中的每个相邻元素对之间翻转&#xff0c;则该子数组是 湍流子数组 。 更正式地来说&#xff0c;当 arr 的子…