Client客户端模块

news2024/12/25 8:54:42

一.Client模块介绍

二.Client具体实现

1.消费者/订阅者模块

2.信道管理模块

3.异步线程模块

4.连接管理模块

这个模块同样是针对muduo库客户端连接的二次封装,向用户提供创建channel信道的接口,创建信道后,可以通过信道来获取指定服务。

三.全部代码

consumer.hpp

#pragma once
#include "../common_mq/helper.hpp"
#include "../common_mq/logger.hpp"
#include "../common_mq/msg.pb.h"
#include <unordered_map>
#include <mutex>
#include <memory>
#include <cassert>
#include <cstring>
#include <vector>
#include <functional>
namespace mq
{
    // tag  BasicAttributes body
    using ConsumerCallBack = std::function<void(const std::string &, const msg::BasicAttributes *, const std::string &)>;
    struct Consumer
    {
        using ptr = std::shared_ptr<Consumer>;

        std::string _tag;     // 消费者标识
        std::string _qname;   // 订阅的队列的名称
        bool _auto_ack;       // 是否自动确认
        ConsumerCallBack _cb; // 消费者回调函数
        Consumer(const std::string &ctag, const std::string &qname, bool auto_ack, const ConsumerCallBack &cb)
            : _tag(ctag), _qname(qname), _auto_ack(auto_ack), _cb(cb)
        {
            DLOG("consumer:%s created %p", ctag.c_str(), this);
        }
        Consumer()
        {
            DLOG("consumer created %p", this);
        }
        ~Consumer()
        {
            DLOG("consumer destroyed %p", this);
        }
    };
};

channel.hpp

#pragma once
#include "../common_mq/helper.hpp"
#include "../common_mq/logger.hpp"
#include "../common_mq/msg.pb.h"
#include "../common_mq/myproto.pb.h"
#include <string>
#include <unordered_map>
#include <mutex>
#include <memory>
#include <cassert>
#include <cstring>
#include "muduo/net/TcpConnection.h"
#include <condition_variable>
#include "consumer.hpp"
#include "proto/codec.h"
#include "../common_mq/myproto.pb.h"

namespace mq
{
    using BasicConsumeRspPtr = std::shared_ptr<msg::BasicConsumeRsp>;
    using BasicCommonResponsePtr = std::shared_ptr<msg::BasicCommonResponse>;
    using ProtobufCodecPtr = std::shared_ptr<ProtobufCodec>;
    class Channel
    {
    private:
        std::string _chid;                                                        // 信道ID
        Consumer::ptr _consumer;                                                  // 消费者对象
        muduo::net::TcpConnectionPtr _conn;                                       // 连接对象
        ProtobufCodecPtr _codec;                                                  // 编解码器
        std::mutex _mutex;                                                        // 互斥锁
        std::condition_variable _cv;                                              // 条件变量
        std::unordered_map<std::string, BasicCommonResponsePtr> _consume_rsp_map; // 存放常规响应的map

    public:
        using ptr = std::shared_ptr<Channel>;

        Channel(const muduo::net::TcpConnectionPtr &conn,
                const ProtobufCodecPtr &codec)
            : _conn(conn), _codec(codec)
        {
            _chid = UUIDHelper::uuid();
            DLOG("channel:%s created", _chid.c_str());
        }
        Channel()
        {
        }
        ~Channel()
        {
            basicCancel();
            DLOG("channel:%s destroyed", _chid.c_str());
        }
        std::string chid() const
        {
            return _chid;
        }

