仿RabbitMQ实现消息队列客户端

文章目录

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

客⼾端模块实现

在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. 声明一个队列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++) {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. 声明一个队列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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

认知战认知作战:激发认知战战术分享热情的秘诀

认知战认知作战&#xff1a;激发认知战战术分享热情的秘诀 认知战认知作战&#xff1a;激发认知战战术分享热情的秘诀 关键词&#xff1a;认知战, 认知作战, 创造独特体验, 融入社交元素, 情感共鸣策略, 分享激励机制, 战略形象塑造, 个性化内容推荐,认知作战,新质生产力,人类…

E. Tree Pruning Codeforces Round 975 (Div. 2)

原题 E. Tree Pruning 解析 本题题意很简单, 思路也很好想到, 假设我们保留第 x 层的树叶, 那么对于深度大于 x 的所有节点都要被剪掉, 而深度小于 x 的节点, 如果没有子节点深度大于等于 x, 那么也要被删掉 在做这道题的时候, 有关于如何找到一个节点它的子节点能通到哪里,…

用Arduino单片机制作一个简单的音乐播放器

Arduino单片机上有多个数字IO针脚&#xff0c;可以输出数字信号&#xff0c;用于驱动发声器件&#xff0c;从而让它发出想要的声音。蜂鸣器是一种常见的发声器件&#xff0c;通电后可以发出声音。因此&#xff0c;单片机可以通过数字输出控制蜂鸣器发出指定的声音。另外&#x…

马丁代尔药物大典数据库

马丁代尔药物大典是一本由Pharmaceutical Press出版的参考书&#xff0c;拥有全球使用的近 6000 种药物和药品&#xff0c;包括超过 125,000 种专有制剂的详细信息。其中还包括近 700 篇疾病治疗评论。 它于 1883 年首次出版&#xff0c;马丁代尔包含全球临床用药信息&#xff…

【qt】QQ仿真项目2

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 一览全局: QQ仿真项目 一.主窗口的创建二.主窗口的ui设计三.初始化状态,等级,app…

<Rust>iced库(0.13.1)学习之部件(三十一):picklist部件的使用及可变style设置

前言 本专栏是学习Rust的GUI库iced的合集,将介绍iced涉及的各个小部件分别介绍,最后会汇总为一个总的程序。 iced是RustGUI中比较强大的一个,目前处于发展中(即版本可能会改变),本专栏基于版本0.12.1. 注:新版本已更新为0.13 概述 这是本专栏的第三十一篇,主要说明下…

俗人,精气神,歌曲《错的人》

精气神&#xff0c;在人体中&#xff0c;精指构成人体生命活动的各层次的有形元素&#xff0c;常呈固体或液体状态。 哲学前提&#xff1a;世界上的一切&#xff0c;从微观上讲&#xff0c;都是由精微物质构成的&#xff0c;比如基本粒子。 关于有形与无形、与主观关注点相关…

DHCP安装

步骤 1&#xff1a;安装DHCP服务器 在系统上安装DHCP服务。以下是安装命令&#xff1a; # 安装DHCP软件包 yum install dhcp步骤 2&#xff1a;配置DHCP服务器 安装完成后&#xff0c;需要配置DHCP服务器来绑定MAC地址和IP地址。 # 备份原始的DHCP配置文件 cp /etc/dhcp/dh…

迁移学习案例-python代码

大白话 迁移学习就是用不太相同但又有一些联系的A和B数据&#xff0c;训练同一个网络。比如&#xff0c;先用A数据训练一下网络&#xff0c;然后再用B数据训练一下网络&#xff0c;那么就说最后的模型是从A迁移到B的。 迁移学习的具体形式是多种多样的&#xff0c;比如先用A训练…

HCIA综合实验

实验步骤 1.划分网段 内网部分---三个大块 2.先配交换机 左边&#xff1a;3个vlan &#xff0c;3个access&#xff0c;1个trunk 右边&#xff1a;2个vlan &#xff0c;2个access&#xff0c;1个trunk 3.再配路由 3.1 r5先配接口ipg/0/0/0 口配子接口 g0/0/0.1-0.3 g0/0/1 …

【YOLOv8实时产品缺陷检测】

YOLOv8应用于产品缺陷检测实例 项目概况项目实现YOLOv8安装及模型训练关键代码展示动态效果展示 项目概况 本项目是应用YOLOv8框架实现训练自定义模型实现单一零件的缺陷检测&#xff0c;软件界面由PyQt5实现。 功能已正式使用&#xff0c;识别效果达到预期。 项目实现 项目…

手机误删照片?试试这5款免费数据恢复神器!

大家好&#xff01;今天咱们来聊聊一个大家都关心的话题——免费数据恢复工具。不论是误删照片、视频&#xff0c;还是丢失重要文件&#xff0c;数据恢复都是个让人头疼的问题。但好消息是&#xff0c;现在有众多免费的数据恢复工具能帮助我们找回失去的数据。今天我就来为大家…

力扣16~20题

题16&#xff08;中等&#xff09;&#xff1a; 思路&#xff1a; 双指针法&#xff0c;和15题差不多&#xff0c;就是要排除了&#xff0c;如果total<target则排除了更小的&#xff08;left右移&#xff09;&#xff0c;如果total>target则排除了更大的&#xff08;rig…

pycharm 远程ssh时,mujuco提示mujoco.FatalError: gladLoadGL error

在ubuntu系统运行时完全没问题&#xff0c;但是使用pycharm远程ssh登录时就会提示这个。 解决方法&#xff1a; 1. 可以修改环境变量 2. export LD_PRELOAD/usr/lib/x86_64-linux-gnu/libstdc.so.6 参考【Mujuco】WSL2安装Mujoco用于python,遇到FatalError,以及图形驱动架构…

【Git原理与使用】远程操作标签管理

远程操作&&标签管理 1.理解分布式版本控制系统2.新建远程仓库3.克隆远程仓库4.向远程仓库推送5.拉取远程仓库6.配置 Git7.配置命令别名8.标签管理8.1创建标签8.2操作标签 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496;…

RTOS系统移植

一、完成系统移植 系统移植上官网寻找合适的系统包&#xff0c;下载后将文件移植入工程文件 二、创建任务句柄、内核对象句柄&#xff08;信号量&#xff0c;消息队列&#xff0c;事件标志组&#xff0c;软件定时器&#xff09;、声明全局变量、声明函数 三、创建主函数&#…

Vue2电商项目(七)、订单与支付

文章目录 一、交易业务Trade1. 获取用户地址2. 获取订单信息 二、提交订单三、支付1. 获取支付信息2. 支付页面--ElementUI(1) 引入Element UI(2) 弹框支付的业务逻辑(这个逻辑其实没那么全)(3) 支付逻辑知识点小总结 四、个人中心1. 搭建二级路由2. 展示动态数据(1). 接口(2).…

【计算机网络 - 基础问题】每日 3 题(二十九)

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?typeblog &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞…

【Docker】03-自制镜像

1. 自制镜像 2. Dockerfile # 基础镜像 FROM openjdk:11.0-jre-buster # 设定时区 ENV TZAsia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone # 拷贝jar包 COPY docker-demo.jar /app.jar # 入口 ENTRYPOINT ["ja…