当前位置: 首页 > news >正文

手撕基于AMQP协议的简易消息队列-7(客户端模块的编写)

在MQClient中编写客户端模块代码

在MQClient中编写makefile文件来编译客户端模块
.PHONY:all
all:PublichClient ConsumeClient
PublichClient : PublichClient.cpp ../MQCommon/request.pb.cc ../MQCommon/message.pb.cc ../ThirdLib/lib/include/muduo/protobuf/codec.ccg++ -g -std=c++11 $^ -o $@ -I../ThirdLib/lib/include -L../ThirdLib/lib/lib  -lmuduo_net -lmuduo_base -pthread -lprotobuf -lz
ConsumeClient : ConsumeClient.cpp  ../MQCommon/request.pb.cc ../MQCommon/message.pb.cc ../ThirdLib/lib/include/muduo/protobuf/codec.ccg++ -g -std=c++11 $^ -o $@ -I../ThirdLib/lib/include -L../ThirdLib/lib/lib  -lmuduo_net -lmuduo_base -pthread -lprotobuf -lz
在MQClient中编写Subscriber.hpp文件实现订阅者模块
  • 与服务端,并⽆太⼤差别

  • 在该文件中,应该实现的功能:

    • 订阅者信息:

      1. 订阅者标识
      2. 订阅队列名
      3. 是否⾃动确认标志
      4. 回调处理函数(收到消息后该如何处理的回调函数对象)
    • 实现函数

      #ifndef __M_Subscriber_H__
      #define __M_Subscriber_H__
      #include "../MQCommon/Helper.hpp"
      #include "../MQCommon/Logger.hpp"
      #include "../MQCommon/message.pb.h"
      #include <google/protobuf/map.h>
      #include <iostream>
      #include <memory>
      #include <mutex>
      #include <unordered_map>namespace MQ
      {using SubscriberCallback = std::function<void(const std::string, const BasicProperties *bp, const std::string)>;class Subscriber{public:using ptr = std::shared_ptr<Subscriber>;// 构造函数Subscriber() {}Subscriber(const std::string &consumer_tag, const std::string &subscribe_queue_name, bool auto_ack, const SubscriberCallback &callback): _auto_ack(auto_ack),_subscribe_queue_name(subscribe_queue_name),_subscribe_queue_tag(consumer_tag),_callback(callback){}// 析构函数virtual ~Subscriber() {}public:// 自动应答标志bool _auto_ack;// 订阅的队列名称std::string _subscribe_queue_name;// 消费者标识std::string _subscribe_queue_tag;// 消费者回调函数SubscriberCallback _callback;};}
      #endif
      
在MQClient中编写Channel.hpp文件实现信道管理模块
  • 同样的,客⼾端也有信道,其功能与服务端⼏乎⼀致,或者说不管是客⼾端的channel还是服务端的channel都是为了⽤⼾提供具体服务⽽存在的,只不过服务端是为客⼾端的对应请求提供服务,⽽客⼾端的接⼝服务是为了⽤⼾具体需要服务,也可以理解是⽤⼾通过客⼾端channel的接⼝调⽤来向服务端发送对应请求,获取请求的服务

  • 在该文件中,应该实现的功能:

    • 信道信息:

      1. 信道ID
      2. 信道关联的⽹络通信连接对象
      3. protobuf协议处理对象
      4. 信道关联的消费者
      5. 请求对应的响应信息队列(这⾥队列使⽤<请求ID,响应>hash表,以便于查找指定的响应)
      6. 互斥锁&条件变量(⼤部分的请求都是阻塞操作,发送请求后需要等到响应才能继续,但是muduo库的通信是异步的,因此需要我们⾃⼰在收到响应后,通过判断是否是等待的指定响应来进⾏同步)
    • 信道操作:

      1. 提供创建信道操作
      2. 提供删除信道操作
      3. 提供声明交换机操作(强断⾔-有则OK,没有则创建)
      4. 提供删除交换机
      5. 提供创建队列操作(强断⾔-有则OK,没有则创建)
      6. 提供删除队列操作
      7. 提供交换机-队列绑定操作
      8. 提供交换机-队列解除绑定操作
      9. 提供添加订阅操作
      10. 提供取消订阅操作
      11. 提供发布消息操作
      12. 提供确认消息操作
    • 信道管理:

      1. 创建信道
      2. 查询信道
      3. 删除信道
  • 实现代码

    #ifndef __M_Channel_H__
    #define __M_Channel_H__#include "../MQCommon/Helper.hpp"
    #include "../MQCommon/Logger.hpp"
    #include "../MQCommon/ThreadPool.hpp"
    #include "../MQCommon/message.pb.h"
    #include "../MQCommon/request.pb.h"
    #include "Subscriber.hpp"
    #include "muduo/net/TcpConnection.h"
    #include "muduo/protobuf/codec.h"
    #include <condition_variable>
    #include <iostream>
    #include <mutex>
    #include <unordered_map>namespace MQ
    {using MessagePtr = std::shared_ptr<google::protobuf::Message>;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): _channel_id(UUIDHelper::uuid()),_connection_ptr(conn),_codec_ptr(codec){}// 析构函数~Channel(){basicCancel();}std::string cid(){return _channel_id;}bool openChannel(){std::string rid = UUIDHelper::uuid();openChannelRequest req;req.set_rid(rid);req.set_cid(_channel_id);_codec_ptr->send(_connection_ptr, req);basicCommonResponsePtr resp = waitResponse(rid);return resp->ok();}void closeChannel(){std::string rid = UUIDHelper::uuid();closeChannelRequest req;req.set_rid(rid);req.set_cid(_channel_id);_codec_ptr->send(_connection_ptr, 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(_channel_id);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_ptr->send(_connection_ptr, 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(_channel_id);req.set_exchange_name(name);_codec_ptr->send(_connection_ptr, 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(_channel_id);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_ptr->send(_connection_ptr, 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(_channel_id);req.set_queue_name(qname);_codec_ptr->send(_connection_ptr, 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(_channel_id);req.set_exchange_name(ename);req.set_queue_name(qname);req.set_binding_key(key);_codec_ptr->send(_connection_ptr, 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(_channel_id);req.set_exchange_name(ename);req.set_queue_name(qname);_codec_ptr->send(_connection_ptr, 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(_channel_id);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_ptr->send(_connection_ptr, req);waitResponse(rid);return;}void basicAck(const std::string &msgid){if (_subscriber_ptr.get() == nullptr){DLOG("消息确认时,找不到消费者信息!");return;}std::string rid = UUIDHelper::uuid();basicAckRequest req;req.set_rid(rid);req.set_cid(_channel_id);req.set_queue_name(_subscriber_ptr->_subscribe_queue_name);req.set_message_id(msgid);_codec_ptr->send(_connection_ptr, req);waitResponse(rid);return;}void basicCancel(){if (_subscriber_ptr.get() == nullptr){return;}std::string rid = UUIDHelper::uuid();basicCancelRequest req;req.set_rid(rid);req.set_cid(_channel_id);req.set_queue_name(_subscriber_ptr->_subscribe_queue_name);req.set_consumer_tag(_subscriber_ptr->_subscribe_queue_tag);_codec_ptr->send(_connection_ptr, req);waitResponse(rid);_subscriber_ptr.reset();return;}bool basicConsume(const std::string &consumer_tag,const std::string &queue_name,bool auto_ack,const SubscriberCallback &cb){if (_subscriber_ptr.get() != nullptr){DLOG("当前信道已订阅其他队列消息!");return false;}std::string rid = UUIDHelper::uuid();basicConsumeRequest req;req.set_rid(rid);req.set_cid(_channel_id);req.set_queue_name(queue_name);req.set_consumer_tag(consumer_tag);req.set_auto_ack(auto_ack);_codec_ptr->send(_connection_ptr, req);basicCommonResponsePtr resp = waitResponse(rid);if (resp->ok() == false){DLOG("添加订阅失败!");return false;}DLOG("添加订阅成功!订阅者:%s,订阅队列:%s", consumer_tag.c_str(), queue_name.c_str());_subscriber_ptr = std::make_shared<Subscriber>(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 (_subscriber_ptr.get() == nullptr){DLOG("消息处理时,未找到订阅者信息!");return;}if (_subscriber_ptr->_subscribe_queue_tag != resp->consumer_tag()){DLOG("收到的推送消息中的消费者标识,与当前信道消费者标识不一致!");return;}_subscriber_ptr->_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 _channel_id;muduo::net::TcpConnectionPtr _connection_ptr;ProtobufCodecPtr _codec_ptr;Subscriber::ptr _subscriber_ptr;std::mutex _mutex;std::condition_variable _cv;std::unordered_map<std::string, basicCommonResponsePtr> _basic_resp;};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;};
    }#endif
    
在MQClient中编写Worker.hpp文件实现异步⼯作线程模块
  • 客⼾端这边存在两个异步⼯作线程,

    • ⼀个是muduo库中客⼾端连接的异步循环线程EventLoopThread,

    • ⼀个是当收到消息后进⾏异步处理的⼯作线程池。

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

  • 实现代码:

    #ifndef __M_WORKER_H__
    #define __M_WORKER_H__
    #include "../MQCommon/Helper.hpp"
    #include "../MQCommon/Logger.hpp"
    #include "../MQCommon/ThreadPool.hpp"
    #include "muduo/net/EventLoopThread.h"namespace MQ
    {class AsyncWorker{public:using ptr = std::shared_ptr<AsyncWorker>;muduo::net::EventLoopThread loopthread;MQ::ThreadPool pool;};
    }#endif
    
    在MQClient中编写Connection.hpp文件实现连接管理模块
    • 在客⼾端这边,RabbitMQ弱化了客⼾端的概念,因为⽤⼾所需的服务都是通过信道来提供的,因此操作思想转换为先创建连接,通过连接创建信道,通过信道提供服务这⼀流程。

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

    • 实现代码

      #ifndef __M_CONNECTION_H__
      #define __M_CONNECTION_H__
      #include "muduo/base/CountDownLatch.h"
      #include "muduo/base/Logging.h"
      #include "muduo/base/Mutex.h"
      #include "muduo/net/EventLoop.h"
      #include "muduo/net/EventLoopThread.h"
      #include "muduo/net/TcpClient.h"
      #include "muduo/protobuf/codec.h"
      #include "muduo/protobuf/dispatcher.h"#include "Channel.hpp"
      #include "Subscriber.hpp"
      #include "Worker.hpp"namespace MQ
      {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; // 信道管理器};
      }
      #endif
      
在MQClient中编写ConsumeClient.cpp文件和PublichClient.cpp文件实现消费者客户端与发布者客户端
  • 消费者客户端的实现

    #include "Connection.hpp"void cb(MQ::Channel::ptr &channel, const std::string consumer_tag, const MQ::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. 实例化异步工作线程对象MQ::AsyncWorker::ptr awp = std::make_shared<MQ::AsyncWorker>();//2. 实例化连接对象MQ::Connection::ptr conn = std::make_shared<MQ::Connection>("127.0.0.1", 8085, awp);//3. 通过连接创建信道MQ::Channel::ptr channel = conn->openChannel();//4. 通过信道提供的服务完成所需//  1. 声明一个交换机exchange1, 交换机类型为广播模式google::protobuf::Map<std::string, std::string> tmp_map;channel->declareExchange("exchange1", MQ::ExchangeType::DIRECT, true, false, tmp_map);//  2. 声明一个队列queue1channel->declareQueue("queue1", true, false, false, tmp_map);//  3. 声明一个队列queue2channel->declareQueue("queue2", true, false, false, tmp_map);//  4. 绑定queue1-exchange1,且binding_key设置为queue1channel->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;
    }
    
  • 发布者客户端的实现

    #include "Connection.hpp"int main()
    {//1. 实例化异步工作线程对象MQ::AsyncWorker::ptr awp = std::make_shared<MQ::AsyncWorker>();//2. 实例化连接对象MQ::Connection::ptr conn = std::make_shared<MQ::Connection>("127.0.0.1", 8085, awp);//3. 通过连接创建信道MQ::Channel::ptr channel = conn->openChannel();//4. 通过信道提供的服务完成所需//  1. 声明一个交换机exchange1, 交换机类型为广播模式google::protobuf::Map<std::string, std::string> tmp_map;channel->declareExchange("exchange1", MQ::ExchangeType::DIRECT, true, false, tmp_map);//  2. 声明一个队列queue1channel->declareQueue("queue1", true, false, false, tmp_map);//  3. 声明一个队列queue2channel->declareQueue("queue2", true, false, false, tmp_map);//  4. 绑定queue1-exchange1,且binding_key设置为queue1channel->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++) {MQ::BasicProperties bp;bp.set_id(UUIDHelper::uuid());bp.set_delivery_mode(MQ::DeliveryMode::DURABLE);bp.set_routing_key("queue1");channel->basicPublish("exchange1", &bp, "Hello World-" + std::to_string(i));}MQ::BasicProperties bp;bp.set_id(UUIDHelper::uuid());bp.set_delivery_mode(MQ::DeliveryMode::DURABLE);bp.set_routing_key("queue1");channel->basicPublish("exchange1", &bp, "Hello Bite");bp.set_routing_key("news.sport");channel->basicPublish("exchange1", &bp, "Hello chileme?");//6. 关闭信道conn->closeChannel(channel);return 0;
    }
    

