仿RabbitMQ实现消息队列客户端

news2024/11/28 22:47:48

文章目录

  • 客⼾端模块实现
  • 订阅者模块
  • 信道管理模块
  • 异步⼯作线程实现
  • 连接管理模块
  • 生产者客户端
  • 消费者客户端

客⼾端模块实现

在RabbitMQ中,提供服务的是信道,因此在客⼾端的实现中,弱化了Client客⼾端的概念,也就是说在RabbitMQ中并不会向⽤⼾展⽰⽹络通信的概念出来,⽽是以⼀种提供服务的形式来体现。其实现思想类似于普通的功能接⼝封装,⼀个接⼝实现⼀个功能,接⼝内部完成向客⼾端请求的过程,但是对外并不需要体现出客⼾端与服务端通信的概念,⽤⼾需要什么服务就调⽤什么接⼝就⾏。

基于以上的思想,客⼾端的实现共分为四⼤模块:

在这里插入图片描述
基于以上模块,实现⼀个客⼾端的流程也就⽐较简单了

  1. 实例化异步线程对象
  2. 实例化连接对象
  3. 通过连接对象,创建信道
  4. 根据信道获取⾃⼰所需服务
  5. 关闭信道
  6. 关闭连接

订阅者模块

与服务端,并⽆太⼤差别,客⼾端这边虽然订阅者的存在感微弱了很多,但是还是有的,当进⾏队列消息订阅的时候,会伴随着⼀个订阅者对象的创建,⽽这个订阅者对象有以下⼏个作⽤:

  • 描述当前信道订阅了哪个队列的消息。
  • 描述了收到消息后该如何对这条消息进⾏处理
  • 描述收到消息后是否需要进⾏确认回复
    在这里插入图片描述
  1. 订阅者信息:
  • 订阅者标识
  • 订阅队列名
  • 是否⾃动确认标志
  • 回调处理函数(收到消息后该如何处理的回调函数对象)
    using ConsumerCallback = std::function<void(const std::string, const BasicProperties *bp, const std::string)>;
    struct Consumer {
        using ptr = std::shared_ptr<Consumer>;
        std::string tag;    //消费者标识
        std::string qname;  //消费者订阅的队列名称
        bool auto_ack;      //自动确认标志
        ConsumerCallback callback;

        Consumer(){
            DLOG("new Consumer: %p", this);
        }
        Consumer(const std::string &ctag, const std::string &queue_name,  bool ack_flag, const ConsumerCallback &cb):
            tag(ctag), qname(queue_name), auto_ack(ack_flag), callback(std::move(cb)) {
            DLOG("new Consumer: %p", this);
        }
        ~Consumer() {
            DLOG("del Consumer: %p", this);
        }
    };

信道管理模块