        // 发送请求,在服务器端处理请求,创建相应的数据结构对象
        // 1. 声明/删除/查找交换机
        bool declareExchange(const std::string &name, msg::ExchangeType type,
                             bool durable,
                             bool auto_del,
                             google::protobuf::Map<std::string, std::string> &args)
        {
            // 构建请求
            msg::DeclareExchangeReq req;
            req.set_rid(UUIDHelper::uuid());
            req.set_chid(_chid);
            req.set_e_name(name);
            req.set_e_type(type);
            req.set_durable(durable);
            req.set_auto_delete(auto_del);
            req.mutable_args()->swap(args);
            // 发送请求
            _codec->send(_conn, req);
            // 异步操作,需要等待响应
            BasicCommonResponsePtr rsp = waitResponse(req.rid());
            if (rsp->success() != true)
            {
                ELOG("declare exchange failed, name:%s", name.c_str());
                return false;
            }
            return true;
        }
        bool removeExchange(const std::string &name)
        {
            // 构建请求
            msg::RemoveExchangeReq req;
            req.set_rid(UUIDHelper::uuid());
            req.set_chid(_chid);
            req.set_e_name(name);
            // 发送请求
            _codec->send(_conn, req);
            // 异步操作,需要等待响应
            BasicCommonResponsePtr rsp = waitResponse(req.rid());
            if (rsp->success() != true)
            {
                ELOG("remove exchange failed, name:%s", name.c_str());
                return false;
            }
            return true;
        }
        // 2. 声明/删除队列
        bool declareQueue(const std::string &name, bool durable,
                          bool exclusive, bool auto_del,
                          google::protobuf::Map<std::string, std::string> &args)
        {
            // 构建请求
            msg::DeclareQueueReq req;
            req.set_rid(UUIDHelper::uuid());
            req.set_chid(_chid);
            req.set_q_name(name);
            req.set_durable(durable);
            req.set_exclusive(exclusive);
            req.set_auto_delete(auto_del);
            req.mutable_args()->swap(args);
            // 发送请求
            _codec->send(_conn, req);
            // 异步操作,需要等待响应
            BasicCommonResponsePtr rsp = waitResponse(req.rid());
            if (rsp->success() != true)
            {
                ELOG("declare queue failed, name:%s", name.c_str());
                return false;
            }
            return true;
        }
        bool removeQueue(const std::string &name)
        {
            // 构建请求
            msg::RemoveQueueReq req;
            req.set_rid(UUIDHelper::uuid());
            req.set_chid(_chid);
            req.set_q_name(name);
            // 发送请求
            _codec->send(_conn, req);
            // 异步操作,需要等待响应
            BasicCommonResponsePtr rsp = waitResponse(req.rid());
            if (rsp->success() != true)
            {
                ELOG("remove queue failed, name:%s", name.c_str());
                return false;
            }
            return true;
        }