相关文章:

手撕基于AMQP协议的简易消息队列-7(客户端模块的编写)

在MQClient中编写客户端模块代码 在MQClient中编写makefile文件来编译客户端模块 .PHONY:all all:PublichClient ConsumeClient PublichClient : PublichClient.cpp ../MQCommon/request.pb.cc ../MQCommon/message.pb.cc ../ThirdLib/lib/include/muduo/protobuf/codec.ccg …...

Spring Security(笔记)

第一步&#xff1a; 首先使用Intellij IDEA创建一个Spring Boot项目&#xff0c;JDK选择自己安装的1.8。点击Next后&#xff0c;编辑项目信息。然后跳转到选择依赖页面。 第二步&#xff1a; 添加Spring Security、Spring Web、Thymeleaf三个依赖。完成后等待项目构建完成。…...

通义灵码编码插件支持MCP

通义灵码MCP功能集成概述 通义灵码已深度集成魔搭社区&#xff08;ModelScope&#xff09;的MCP&#xff08;Model Context Protocol&#xff09;服务&#xff0c;为开发者提供了在IDE中直接调用AI能力的便捷通道。MCP作为标准化协议&#xff0c;通过定义Resources、Prompts和…...

问题 | 当前计算机视觉迫切解决的问题

当前计算机视觉领域虽然在技术上取得了显著进展&#xff0c;但仍面临一系列关键挑战。结合最新研究与应用现状&#xff0c;以下是最迫切需要解决的几大问题&#xff1a; 1. 数据质量与多样性不足 高质量标注数据的获取&#xff1a;训练高效模型依赖大量精准标注的数据&#x…...