同样的,客⼾端也有信道,其功能与服务端⼏乎⼀致,或者说不管是客⼾端的channel还是服务端的channel都是为了⽤⼾提供具体服务⽽存在的,只不过服务端是为客⼾端的对应请求提供服务,⽽客⼾端的接⼝服务是为了⽤⼾具体需要服务,也可以理解是⽤⼾通过客⼾端channel的接⼝调⽤来向服务端发送对应请求,获取请求的服务。
在这里插入图片描述

  1. 信道信息:
  • a. 信道ID
  • b. 信道关联的⽹络通信连接对象
  • c. protobuf协议处理对象
  • d. 信道关联的消费者
  • e. 请求对应的响应信息队列(这⾥队列使⽤<请求ID,响应>hash表,以便于查找指定的响应)
  • f. 互斥锁&条件变量(⼤部分的请求都是阻塞操作,发送请求后需要等到响应才能继续,但是muduo库的通信是异步的,因此需要我们⾃⼰在收到响应后,通过判断是否是等待的指定响应来进⾏同步)
  1. 信道操作:
  • a. 提供创建信道操作
  • b. 提供删除信道操作
  • c. 提供声明交换机操作(强断⾔-有则OK,没有则创建)
  • d. 提供删除交换机
  • e. 提供创建队列操作(强断⾔-有则OK,没有则创建)
  • f. 提供删除队列操作
  • g. 提供交换机-队列绑定操作
  • h. 提供交换机-队列解除绑定操作
  • i . 提供添加订阅操作
  • j. 提供取消订阅操作
  • k. 提供发布消息操作
  • l. 提供确认消息操作
    typedef std::shared_ptr<google::protobuf::Message> MessagePtr;
    using ProtobufCodecPtr = std::shared_ptr<ProtobufCodec>;
    using basicConsumeResponsePtr = std::shared_ptr<basicConsumeResponse>;
    using basicCommonResponsePtr = std::shared_ptr<basicCommonResponse>;
    class Channel {
        public:
            using ptr = std::shared_ptr<Channel>;
            Channel(const muduo::net::TcpConnectionPtr& conn, const ProtobufCodecPtr& codec):
                _cid(UUIDHelper::uuid()), _conn(conn), _codec(codec) {}
            ~Channel() { basicCancel(); }
            std::string cid() { return _cid; }
            bool openChannel() {
                std::string rid = UUIDHelper::uuid();
                openChannelRequest req; 
                req.set_rid(rid);
                req.set_cid(_cid);
                _codec->send(_conn, req);
                basicCommonResponsePtr resp = waitResponse(rid);
                return resp->ok();
            }
            void closeChannel() {
                std::string rid = UUIDHelper::uuid();
                closeChannelRequest req; 
                req.set_rid(rid);
                req.set_cid(_cid);
                _codec->send(_conn, req);
                waitResponse(rid);
                return ;
            }
            bool declareExchange(
                const std::string &name,
                ExchangeType type, 
                bool durable, 
                bool auto_delete,
                google::protobuf::Map<std::string, std::string> &args) {
                //构造一个声明虚拟机的请求对象,
                std::string rid = UUIDHelper::uuid();
                declareExchangeRequest req;
                req.set_rid(rid);
                req.set_cid(_cid);
                req.set_exchange_name(name);
                req.set_exchange_type(type);
                req.set_durable(durable);
                req.set_auto_delete(auto_delete);
                req.mutable_args()->swap(args);
                //然后向服务器发送请求
                _codec->send(_conn, req);
                //等待服务器的响应
                basicCommonResponsePtr resp = waitResponse(rid);
                //返回
                return resp->ok();
            }
            void deleteExchange(const std::string &name) {
                std::string rid = UUIDHelper::uuid();
                deleteExchangeRequest req;
                req.set_rid(rid);
                req.set_cid(_cid);
                req.set_exchange_name(name);
                _codec->send(_conn, req);
                waitResponse(rid);
                return ;
            }

            bool declareQueue(
                const std::string &qname, 
                bool qdurable, 
                bool qexclusive,
                bool qauto_delete,
                google::protobuf::Map<std::string, std::string> &qargs) {
                std::string rid = UUIDHelper::uuid();
                declareQueueRequest req;
                req.set_rid(rid);
                req.set_cid(_cid);
                req.set_queue_name(qname);
                req.set_durable(qdurable);
                req.set_auto_delete(qauto_delete);
                req.set_exclusive(qexclusive);
                req.mutable_args()->swap(qargs);
                _codec->send(_conn, req);
                basicCommonResponsePtr resp = waitResponse(rid);
                return resp->ok();
            }
            void deleteQueue(const std::string &qname) {
                std::string rid = UUIDHelper::uuid();
                deleteQueueRequest req;
                req.set_rid(rid);
                req.set_cid(_cid);
                req.set_queue_name(qname);
                _codec->send(_conn, req);
                waitResponse(rid);
                return ;
            }

            bool queueBind(
                const std::string &ename, 
                const std::string &qname, 
                const std::string &key) {
                std::string rid = UUIDHelper::uuid();
                queueBindRequest req;
                req.set_rid(rid);
                req.set_cid(_cid);
                req.set_exchange_name(ename);
                req.set_queue_name(qname);
                req.set_binding_key(key);
                _codec->send(_conn, req);
                basicCommonResponsePtr resp = waitResponse(rid);
                return resp->ok();
            }
            void queueUnBind(const std::string &ename, const std::string &qname) {
                std::string rid = UUIDHelper::uuid();
                queueUnBindRequest req;
                req.set_rid(rid);
                req.set_cid(_cid);
                req.set_exchange_name(ename);
                req.set_queue_name(qname);
                _codec->send(_conn, req);
                waitResponse(rid);
                return ;
            }

            void basicPublish(
                const std::string &ename,
                const BasicProperties *bp,
                const std::string &body) {
                std::string rid = UUIDHelper::uuid();
                basicPublishRequest req;
                req.set_rid(rid);
                req.set_cid(_cid);
                req.set_body(body);
                req.set_exchange_name(ename);
                if (bp != nullptr) {
                    req.mutable_properties()->set_id(bp->id());
                    req.mutable_properties()->set_delivery_mode(bp->delivery_mode());
                    req.mutable_properties()->set_routing_key(bp->routing_key());
                }
                _codec->send(_conn, req);
                waitResponse(rid);
                return ;
            }

            void basicAck(const std::string &msgid) {
                if (_consumer.get() == nullptr) {
                    DLOG("消息确认时,找不到消费者信息!");
                    return ;
                }
                std::string rid = UUIDHelper::uuid();
                basicAckRequest req;
                req.set_rid(rid);
                req.set_cid(_cid);
                req.set_queue_name(_consumer->qname);
                req.set_message_id(msgid);
                _codec->send(_conn, req);
                waitResponse(rid);
                return;
            }
            
            void basicCancel() {
                if (_consumer.get() == nullptr) {
                    return ;
                }
                std::string rid = UUIDHelper::uuid();
                basicCancelRequest req;
                req.set_rid(rid);
                req.set_cid(_cid);
                req.set_queue_name(_consumer->qname);
                req.set_consumer_tag(_consumer->tag);
                _codec->send(_conn, req);
                waitResponse(rid);
                _consumer.reset();
                return;
            }

            bool basicConsume(
                const std::string &consumer_tag,
                const std::string &queue_name,
                bool auto_ack,
                const ConsumerCallback &cb) {
                if (_consumer.get() != nullptr) {
                    DLOG("当前信道已订阅其他队列消息!");
                    return false;
                }
                std::string rid = UUIDHelper::uuid();
                basicConsumeRequest req;
                req.set_rid(rid);
                req.set_cid(_cid);
                req.set_queue_name(queue_name);
                req.set_consumer_tag(consumer_tag);
                req.set_auto_ack(auto_ack);
                _codec->send(_conn, req);
                basicCommonResponsePtr resp =  waitResponse(rid);
                if (resp->ok() == false) {
                    DLOG("添加订阅失败!");
                    return false;
                }
                _consumer = std::make_shared<Consumer>(consumer_tag, queue_name, auto_ack, cb);
                return true;
            }
        public:   
            //连接收到基础响应后,向hash_map中添加响应
            void putBasicResponse(const basicCommonResponsePtr& resp) {
                std::unique_lock<std::mutex> lock(_mutex);
                _basic_resp.insert(std::make_pair(resp->rid(), resp));
                _cv.notify_all();
            }
            
            //连接收到消息推送后,需要通过信道找到对应的消费者对象,通过回调函数进行消息处理
            void consume(const basicConsumeResponsePtr& resp) {
                if (_consumer.get() == nullptr) {
                    DLOG("消息处理时,未找到订阅者信息!");
                    return;
                }
                if (_consumer->tag != resp->consumer_tag()) {
                    DLOG("收到的推送消息中的消费者标识,与当前信道消费者标识不一致!");
                    return ;
                }
                _consumer->callback(resp->consumer_tag(), resp->mutable_properties(), resp->body());
            }
        private:
            basicCommonResponsePtr waitResponse(const std::string &rid) {
                std::unique_lock<std::mutex> lock(_mutex);
                _cv.wait(lock, [&rid, this](){
                    return _basic_resp.find(rid) != _basic_resp.end();
                });
                //while(condition()) _cv.wait();
                basicCommonResponsePtr basic_resp = _basic_resp[rid];
                _basic_resp.erase(rid);
                return basic_resp;
            }
        private:
            std::string _cid;
            muduo::net::TcpConnectionPtr _conn;
            ProtobufCodecPtr _codec;
            Consumer::ptr _consumer;
            std::mutex _mutex;
            std::condition_variable _cv;
            std::unordered_map<std::string, basicCommonResponsePtr> _basic_resp;
    };
  1. 信道管理:
  • a. 创建信道
  • b. 查询信道
  • c. 删除信道
    class ChannelManager {
        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<Channel>(conn, codec);
                _channels.insert(std::make_pair(channel->cid(), channel));
                return channel;
            }
            void remove(const std::string &cid) {
                std::unique_lock<std::mutex> lock(_mutex);
                _channels.erase(cid);
            }
            Channel::ptr get(const std::string &cid) {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _channels.find(cid);
                if (it == _channels.end()) {
                    return Channel::ptr();
                }
                return it->second;
            }
        private:
            std::mutex _mutex;
            std::unordered_map<std::string, Channel::ptr> _channels;
    };