        // 3. 绑定/解绑
        bool bind(const std::string &ename, const std::string &qname, const std::string &key)
        {
            // 构建请求
            msg::BindReq req;
            req.set_rid(UUIDHelper::uuid());
            req.set_chid(_chid);
            req.set_e_name(ename);
            req.set_q_name(qname);
            req.set_bind_key(key);
            // 发送请求
            _codec->send(_conn, req);
            // 异步操作,需要等待响应
            BasicCommonResponsePtr rsp = waitResponse(req.rid());
            if (rsp->success() != true)
            {
                ELOG("bind failed, ename:%s, qname:%s, key:%s", ename.c_str(), qname.c_str(), key.c_str());
                return false;
            }
            return true;
        }
        bool unbind(const std::string &ename, const std::string &qname)
        {
            // 构建请求
            msg::UnbindReq req;
            req.set_rid(UUIDHelper::uuid());
            req.set_chid(_chid);
            req.set_e_name(ename);
            req.set_q_name(qname);
            // 发送请求
            _codec->send(_conn, req);
            // 异步操作,需要等待响应
            BasicCommonResponsePtr rsp = waitResponse(req.rid());
            if (rsp->success() != true)
            {
                ELOG("unbind failed, ename:%s, qname:%s", ename.c_str(), qname.c_str());
                return false;
            }
            return true;
        }
        // 4. 订阅/取消订阅
        bool basicSubscribe(const std::string &qname, const std::string &consumer_tag, bool auto_ack, const ConsumerCallBack &cb)
        {
            if (_consumer.get() != nullptr)
            {
                ELOG("channel has consumer, qname:%s, consumer_tag:%s", qname.c_str(), consumer_tag.c_str());
                return false;
            }

            // 构建请求
            msg::BasicSubscribeReq req;
            req.set_rid(UUIDHelper::uuid());
            req.set_chid(_chid);
            req.set_q_name(qname);
            req.set_consumer_tag(consumer_tag);
            req.set_auto_ack(auto_ack);
            // 发送请求
            _codec->send(_conn, req);
            // 异步操作,需要等待响应
            BasicCommonResponsePtr rsp = waitResponse(req.rid());
            if (rsp->success() != true)
            {
                ELOG("basic subscribe failed, qname:%s, consumer_tag:%s", qname.c_str(), consumer_tag.c_str());
                return false;
            }
            // 注册消费者
            _consumer = std::make_shared<Consumer>(consumer_tag, qname, auto_ack, cb);
            return true;
        }
        void basicCancel()
        {
            if (_consumer.get() == nullptr)
            {
               // DLOG("不是消费者信道,不需要取消订阅");
                return;
            }
            // 构建请求
            msg::BasicCancelReq req;
            req.set_rid(UUIDHelper::uuid());
            req.set_chid(_chid);
            req.set_consumer_tag(_consumer->_tag);
            // 发送请求
            _codec->send(_conn, req);
            // 异步操作,需要等待响应
            BasicCommonResponsePtr rsp = waitResponse(req.rid());
            if (rsp->success() != true)
            {
                ELOG("basic cancel failed, consumer:%s", _consumer->_tag.c_str());
                return;
            }
            // 注销消费者
            _consumer.reset();
            return;
        }
        // 5. 消息的发布,确认
        bool basicPublish(const std::string &ename, msg::BasicAttributes *bp, const std::string &body)
        {
            // 构建请求
            msg::BasicPublishReq req;
            req.set_rid(UUIDHelper::uuid());
            req.set_chid(_chid);
            req.set_e_name(ename);
            req.set_body(body);
            if (bp != nullptr)
            {
                req.mutable_attr()->CopyFrom(*bp);
            }
            // 发送请求
            _codec->send(_conn, req);
            // 异步操作,需要等待响应
            BasicCommonResponsePtr rsp = waitResponse(req.rid());
            if (rsp->success() != true)
            {
                ELOG("basic publish failed, qname:%s", ename.c_str());
                return false;
            }
            return true;
        }

        void basicAck(const std::string &id)
        {
            if (_consumer.get() == nullptr)
            {
                ELOG("channel has no consumer, qname:%s, id:%s", _consumer->_qname.c_str(), id.c_str());
                return;
            }
            // 构建请求
            msg::BasicAckReq req;
            req.set_rid(UUIDHelper::uuid());
            req.set_chid(_chid);
            req.set_q_name(_consumer->_qname);
            req.set_messagg_id(id);
            // 发送请求
            _codec->send(_conn, req);
            // 异步操作,需要等待响应

            BasicCommonResponsePtr rsp = waitResponse(req.rid());
            if (rsp->success() != true)
            {
                ELOG("basic ack failed, qname:%s, id:%s", _consumer->_qname.c_str(), id.c_str());
                return;
            }
            return;
        }

        // 6.打开/关闭信道
        bool openChannel()
        {
            msg::OpenChannelReq req;
            req.set_rid(UUIDHelper::uuid());
            req.set_chid(_chid);
            if(_codec.get() == nullptr)
            {
                ELOG("codec is null");
                return false;
            }
            _codec->send(_conn, req);
            BasicCommonResponsePtr rsp = waitResponse(req.rid());
            return rsp->success() == true;
        }
        void closeChannel()
        {
            msg::CloseChannelReq req;
            req.set_rid(UUIDHelper::uuid());
            req.set_chid(_chid);
            _codec->send(_conn, req);
            BasicCommonResponsePtr rsp = waitResponse(req.rid());
            return;
        }