C++ STL入门:vecto容器

C STL 系列入门&#xff1a;vector 动态数组 一、vector 容器核心特性 vector 是 C 标准库提供的动态数组容器&#xff0c;具有以下显著优势&#xff1a; 自动扩容机制&#xff1a;当插入元素超出当前容量时&#xff0c;自动申请新内存并迁移数据随机访问效率&#xff1a;支持…...

Java 线程全面概述

Java 线程全面概述 线程是程序执行的最小单元&#xff0c;是操作系统能够调度的最小单位。Java 提供了完善的线程支持&#xff0c;下面从基础概念到高级特性进行全面解析。 一、线程基础概念 1. 线程 vs 进程 特性进程线程资源占用独立内存空间共享进程内存切换成本高&#…...

高效文件夹迁移工具,轻松实现批量文件管理

软件介绍 DirMapper是一款专注于文件夹迁移的工具&#xff0c;可以快速完成文件的批量整理与位置调整。 功能特点 这款文件夹迁移工具提供两种操作模式&#xff1a;复制模式和移动模式&#xff0c;用户可以根据需求自行选择。如果需要保留原文件&#xff0c;可以选择复…...

sherpa:介绍

更多内容&#xff1a;XiaoJ的知识星球 目录 1. sherpa 介绍 1. sherpa 介绍 sherpa是 Next-gen Kaldi 项目的部署框架。 sherpa 支持在各种平台上部署与语音相关的预训练模型&#xff0c;并提供多种语言绑定。 目前&#xff0c;sherpa 拥有以下子项目&#xff1a; k2-fsa/sh…...