异步⼯作线程实现

客⼾端这边存在两个异步⼯作线程,

  • ⼀个是muduo库中客⼾端连接的异步循环线程EventLoopThread
  • ⼀个是当收到消息后进⾏异步处理的⼯作线程池。

这两项都不是以连接为单元进⾏创建的,⽽是创建后,可以⽤以多个连接中,因此单独进⾏封装。

class AsyncWorker {
        public:
            using ptr = std::shared_ptr<AsyncWorker>;
            muduo::net::EventLoopThread loopthread;
            threadpool pool;
    };

连接管理模块

在客⼾端这边,RabbitMQ弱化了客⼾端的概念,因为⽤⼾所需的服务都是通过信道来提供的,因此操作思想转换为先创建连接,通过连接创建信道,通过信道提供服务这⼀流程。

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

在这里插入图片描述

    class Connection
    {
    public:
        using ptr = std::shared_ptr<Connection>;
        Connection(const std::string &sip, int sport, const AsyncWorker::ptr &worker)
            : _latch(1), _client(worker->loopthread.startLoop(), muduo::net::InetAddress(sip, sport), "Client"),
              _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))),
              _worker(worker),
              _channel_manager(std::make_shared<ChannelManager>())
        {

            _dispatcher.registerMessageCallback<basicCommonResponse>(std::bind(&Connection::basicResponse, this,
                std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));

            _dispatcher.registerMessageCallback<basicConsumeResponse>(std::bind(&Connection::consumeResponse, this,
                std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));

            _client.setMessageCallback(std::bind(&ProtobufCodec::onMessage, _codec.get(),
                std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
            _client.setConnectionCallback(std::bind(&Connection::onConnection, this, std::placeholders::_1));

            _client.connect();
            _latch.wait(); // 阻塞等待,直到连接建立成功
        }
        Channel::ptr openChannel()
        {
            Channel::ptr channel = _channel_manager->create(_conn, _codec);
            bool ret = channel->openChannel();
            if (ret == false)
            {
                DLOG("打开信道失败!");
                return Channel::ptr();
            }
            return channel;
        }
        void closeChannel(const Channel::ptr &channel)
        {
            channel->closeChannel();
            _channel_manager->remove(channel->cid());
        }

    private:
        void basicResponse(const muduo::net::TcpConnectionPtr &conn, const basicCommonResponsePtr &message, muduo::Timestamp)
        {
            // 1. 找到信道
            Channel::ptr channel = _channel_manager->get(message->cid());
            if (channel.get() == nullptr)
            {
                DLOG("未找到信道信息!");
                return;
            }
            // 2. 将得到的响应对象,添加到信道的基础响应hash_map中
            channel->putBasicResponse(message);
        }
        void consumeResponse(const muduo::net::TcpConnectionPtr &conn, const basicConsumeResponsePtr &message, muduo::Timestamp)
        {
            // 1. 找到信道
            Channel::ptr channel = _channel_manager->get(message->cid());
            if (channel.get() == nullptr)
            {
                DLOG("未找到信道信息!");
                return;
            }
            // 2. 封装异步任务(消息处理任务),抛入线程池
            _worker->pool.push([channel, message]()
                               { channel->consume(message); });
        }
        void onUnknownMessage(const muduo::net::TcpConnectionPtr &conn, const MessagePtr &message, muduo::Timestamp)
        {
            LOG_INFO << "onUnknownMessage: " << message->GetTypeName();
            conn->shutdown();
        }
        void onConnection(const muduo::net::TcpConnectionPtr &conn)
        {
            if (conn->connected())
            {
                _latch.countDown(); // 唤醒主线程中的阻塞
                _conn = conn;
            }
            else
            {
                // 连接关闭时的操作
                _conn.reset();
            }
        }

    private:
        muduo::CountDownLatch _latch;       // 实现同步的
        muduo::net::TcpConnectionPtr _conn; // 客户端对应的连接
        muduo::net::TcpClient _client;      // 客户端
        ProtobufDispatcher _dispatcher;     // 请求分发器
        ProtobufCodecPtr _codec;            // 协议处理器

        AsyncWorker::ptr _worker;
        ChannelManager::ptr _channel_manager;
    };

生产者客户端

publish_client.cc

  1. 实例化异步工作线程对象
  2. 实例化连接对象
  3. 通过连接创建信道
  4. 通过信道提供的服务完成所需
  5. 循环向交换机发布消息
  6. 关闭信道
#include "connection.hpp"

int main()
{
    //1. 实例化异步工作线程对象
    nzq::AsyncWorker::ptr awp = std::make_shared<nzq::AsyncWorker>();
    //2. 实例化连接对象
    nzq::Connection::ptr conn = std::make_shared<nzq::Connection>("127.0.0.1",8085,awp);
    //3. 通过连接创建信道
    nzq::Channel::ptr channel = conn->openChannel();
    //4. 通过信道提供的服务完成所需
    //  1. 声明一个交换机exchange1, 交换机类型为广播模式
    google::protobuf::Map<std::string,std::string> tmp_map;
    channel->declareExchange("exchange1", nzq::ExchangeType::TOPIC, true, false, tmp_map);
    //  2. 声明一个队列queue1
    channel->declareQueue("queue1", true, false, false, tmp_map);
    //  3. 声明一个队列queue2
    channel->declareQueue("queue2", true, false, false, tmp_map);
    //  4. 绑定queue1-exchange1,且binding_key设置为queue1
    channel->queueBind("exchange1", "queue1", "queue1");
    //  5. 绑定queue2-exchange1,且binding_key设置为news.music.#
    channel->queueBind("exchange1", "queue2", "news.music.#");
    //5. 循环向交换机发布消息
    for (int i = 0; i < 10; i++) {
        nzq::BasicProperties bp;
        bp.set_id(nzq::UUIDHelper::uuid());
        bp.set_delivery_mode(nzq::DeliveryMode::DURABLE);
        bp.set_routing_key("news.music.pop");
        channel->basicPublish("exchange1", &bp, "Hello World-" + std::to_string(i));
    }
    nzq::BasicProperties bp;
    bp.set_id(nzq::UUIDHelper::uuid());
    bp.set_delivery_mode(nzq::DeliveryMode::DURABLE);
    bp.set_routing_key("news.music.sport");
    channel->basicPublish("exchange1", &bp, "Hello linux");

    bp.set_routing_key("news.sport");
    channel->basicPublish("exchange1", &bp, "Hello chileme?");
    //6. 关闭信道
    conn->closeChannel(channel);
    return 0;
    return 0;
}

消费者客户端

同生产者客户端,如何消费需要自己设置,下面只是其中一种

void cb(nzq::Channel::ptr &channel, const std::string consumer_tag, 
    const nzq::BasicProperties *bp, const std::string &body)
{
    std::cout << consumer_tag << "消费了消息:" << body << std::endl;
    channel->basicAck(bp->id());
}
int main(int argc, char *argv[])
{
    if (argc != 2) {
        std::cout << "usage: ./consume_client queue1\n";
        return -1;
    }
    //1. 实例化异步工作线程对象
    nzq::AsyncWorker::ptr awp = std::make_shared<nzq::AsyncWorker>();
    //2. 实例化连接对象
    nzq::Connection::ptr conn = std::make_shared<nzq::Connection>("127.0.0.1", 8085, awp);
    //3. 通过连接创建信道
    nzq::Channel::ptr channel = conn->openChannel();
    //4. 通过信道提供的服务完成所需
    //  1. 声明一个交换机exchange1, 交换机类型为广播模式
    google::protobuf::Map<std::string, std::string> tmp_map;
    channel->declareExchange("exchange1", nzq::ExchangeType::TOPIC, true, false, tmp_map);
    //  2. 声明一个队列queue1
    channel->declareQueue("queue1", true, false, false, tmp_map);
    //  3. 声明一个队列queue2
    channel->declareQueue("queue2", true, false, false, tmp_map);
    //  4. 绑定queue1-exchange1,且binding_key设置为queue1
    channel->queueBind("exchange1", "queue1", "queue1");
    //  5. 绑定queue2-exchange1,且binding_key设置为news.music.#
    channel->queueBind("exchange1", "queue2", "news.music.#");

    auto functor = std::bind(cb, channel, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
    channel->basicConsume("consumer1", argv[1], false, functor);

    while(1) std::this_thread::sleep_for(std::chrono::seconds(3));
    conn->closeChannel(channel);

    return 0;
}

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

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

相关文章

V2M2引擎源码BlueCodePXL源码完整版

V2M2引擎源码BlueCodePXL源码完整版 链接: https://pan.baidu.com/s/1ifcTHAxcbD2CyY7gDWRVzQ?pwdmt4g 提取码: mt4g 参考资料&#xff1a;BlueCodePXL源码完整版_1234FCOM专注游戏工具及源码例子分享

图解大模型计算加速系列:vLLM源码解析3,块管理器(BlockManager)上篇

vllm块管理器又分成朴素块管理器&#xff08;UncachedBlockAllocator&#xff09;和prefix caching型块管理器&#xff08;CachedBlockAllocator&#xff09;。本篇我们先讲比较简单的前者&#xff0c;下篇我们来细看更有趣也是更难的后者。 【全文目录如下】 【1】前情提要…

阿里巴巴开源的FastJson 1反序列化漏洞复现攻击保姆级教程

免责申明 本文仅是用于学习检测自己搭建的靶场环境有关FastJson1反序列化漏洞的原理和攻击实验,请勿用在非法途径上,若将其用于非法目的,所造成的一切后果由您自行承担,产生的一切风险和后果与笔者无关;本文开始前请认真详细学习《‌中华人民共和国网络安全法》‌及其所在…

Linux高级编程_29_信号

文章目录 进程间通讯 - 信号信号完整的信号周期信号的编号信号的产生发送信号1 kill 函数(他杀)作用&#xff1a;语法&#xff1a;示例&#xff1a; 2 raise函数(自杀)作用&#xff1a;示例&#xff1a; 3 abort函数(自杀)作用&#xff1a;语法&#xff1a;示例&#xff1a; 4 …

macos安装git并连接gitCode远程仓库

文章目录 资料地址下载和安装初始化配置本地全局配置&#xff0c;SSH公私密钥生成远程SSH key配置 新建代码仓库&#xff0c;并关联到本地 资料地址 git官网地址gitCode地址 下载和安装 打开git官网地址&#xff0c;直接下载。【不建议使用brew&#xff0c;因为本人实践&…

【Qt】控件概述(2)—— 按钮类控件

控件概述&#xff08;2&#xff09; 1. PushButton2. RadioButton——单选按钮2.1 使用2.2 区分信号 clicked&#xff0c;clicked(bool)&#xff0c;pressed&#xff0c;released&#xff0c;toggled(bool)2.3 QButtonGroup分组 3. CheckBox——复选按钮 1. PushButton QPushB…

B树系列解析

我最近开了几个专栏&#xff0c;诚信互三&#xff01; > |||《算法专栏》&#xff1a;&#xff1a;刷题教程来自网站《代码随想录》。||| > |||《C专栏》&#xff1a;&#xff1a;记录我学习C的经历&#xff0c;看完你一定会有收获。||| > |||《Linux专栏》&#xff1…

etcd 快速入门

简介 随着go与kubernetes的大热&#xff0c;etcd作为一个基于go编写的分布式键值存储&#xff0c;逐渐为开发者所熟知&#xff0c;尤其是其还作为kubernetes的数据存储仓库&#xff0c;更是引起广泛专注。 本文我们就来聊一聊etcd到底是什么及其工作机制。 首先&#xff0c;…

查找回收站里隐藏的文件

在Windows里&#xff0c;每个磁盘分区都有一个隐藏的回收站Recycle&#xff0c; 回收站里保存着用户删除的文件、图片、视频等数据&#xff0c;比如&#xff0c;C盘的回收站为C:\RECYCLE.BIN\&#xff0c;D盘的的回收站为D:\RECYCLE.BIN\&#xff0c;E盘的的回收站为E:\RECYCLE…

【解决方案】JVM调优:给定资源条件下减少Full GC频率

1 缘起 在一次其他团队技术分享时,有幸进行了旁听, 谈到一个应用场景,服务端在给定的资源下,频繁Full GC, 降低了服务请求处理能力以及任务处理能力,频繁Full GC,导致服务处理能力下降, 服务在Full GC期间无法处理用户请求以及其他任务,服务不稳定,可以理解为服务在…

【C++算法】9.双指针_四数之和

文章目录 题目链接&#xff1a;题目描述&#xff1a;解法C 算法代码&#xff1a;图解 题目链接&#xff1a; 18.四数之和 题目描述&#xff1a; 解法 解法一&#xff1a;排序暴力枚举利用set去重 解法二&#xff1a;排序双指针 从左往右依次固定一个数a在a后面的区间里&#x…

坐标系变换总结

二维情况下的转换 1 缩放变换 形象理解就是图像在x方向和y方向上放大或者缩小。 代数形式&#xff1a; { x ′ k x x y ′ k y y \begin{cases} x k_x x \\ y k_y y \end{cases} {x′kx​xy′ky​y​ 矩阵形式&#xff1a; ( x ′ y ′ ) ( k x 0 0 k y ) ( x y ) \be…

【C语言】数据在内存中的存储(万字解析)

文章目录 一、大小端字节序和字节序判断1.案例引入2.什么是大小端字节序3.大小端字节序判断 二、整数在内存中的存储以及相关练习1.整型在内存中的存储2.练习练习1&#xff1a;练习2练习3练习4练习5&#xff1a;练习6 三、浮点数在内存中的存储1.案例引入2.浮点数在内存中的存储…

uniapp+Android面向网络学习的时间管理工具软件 微信小程序

目录 项目介绍支持以下技术栈&#xff1a;具体实现截图HBuilderXuniappmysql数据库与主流编程语言java类核心代码部分展示登录的业务流程的顺序是&#xff1a;数据库设计性能分析操作可行性技术可行性系统安全性数据完整性软件测试详细视频演示源码获取方式 项目介绍 用户功能…

KVM虚拟化技术介绍

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 虚拟化技术是云计算的基础&#xff0c;什么是虚拟化&#xff1f;虚拟化技术的本质是什么&#xff1f;主流的虚拟化技术有哪些&#xff1f;本章将为您揭晓 一.虚拟化概述 虚拟化是一种将计…

【有啥问啥】领域自适应(Domain Adaptation, DA)详解

领域自适应&#xff08;Domain Adaptation, DA&#xff09;详解 引言 在机器学习和深度学习的广泛应用中&#xff0c;一个核心挑战在于模型往往在一个特定数据集&#xff08;源领域&#xff09;上训练后&#xff0c;难以直接应用于另一个不同但相关的数据集&#xff08;目标领…

通信工程学习:什么是ICMP因特网控制报文协议

ICMP&#xff1a;因特网控制报文协议 ICMP&#xff08;Internet Control Message Protocol&#xff0c;因特网控制报文协议&#xff09;是TCP/IP协议簇中的一个重要子协议&#xff0c;主要用于在IP主机和路由器之间传递控制消息。以下是关于ICMP协议的详细解释&#xff1a; 一…

Pikachu-Unsafe FileUpload-客户端check

上传图片&#xff0c;点击查看页面的源码&#xff0c; 可以看到页面的文件名校验是放在前端的&#xff1b;而且也没有发起网络请求&#xff1b; 所以&#xff0c;可以通过直接修改前端代码&#xff0c;删除 checkFileExt(this.value) 这部分&#xff1b; 又或者先把文件名改成…

九、2 USART串口外设

1、STM32内部的USART外设的介绍 &#xff08;1&#xff09; STM32的USART的同步模式只是多了个时钟输出&#xff0c;只支持时钟输出&#xff0c;不支持时钟输入。该同步模式更多是为了兼容别的协议或者特殊用途而设计的&#xff0c;并不支持两个USART之间进行同步通信&#xf…

剖解最小栈

最小栈 思路&#xff1a; 1. 首先实例化两个栈&#xff0c;分别是stack用于存放数据&#xff0c;minstack用于存放最小值 2. 将第一个元素压入两个栈中&#xff0c;判断此时若minStack栈中为空&#xff0c;则表示压入的为第一个数据 if ( minStack.empty () ) { minStack.pus…