    private:
        // 等待服务端响应
        BasicCommonResponsePtr waitResponse(const std::string &req_id)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _cv.wait(lock, [this, &req_id]()
                     { return _consume_rsp_map.find(req_id) != _consume_rsp_map.end(); });
            BasicCommonResponsePtr rsp = _consume_rsp_map[req_id];
            _consume_rsp_map.erase(req_id);
            return rsp;
        }

    public:
        // 给连接对象提供的函数,收到来自服务器不同类型的消息,进行不同的处理
        // 收到通用响应,向map中添加响应
        void putBasicCommonResponse(const BasicCommonResponsePtr &rsp)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _consume_rsp_map[rsp->rid()] = rsp;
            _cv.notify_all();
        }
        // 收到来自服务器的消费响应,调用消费者的回调函数
        void consume(const BasicConsumeRspPtr &rsp)
        {
            if (_consumer.get() == nullptr)
            {
                ELOG("channel has no consumer");
                return;
            }
            if (_consumer->_tag != rsp->consumer_tag())
            {
                ELOG("consumer tag not match, consumer_tag:%s", _consumer->_tag.c_str());
                return;
            }
            _consumer->_cb(rsp->consumer_tag(), rsp->mutable_attr(), rsp->body());
        }
    };

    class ChannelManager
    {
    private:
        std::unordered_map<std::string, mq::Channel::ptr> _channels; // 信道管理器
        std::mutex _mutex;                                           // 互斥锁
    public:
        using ptr = std::shared_ptr<ChannelManager>;
        ChannelManager() {}

        Channel::ptr create(const muduo::net::TcpConnectionPtr &conn, const ProtobufCodecPtr &codec)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto channel = std::make_shared<mq::Channel>(conn, codec);
            _channels.insert(std::make_pair(channel->chid(), channel));
            return channel;
        }
        void remove(const std::string &chid)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _channels.erase(chid);
            //DLOG("erase channel:%s", chid.c_str());
        }
        Channel::ptr get(const std::string &chid)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _channels.find(chid);
            if (it == _channels.end())
            {
                return Channel::ptr();
            }
            return it->second;
        }
    };
};

async_worker.hpp

#pragma once
#include <memory>
#include "../common_mq/thread_pool.hpp"
#include "muduo/net/EventLoopThread.h"
#include "../common_mq/helper.hpp"
#include "../common_mq/logger.hpp"

namespace mq
{
    class AsyncWorker
    {
    public:
        using ptr = std::shared_ptr<AsyncWorker>;
        // muduo::net::EventLoopThread _loopThread;
        std::unique_ptr<muduo::net::EventLoopThread> _loopThread;
        ThreadPool _pool;

        AsyncWorker()
            : _loopThread(std::make_unique<muduo::net::EventLoopThread>())
        {
        }
    };
}

connection.hpp

#pragma once
#include "proto/codec.h"
#include "proto/dispatcher.h"

#include "../include/muduo/base/Logging.h"
#include "../include/muduo/base/Mutex.h"
#include "../include/muduo/net/TcpClient.h"
#include "../include/muduo/net/EventLoopThread.h"
#include "../include/muduo/net/TcpConnection.h"
#include "../include/muduo/base/CountDownLatch.h"
// #include "include/muduo/base/Mutex.h"
// #include "include/muduo/net/TcpClient.h"
// #include "include/muduo/net/EventLoopThread.h"
// #include "include/muduo/net/TcpConnection.h"
// #include "include/muduo/base/CountDownLatch.h"
#include <iostream>
#include <functional>
#include <string>
#include <stdio.h>
#include <unistd.h>
#include "async_worker.hpp"
#include "channel.hpp"
namespace mq
{
    using MessagePtr = std::shared_ptr<google::protobuf::Message>;