Android Studio Gradle 中 只显示 Tasks 中没有 build 选项解决办法

一、问题描述 想把项目中某一个模块的代码单独打包成 aar ,之前是点击 AndroidStudio 右侧的 Gradle 选项&#xff0c;然后再点击需要打包的模块找到 build 进行打包&#xff0c;但是却发现没有 build 选项。 二、解决办法 1、设置中勾选 Configure all Gradle tasks… 选项 …...

手撕基于AMQP协议的简易消息队列-6(服务端模块的编写)

在MQServer中编写服务端模块代码 在MQServer中编写makefile文件来编译服务端模块 .PHONY: server CFLAG -I../ThirdLib/lib/include LFLAG -L../ThirdLib/lib/lib -lgtest -lprotobuf -lsqlite3 -pthread -lmuduo_net -lmuduo_base -lz server:server.cpp ../MQCommon/messag…...

面试实践AND面经热点题目总结

1、对于Rocketmq消息积压、丢失如何解决&#xff1f; 消息积压原因以及解决方案 &#x1f3af; 产生原因&#xff1a; 消费者处理能力弱&#xff0c;消费速度远低于生产速度&#xff1b; 网络不稳定&#xff0c;消费者拉取消息失败&#xff1b; 消费端异常&#xff08;如处理…...

MySQL基础关键_012_事务

目 录 一、概述 二、ACID 四大特性 三、MySQL 事务 四、事务隔离级别 1.说明 2.现象 &#xff08;1&#xff09;脏读 &#xff08;2&#xff09;不可重复读 &#xff08;3&#xff09;幻读 3.查看隔离级别 4.设置隔离级别 5.隔离级别 &#xff08;1&#xff09;初始…...

Missashe考研日记-day35

Missashe考研日记-day35 1 专业课408 学习时间&#xff1a;3h学习内容&#xff1a; 完结撒花&#xff01;&#xff01;今天把OS最后一节的内容学完了&#xff0c;操作系统也算是告一段落了&#xff0c;接下来是计网时间&#xff01;不过计网我是上学期才学过的&#xff0c;当…...

如何添加二级域名

在 华为云 上添加二级域名&#xff08;如 sub.example.com&#xff09;主要涉及 DNS解析配置 和 服务器绑定 两个步骤。以下是详细操作指南&#xff1a; 一、前提条件 已拥有 主域名&#xff08;如 example.com&#xff09;并完成 ICP备案&#xff08;若服务器在中国大陆&#…...

【数据结构】01Trie

什么是 01Trie? 01Trie是字典树的一种变种&#xff0c;其只有两种情况&#xff0c;即 0 和 1&#xff0c;实现方式其实和字典树是一样的 有什么用呢&#xff1f; 其一般用于解决异或问题&#xff0c;是一种快速的数据结构&#xff0c;某些情况下可以无脑套用 实现方式&#…...

使用 CDN 在国内加载本地 PDF 文件并处理批注:PDF.js 5.x 实战指南

PDF.js 是一个强大的开源 JavaScript 库&#xff0c;用于在 Web 浏览器中渲染 PDF 文件。它由 Mozilla 开发&#xff0c;能够将 PDF 文档绘制到 HTML5 Canvas 或 SVG 上&#xff0c;无需任何本机代码或浏览器插件。对于许多需要在网页中展示 PDF 内容的应用场景来说&#xff0c…...

SpringBoot指定项目层日志记录

1、新建一个Springboot项目&#xff0c;添加Lombok依赖&#xff08;注意&#xff1a;这里使用的Lombok下的Slf4j快速日志记录方式&#xff09; <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependenc…...

使用Mathematica内置函数绘制Sierpinski地毯

除了SierpinskiCurve之外&#xff0c;Mathematica还内置了SierpinskiMesh这个函数&#xff0c;用来绘制地毯。 SierpinskiMesh[n] gives a mesh region representing the n-step Sierpiński triangle. SierpinskiMesh[n,d] gives the n-step Sierpiński sponge in dimension…...

CMake笔记(简易教程)

CMake笔记 概述&#xff08;需要提前了解的知识&#xff09; 一个c/c程序从代码到生成二进制文件&#xff0c;需要经历的几个关键步骤&#xff1a;预编译&#xff08;预处理&#xff09;、编译、汇编、连接 【编译链接的几个步骤】 编译器&#xff1a;目前市面常见的编译器有…...