    class Connection
    {
    private:
        AsyncWorker::ptr _worker;      // 异步工作者
        muduo::CountDownLatch _latch;  // 等待连接成功,通知主线程
        muduo::net::TcpClient _client; // TCP客户端

        muduo::net::TcpConnectionPtr _conn; // TCP连接
        ProtobufDispatcher _dispatcher;     // Protobuf消息派发器
        ProtobufCodecPtr _codec;            // Protobuf编解码器

        ChannelManager::ptr _channelManager; // 信道管理器
    public:
        using ptr = std::shared_ptr<Connection>;
        Connection(const std::string &ip, uint16_t port, const AsyncWorker::ptr &worker)
            : _worker(worker),
              _client(_worker->_loopThread->startLoop(), muduo::net::InetAddress(ip, port), "Connection"),
              _latch(1),
              _dispatcher(std::bind(&Connection::onUnknownMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)),
              _codec(std::make_shared<ProtobufCodec>(std::bind(&ProtobufDispatcher::onProtobufMessage, &_dispatcher, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))),
              _channelManager(std::make_shared<ChannelManager>())
        {
            _client.setConnectionCallback(std::bind(&Connection::onConnection, this, std::placeholders::_1));
            _client.setMessageCallback(std::bind(&ProtobufCodec::onMessage, _codec.get(), std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));

            _dispatcher.registerMessageCallback<msg::BasicConsumeRsp>(std::bind(&Connection::onBasicConsumeRspCb, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
            _dispatcher.registerMessageCallback<msg::BasicCommonResponse>(std::bind(&Connection::onBasicCommonRspCb, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));

            _client.connect();
            _latch.wait();
        }

        Channel::ptr openChannel()
        {
            // 创建客户端channel
            auto newchannel = _channelManager->create(_conn, _codec);
            // 在服务端也要创建channel
            bool ret = newchannel->openChannel();
            if (!ret)
            {
                ELOG("open channel failed");
                return Channel::ptr();
            }

            return newchannel;
        }
        void closeChannel(const Channel::ptr &channel)
        {
            channel->closeChannel();                  // 关闭服务端channel
            std::string chid = channel->chid();
            //DLOG("remove channel: %s", chid.c_str());
            _channelManager->remove(chid); // 删除客户端channel
        }

    private:
        void onConnection(const muduo::net::TcpConnectionPtr &conn)
        {
            if (conn->connected()) // 连接成功
            {
                LOG_INFO << "Connected to " << conn->peerAddress().toIpPort();
                _conn = conn;
                _latch.countDown(); // 通知主线程连接成功,可以发送消息
            }
            else
            {
                LOG_ERROR << "Disconnected from " << conn->peerAddress().toIpPort();
                _conn.reset();
            }
        }
        void onUnknownMessage(const muduo::net::TcpConnectionPtr &conn,
                              const MessagePtr &message,
                              muduo::Timestamp time)
        {
            LOG_ERROR << "Unknown message: " << message->GetTypeName();
            conn->shutdown();
        }

    private:
        void onBasicConsumeRspCb(const muduo::net::TcpConnectionPtr &conn, const BasicConsumeRspPtr &rsp, muduo::Timestamp time)
        {
            auto channel = _channelManager->get(rsp->chid());
            if (channel.get() == nullptr)
            {
                LOG_ERROR << "channel not found: " << rsp->chid();
                return;
            }
            _worker->_pool.push([channel, rsp]
                                {
                                    channel->consume(rsp); // 封装任务,抛给线程池执行
                                });
        }
        void onBasicCommonRspCb(const muduo::net::TcpConnectionPtr &conn, const BasicCommonResponsePtr &rsp, muduo::Timestamp time)
        {
            auto channel = _channelManager->get(rsp->chid());
            if (channel.get() == nullptr)
            {
                LOG_ERROR << "channel not found: " << rsp->chid();
                return;
            }
            channel->putBasicCommonResponse(rsp); // 向map中添加响应,唤醒等待
        }
    };

};

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

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

相关文章

游泳耳机哪个牌子好?四大硬核爆款游泳耳机推荐种草!

随着人们对健康生活方式的不断追求&#xff0c;游泳作为一项全身性的运动受到了越来越多人的喜爱。与此同时&#xff0c;为了在水下也能享受音乐的乐趣&#xff0c;游泳耳机应运而生&#xff0c;并迅速成为泳池和海滩上不可或缺的装备之一。面对市面上琳琅满目的游泳耳机产品&a…

线性表之静态链表

1. 静态链表的设计 1.1 定义静态链表 链表是由多个相同类型的节点组成的线性表&#xff0c;它的每个节点都包含一个数据项和一个指向下一个节点的指针&#xff0c;链表中各个节点的地址是不连续的。 下面是一个用于存储整形数据的链表节点结构&#xff1a; struct Node {int…

深度学习与大模型第1课环境搭建

深度学习与大模型第1课 环境搭建 1. 安装 Anaconda 首先&#xff0c;您需要安装 Anaconda&#xff0c;这是一个开源的 Python 发行版&#xff0c;能够简化包管理和环境管理。以下是下载链接及提取码&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1Na2xOFpBXQMgzXA…

Text Control 控件教程:智能文档处理 (IDP)

TX Text Control 是一款功能类似于 MS Word 的文字处理控件&#xff0c;包括文档创建、编辑、打印、邮件合并、格式转换、拆分合并、导入导出、批量生成等功能。广泛应用于企业文档管理&#xff0c;网站内容发布&#xff0c;电子病历中病案模板创建、病历书写、修改历史、连续打…

【云计算】什么是云计算服务|为什么出现了云计算|云计算的服务模式

文章目录 什么是云计算服务本地部署VS云计算SaaS PaaS IaaS公有云、私有云、混合云为什么优先发展云计算服务的厂商是亚马逊、阿里巴巴等公司 什么是云计算服务 根据不同的目标用户&#xff0c;云计算服务&#xff08;Cloud Computing Services&#xff09;分为两种&#xff1…

前端性能优化:提升网站加载速度的五个关键技巧

聚沙成塔每天进步一点点 本文回顾 ⭐ 专栏简介前端性能优化&#xff1a;提升网站加载速度的五个关键技巧1. 引言2. 前端性能优化的五个关键技巧2.1 减少HTTP请求技巧说明实现示例 2.2 启用浏览器缓存技巧说明实现示例 2.3 使用内容分发网络&#xff08;CDN&#xff09;技巧说明…

完美洗牌的秘密(五)——完美洗牌的性质和变体

‍ ‍早点关注我&#xff0c;精彩不错过&#xff01; 之前4篇文章&#xff0c;我们介绍清楚了完美洗牌的3对基本定理。今天&#xff0c;我们来看在这一般的描述基础上&#xff0c;还有哪些常见性质和变体值得探讨。 所谓操作的性质&#xff0c;在扑克牌数学魔术的语境下&#x…

公务员事业编【判断推理】 之“逻辑判断” 个人NOTE

目录 1、翻译推理 1.1 翻译规则之“前推后” 1.2 翻译规则之“逆否等价” 1.3 翻译规则之“后推前” 1.4 且和或 2、组合排列&#xff08;代入法/排除法&#xff09; 3、日常结论 4、逻辑论证 4.1 加强类 4.1.1 搭桥&#xff08;论证力度最强&#xff09; 4.1.2 补充…

系统架构设计师 - 软件架构设计(2)

软件架构设计 软件架构设计&#xff08;20多分&#xff09;最重要基于架构的软件开发 ★ ★ ★概念开发过程架构需求、设计过程架构文档化架构复审架构实现、演化过程 软件质量属性 ★ ★ ★ ★ ★软件架构评估 ★ ★ ★ ★ ★概念架构评估方法评估方式基于场景的评估方法 软件…

EmguCV学习笔记 VB.Net 9.1 VideoCapture类

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 EmguCV是一个基于OpenCV的开源免费的跨平台计算机视觉库,它向C#和VB.NET开发者提供了OpenCV库的大部分功能。 教程VB.net版本请访问…

人工智能开发实战MNIST数据集及神经网络完全解析

内容提要 MNIST数据集简介神经元常用函数深度神经网络卷积神经网络介绍循环神经网络 一、MNIST数据集简介 数据集&#xff08;Dataset&#xff09;是一类数据的集合。传统的数据集通常表现为表格或者文档形式&#xff0c;每个数值被称为数据资料。 MNIST数据集是一个含有手写…

ARM32开发——(二十四)电源管理单元

1. 重点 了解什么是电池管理单元PMU了解ARM32中的电源域了解几种省电模式 2. 主要内容 2.1 PMU PMU全称Power Management Unit&#xff0c;电源管理单元。 2.2 电源域 总共有三大电源域&#xff0c;包括VDD / VDDA域&#xff0c;1.2V域和备份域。 2.2.1 VDD/VDDA域 VDD/…

2024最新最全:网络安全软件大合集(非常详细)零基础入门到精通,收藏这一篇就够了

安全建议&#xff1a;渗透类软件&#xff0c;建议先在虚拟机试运行&#xff01; VMware虚拟机 https://pan.quark.cn/s/6e439e2c15c1 下载KALI&#xff08;安装版&#xff09; https://pan.quark.cn/s/2124bdf3c732 下载KALI&#xff08;免安装版&#xff09; https://pa…

解决警告【WARNING: Ignoring invalid distribution -xxx 】

1、问题发生 出现警告【WARNING: Ignoring invalid distribution -umpy (c:\programdata\anaconda3\lib\site-packages)】 2、解决方法 进入报错文件夹 &#xff0c;删除相关文件夹 3、解决效果

【C++标准模版库】模拟实现容器适配器:stack、queue、priority_queue(优先级队列)

stack和queue 一.容器适配器1.什么是适配器 二.模拟实现stack和queue三.STL标准库中stack和queue的底层结构四.deque&#xff08;双端队列&#xff09;的简单介绍五.deque作为stack和queue的默认容器的原因六.priority_queue&#xff08;优先级队列&#xff09;的介绍和使用七.…

前端性能优化--元素类型和dom层级

展示相同布局&#xff0c;使用控制变量法&#xff0c;对比性能差距 1. 结论&#xff1a;用块级元素模拟行内元素时&#xff0c;会有性能浪费&#xff0c;所以能用行内元素的&#xff0c;就不要使用块元素(能用span就不用div) 2. 结论&#xff1a;行内元素模拟块级元素时&…

rk3588_dp调试

配置流程&#xff1a; 开启usbusb需要绑定dpdp绑定vp 查看rk3588内部寄存器可知 需要在USB3.0控制器中打开DP0 在设备树中需要在usb3dp端口。 usbc0: fusb3024e { ......ports {port1 { reg <1>; …

SpringBoot配置MybatisPlus

文章目录 介绍特性工作流程图添加依赖Spring Boot2Spring Boot3 配置定义Mapper接口并继承BaseMapperServer 接口自定义 Service 接口继承 IServie 接口自定义 Service 实现类&#xff0c;实现自定义接口并继承 ServiceImpl 添加Config类 介绍 MyBatis-Plus 是一个 MyBatis 的…

uniapp钱包支付、与设置密码页面

设置密码页面 <template><view class="paymentCodeBox"><!-- 自定义键盘 -->

计算机毕业设计推荐-基于python的电子图书馆数据可视化分析

&#x1f496;&#x1f525;作者主页&#xff1a;毕设木哥 精彩专栏推荐订阅&#xff1a;在 下方专栏&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; 实战项目 文章目录 实战项目 一、基于python的电子图书馆数据…