现代健康养生新范式:多维度守护身心活力

在快节奏的现代生活中&#xff0c;健康养生是维持高品质生活的关键。从环境调节到生活习惯养成&#xff0c;多个维度的协同发力&#xff0c;才能为健康注入持久动力。​ 良好的生活环境是健康的基础。室内空气流通至关重要&#xff0c;每天开窗通风 2-3 次&#xff0c;每次 30…...

推测式思维树:让大模型快速完成复杂推理

论文标题 Accelerating Large Language Model Reasoning via Speculative Search 论文地址 https://www.arxiv.org/pdf/2505.02865 作者背景 中科大&#xff0c;华为诺亚方舟实验室&#xff0c;天津大学 ICML 2025接收 动机 之前介绍过多篇投机解码&#xff08;推测式解…...

软考错题(三)

telnet协议是一种基于TCP的远程登录协议 占用辅助空间最多的是归并排序 直接插入&#xff0c;堆排&#xff0c;简单选择&#xff0c;冒泡的空间复杂度是O(1) 快排是O(logn) 归并是O(n) B树的叶子节点通过指针链接为有序表&#xff0c;不是b-树 python中切片语法[start,end,s…...

注解的定义

一、理论说明 1. 注解的定义 Java 注解是从 JDK 5.0 开始引入的一种元数据机制&#xff0c;它可以为代码添加额外的信息&#xff0c;这些信息不影响程序的运行逻辑&#xff0c;但可以在编译期、类加载期或运行期被读取和处理。注解本质上是一种特殊的接口&#xff0c;所有注解…...

企业微信自建消息推送应用

企业微信自建应用来推送消息 前言 最近有个给特定部门推送消息的需求&#xff0c;所以配置一个应用专门用来推送消息。实现过程大致为&#xff1a;服务器生成每天的报告&#xff0c;通过调用API来发送消息。以前一直都是发邮件&#xff0c;整个邮箱里全是报告文件&#xff0c…...

swagger3融入springboot

标签&#xff1a; 放controller上面 Api(description "xxx") 放方法上面 Operation(summary "xxx") 引入&#xff1a; 我用的是swagger3.X 需要在yml配置文件中加上&#xff1a; spring:mvc:pathmatch:matching-strategy: ant_path_matcher 然后生…...

CH32V208GBU6沁恒绑定配对获取静态地址

从事嵌入式单片机的工作算是符合我个人兴趣爱好的,当面对一个新的芯片我即想把芯片尽快搞懂完成项目赚钱,也想着能够把自己遇到的坑和注意事项记录下来,即方便自己后面查阅也可以分享给大家,这是一种冲动,但是这个或许并不是原厂希望的,尽管这样有可能会牺牲一些时间也有哪天原…...

[计算机科学#11]:编程语言简史,从二进制到简约表达的华丽转身,造就原因——“懒”

【核知坊】&#xff1a;释放青春想象&#xff0c;码动全新视野。 我们希望使用精简的信息传达知识的骨架&#xff0c;启发创造者开启创造之路&#xff01;&#xff01;&#xff01; 内容摘要&#xff1a; 由于早期的编程需要直接操作硬件&#xff0c;例如使…...

Kubernetes HPA 深度解析:生产环境自动扩缩容实战指南

一、HPA 核心原理剖析 1. 运作机制三步曲 (图示&#xff1a;指标采集 → 决策计算 → 执行扩缩容的完整闭环) 指标采集层&#xff1a;通过 Metrics Server/Prometheus 等组件实时收集 CPU、内存或自定义指标决策计算层&#xff1a;根据当前指标值与目标阈值的比例计算所需副本…...

Matlab 四分之一车体被动和模糊控制对比

1、内容简介 Matlab215-四分之一车体被动和模糊控制对比 可以交流、咨询、答疑 2、内容说明 略 3、仿真分析 略 4、参考论文 略...

pm2如何执行脚本批量启动多个服务

在 PM2 中批量启动多个服务&#xff0c;可以通过以下几种高效方式实现&#xff0c;具体操作如下&#xff1a; 方法1&#xff1a;使用 ecosystem.config.js 配置文件&#xff08;推荐&#xff09; 步骤1&#xff1a;生成配置文件 在项目根目录运行以下命令&#xff0c;生成模板…...

Debian系统详解

以下是关于 Debian 操作系统 的超详细深度解析&#xff0c;涵盖历史、架构、功能特性、管理细节及应用场景等方面&#xff0c;帮助你全面掌握这一经典 Linux 发行版&#xff1a; 一、Debian 概述&#xff1a;开源社区的基石 1. 历史与定位 • 诞生&#xff1a;1993 年由 Ian…...

Dify X 奇墨科技,让AI大模型从“巨头专属”变为“触手可及”

AI大模型和AI Agent蓬勃发展&#xff0c;企业比拼的已不仅是AI技术储备&#xff0c;更是AI应用落地的实战能力。奇墨科技正式成为 AI 应用开发平台Dify中国大陆区企业版合作伙伴&#xff0c;帮助企业更便捷地接触到Dify并使用其开发AI应用。 Dify 是一款简单易用的 LLM 应用开…...

CSS相对定位与绝对定位

在网页设计里&#xff0c;相对定位&#xff08;Relative Positioning&#xff09;和绝对定位&#xff08;Absolute Positioning&#xff09;是 CSS&#xff08;层叠样式表&#xff09;里控制元素位置的关键手段。下面为你详细讲解它们的概念、特点与应用场景。 相对定位 概念…...

正则表达式(Regular Expression)详解

正则表达式&#xff08;简称"regex"或"regexp"&#xff09;是一种强大的文本模式匹配工具&#xff0c;它使用特定语法来描述、匹配和操作字符串。 基本概念 正则表达式是由普通字符&#xff08;如字母a到z&#xff09;和特殊字符&#xff08;称为"元…...

OpenCV-Python (官方)中文教程(部分一)_Day22

22.3 2D直方图 在前面的部分我们介绍了如何绘制一维直方图,之所以称为一维,是因为我们只考虑了图像的一个特征&#xff1a;灰度值。但是在 2D 直方图中我们就要考虑 两个图像特征。对于彩色图像的直方图通常情况下我们需要考虑每个的颜色&#xff08;Hue&#xff09;和饱和度&…...

【软考-高级】【信息系统项目管理师】【论文基础】采购管理过程输入输出及工具技术的使用方法

采购管理概念 项目采购管理包括从项目团队外部采购或获取所需产品、服务或成果的各个过程。项目采购管理包括编制和管理协议所需的管理和控制过程&#xff0c;例如合同、订购单、协议备忘录&#xff08;MOA&#xff09;和服务水平协议&#xff08;SLA&#xff09;。 采购管理…...

基于STM32、HAL库的CP2102-GMR USB转UART收发器 驱动程序设计

一、简介: CP2102-GMR是Silicon Labs公司生产的一款USB转UART桥接芯片,主要特点包括: 集成USB 2.0全速功能控制器 内置USB收发器,无需外部电阻 工作电压:3.0V至3.6V 支持的数据格式:数据位8,停止位1,无校验 最高支持1Mbps的波特率 内置512字节接收缓冲区和512字节发送…...

信息系统项目管理工程师备考计算类真题讲解十四

一、最小生成树问题 此问题采用破圈法来解决&#xff0c; 1&#xff09;以1节点为例&#xff0c;找到路径最小 点&#xff1a;1--5&#xff1a;距离为3 2&#xff09;找1--5最短的节点&#xff0c;选择4&#xff1a;1--5--4&#xff1a;距离为&#xff1a;5 3&#xff09;找…...

二叉树的基本操作

二叉树的基本操作(C 语言版) 1 二叉树的定义 二叉树的图长这样: 二叉树是每个结点最多有两个子树的树结构,常被用于实现二叉查找树和二叉堆。二叉树是链式存储结构,用的是二叉链,本质上是链表。二叉树通常以结构体的形式定义,如下,结构体内容包括三部分:本节点所存储…...

网络基础入门第6-7集(抓包技术)

前言&#xff1a; 来自小迪安全v2023 内容&#xff1a; 第六集&#xff1a; 大致内容&#xff1a;burpsuit、茶杯、fiddler的抓包流程 1、安装抓包软件的相关证书 2、各大抓包软件的测试 注意用burp抓模拟器的数据包&#xff0c;需要将ip地址设置为本地的ip地址&#xff…...

自定义Widget开发:自定义布局实现

自定义Widget开发&#xff1a;自定义布局实现 一、Flutter布局系统基础 1. 布局约束&#xff08;Constraints&#xff09; 在Flutter中&#xff0c;布局系统基于约束&#xff08;Constraints&#xff09;的概念。每个widget都会接收来自其父widget的约束&#xff0c;并根据这…...

MyBatis(进阶)(xml标签)

本节⽬标 1. 学习MyBatis的动态SQL查询 2. 掌握MyBatis在项⽬中的应⽤, 可以使⽤Spring MVC完成⼀些基础的功能 1. 动态SQL&#xff08;XML&#xff09; 动态 SQL 是Mybatis的强⼤特性之⼀&#xff0c;能够完成不同条件下不同的 sql 拼接 可以参考官⽅⽂档&#xff1a; M…...

英皇娱乐X乐华娱乐携手造星!“英皇乐华青少年艺人培训班”正式启动!

2025年5月8日&#xff0c;英皇娱乐集团与乐华娱乐集团联合宣布&#xff0c;双方将在北京市燕京实验中学合作开设“英皇乐华青少年艺人培训班”&#xff0c;为8至18岁的青少年提供专业的演艺及才艺学习平台。此次合作旨在集合两大娱乐公司在演艺行业的资源与优势&#xff0c;共同…...

Linux云计算训练营笔记day04(Rocky Linux中的命令)

mv 移动(剪切) 源数据会消失 格式: mv 源文件 目标路径 touch /opt/a.txt 创建文件 mv /opt/a.txt /root 移动文件&#xff0c;没有改名 mkdir gongli 创建目录 mv gongli /opt/ 移动目录&#xff0c;没有改名 mv /opt/gongli tedu 移动目录&#xff0c;改名了 …...

枚举 · 例13-【模板】双指针

登录—专业IT笔试面试备考平台_牛客网 代码区&#xff1a; #include<algorithm> #include<iostream> #include<vector> #include<unordered_set> using namespace std;struct INTER{int left,right; }; bool compare(const INTER&a,const INTER&a…...

Linux网络编程day7 线程池and UDP

线程池 typedef struct{void*(*function)(void*); //函数指针&#xff0c;回调函数void*arg; //上面函数的参数 }threadpool_task_t; //各子线程任务的结构体/*描述线程池相关信息*/struct threadpool_t{pthread_mutex_t lock; …...

WHAT - ahooks vs swr 请求

文章目录 ahooks特点常用 Hooks 示例1. useRequest — 封装网络请求逻辑&#xff08;比 SWR / React Query 更轻量&#xff09;2. useDebounce — 防抖值3. useLocalStorageState — 本地存储的状态4. useBoolean — 快速管理布尔状态5. useEventListener — 添加事件监听 ahoo…...

算法训练营第十一天|150. 逆波兰表达式求值、239. 滑动窗口最大值、347.前 K 个高频元素

150. 逆波兰表达式求值 题目 思路与解法 第一思路&#xff1a; 比较简单 class Solution:def evalRPN(self, tokens: List[str]) -> int:stack []for item in tokens:if item ! and item ! - and item ! * and item ! / :stack.append(item)else:b int(stack.pop())a …...

可视化图解算法35:在二叉树中找到两个节点的最近公共祖先(二叉树的最近公共祖先)

1. 题目 描述 给定一棵二叉树(保证非空)以及这棵树上的两个节点对应的val值 o1 和 o2&#xff0c;请找到 o1 和 o2 的最近公共祖先节点。 数据范围&#xff1a;树上节点数满足 1≤n≤105 , 节点值val满足区间 [0,n) 要求&#xff1a;时间复杂度 O(n) 注&#xff1a;本题保…...

如果说开启的TIM3定时器有ccr1,ccr2,ccr3,我想要关闭ccr2的PWM输出,怎么通过代码实现

目录 作用概述&#xff1a; 具体原理&#xff1a; 代码的操作细节&#xff1a; 实际效果&#xff1a; 示意全文&#xff1a; 小结&#xff1a; TIM3->CCER & ~TIM_CCER_CC2E; 作用概述&#xff1a; 作用是禁用 TIM3 的通道 2&#xff08;CCR2&#xff09;的捕获…...