项目第七弹:消费者管理模块

项目第七弹:消费者管理模块

  • 一、为何要有这个模块?
  • 二、消费者是否需要持久化?
  • 三、怎么设计?
    • 1.如何抽象描述?
      • 1.回想一下基于生产消费模型的线程池
      • 2.如何组织
      • 3.消息处理与确认问题的解决
      • 4.自动确认标志
      • 5.消费者代码
    • 2.队列消费者管理模块
      • 1.为何要有队列消费者管理模块?
      • 2.队列消费者管理模块的设计
    • 3.总体消费者管理模块
  • 四、代码
    • 1.队列消费者管理模块
    • 2.总体消费者管理模块
      • 1.pair<string,string>的哈希函数书写
      • 2.代码
    • 3.测试代码

一、为何要有这个模块?

在我们项目的前几弹当中,我们已经实现了:
交换机数据管理模块,队列数据管理模块,绑定信息管理模块,消息管理模块,虚拟机管理模块和路由匹配模块
在这里插入图片描述
在这里插入图片描述

二、消费者是否需要持久化?

在第六弹介绍虚拟机设计的时候,我们提到过,我们的项目模块划分:
在这里插入图片描述
消费者管理模块是描述模块,是对用户的描述,服务重启时,所有的TCP连接都要断开(我们暂且不考虑TCP的允许断线重连的机制),也就是说一旦服务重启,所有的消费者都会没有了

因此即使把消费者持久化,并成功恢复了,对应的用户也早就不存在了,因此持久化没有意义

三、怎么设计?

怎么设计?跟交换机那里一样,先抽象描述
在放到具体数据结构当中进行组织,当然,都要配上对应的访问操作接口

1.如何抽象描述?

1.回想一下基于生产消费模型的线程池

class threadpool
{
public:using functor = std::function<void()>;threadpool(int thread_num = 5){thread_vec.resize(thread_num);//消费者需要有一个线程函数[routine : 例行程序]for (int i = 0; i < thread_num; i++)thread_vec[i] = thread(std::bind(&threadpool::take, this));}// 生产者放数据void put(functor func);private:// 消费者拿数据void take();queue<functor> func_pool;  // 任务队列vector<thread> thread_vec; // 线程vec
};

在这个线程池当中,我们可以将消费者(工作线程)抽象为:

  1. 线程标识(在这里是 : 下标)
  2. 例行程序(在这里是 : take)

但是他并不符合我们的预期,因为:

  1. 我们的消息队列是基于消息订阅的,消费者需要选择他想订阅的队列
  2. 我们的消费者是可以用自己的想法来处理消息的
    【这才是消息中间件的灵魂】
  3. 我们的消息是基于发布确认机制的,消费者成功消费消息之后要对消息进行确认【ack】,此时我们才会删除那个消息

2.如何组织

那么该如何组织消费者呢?
首先,消费者是依附于队列而存在的,通过订阅与取消订阅跟对应的队列建立联系,因此势必需要队列名这一字段作为成员

其次,因为不同虚拟机当中可以存在同名队列,而且我们的消费者管理模块并不属于虚拟机管理模块当中,因此势必需要虚拟机名称这一字段作为成员来区分同名队列

因为虚拟机管理模块是资源模块,而消费者管理模块是描述模块,两者不适合整合为统一的一个模块,持久化要求也不同

所以两者通过虚拟机名称这一字段进行关联。从而实现高内聚,低耦合
在这里插入图片描述
这也是借助MySQL数据库表的连接相关设计知识来设计出的模块组织方案

3.消息处理与确认问题的解决

我们的消费者是可以用自己的想法来处理消息的

因此我们可以让消费者自己提供消息处理函数,所以消费者类当中要有一个回调函数

message BasicProperities
{string msg_id = 1;DeliveryMode mode = 2;string routing_key = 3;
}
message Message 
{message ValidLoad{string body = 1;BasicProperities properities = 2;string valid = 3;}ValidLoad valid = 1;uint64 offset = 2;uint64 len = 3;  
}

那这个回调函数的签名是什么样的?
我们就要从消费者需要知道消息的什么信息开始:

  1. 怎么处理这个消息
    需要知道消息的内容是什么,是谁处理的:
const std::string &body
const std::string &consumer_tag是consumer_tag处理了body这个消息
  1. 怎么确认这个消息
bool basicAck(const std::string &qname, const std::string &msg_id);

msg_id在BasicProperities当中,因此我们需要:

const BasicProperites* bp

那么这个队列名呢?
在回调函数当中其实是不需要的,至于为何,我们以后会介绍的

因此:
回调函数的签名:

using ConsumerCallback = std::function<void(const std::string &tag, const ns_proto::BasicProperites *bp, const std::string &body)>;

4.自动确认标志

有些消息不是非常重要,且直到我们消费者能够进行确认时,中间可能相隔时间较长【毕竟肯定需要走一个网络IO】

所以RabbitMQ提出一个自动确认标志,来让发布确认机制更加灵活
bool auto_ack;

5.消费者代码

/*
因为我们的消费者是一种描述模块,而不是资源模块,其存在是为了更好的描述和管理消费者
*/
using ConsumerCallback = std::function<void(const std::string &, const ns_proto::BasicProperities *, const std::string &)>;struct Consumer
{using ptr = std::shared_ptr<Consumer>;Consumer() = default;Consumer(const std::string &tag, const ConsumerCallback &callback, const std::string &vhost_name, const std::string &qname, bool auto_ack): _consumer_tag(tag), _callback(callback), _vhost_name(vhost_name), _qname(qname), _auto_ack(auto_ack) {}std::string _consumer_tag;  // 消费者tag(唯一标识)ConsumerCallback _callback; // 消费者回调函数std::string _vhost_name;    // 消费者队列所在虚拟机名称std::string _qname;         // 消费者订阅的队列bool _auto_ack;             // 自动确认标志
};

2.队列消费者管理模块

1.为何要有队列消费者管理模块?

因为我们的消费者描述是依附于队列而存在的,只有订阅了某个队列的客户端才是消费者
在这里插入图片描述
类似于我们之前说的负载均衡,只不过消费者这里的负载均衡是这样设计并实现的:
在这里插入图片描述
在这里插入图片描述

2.队列消费者管理模块的设计

我们队列消费者管理模块要实现负载均衡:

  1. RR轮转:将请求按顺序分配给各个消费者【实现简单,适用于消费者消费能力大致相同的场景】
  2. 最小连接数:将请求分配给负载最低的消费者【完全式的负载均衡】

还有很多其他算法,大家感兴趣的话可以自行去了解一下,这里就不赘述了

为了降低消费者管理模块和消费者之间的耦合度,我们采用RR轮转,而不是最少连接数

这样的话,我们的消费者管理模块只负责组织维护对应队列的消费者,负载均衡式选取适合推送消息的消费者
之后消费者如何处理,处理多长时间,当前的状态是什么,我们无需考虑

【因为要支持轮询序号的随机访问,所以用vector
尽管vector在中间位置/起始位置删除的代价较大,但是消费者的获取需求远高于增删
所以在权衡下,选择了vector以牺牲新增和删除效率来提升查询效率】

成员:

  1. 虚拟机名称
  2. 队列名
  3. vector<消费者句柄> consumer_vec
  4. size_t seq;//轮询序号
  5. 互斥锁【因为会存在多线程同时访问consumer_vec和int seq】

其实int seq可以通过搞成atomic<int>来确保线程安全,因为只需要++即可

size_t index=seq.fetch_add(1);
index%=consumer_vec.size();
反正index溢出不报错,直接回归到0也没什么大问题

但是因为都已经需要加锁保护consumer_vec了,所以不用原子类,保护这个临界资源也只是顺手的事
用了原子类,反而多此一举,纯属浪费资源

接口:

  1. 新增消费者
  2. 删除消费者
  3. 获取消费者【RR轮转,负载均衡式获取】
  4. 判断指定消费者是否存在
  5. 销毁该队列所有消费者
  6. 判断是否有消费者

3.总体消费者管理模块

其实就是增,删,查

成员:

  1. unordered_map<pair<虚拟机名称,队列名>,队列消费者管理模块句柄>
  2. 互斥锁

接口:
3. 初始化队列消费者管理模块
4. 销毁队列消费者管理模块
5. 新增消费者
6. 删除消费者
7. 获取消费者(RR轮转式负载均衡)
8. 判断指定消费者是否存在
9. 销毁所有消费者
10. 判断某一队列是否有消费者

四、代码

其实消费者管理模块就是设计起来略显复杂,但是代码简洁

1.队列消费者管理模块

总体上没啥难的,就是注意一下:
只有想要订阅当前队列消息的消费者才会调用该队列消费者管理模块的createConsumer函数
因此该函数无需传入vhost_name、qname作为参数

class QueueConsumerManager
{
public:using ptr = std::shared_ptr<QueueConsumerManager>;QueueConsumerManager(const std::string &vhost_name, const std::string &qname): _vhost_name(vhost_name), _qname(qname), _seq(0) {}// 1. 新增消费者[只有想要订阅当前队列消息的消费者才会调用该函数]Consumer::ptr createConsumer(const std::string &consumer_tag, const ConsumerCallback &callback, bool auto_ack){// 1. 加锁,并查找是否有该消费者std::unique_lock<std::mutex> ulock(_mutex);for (auto iter = _consumer_vec.begin(); iter != _consumer_vec.end(); ++iter){if ((*iter)->_consumer_tag == consumer_tag)return *iter;}// 2. 插入该消费者Consumer::ptr cp = std::make_shared<Consumer>(consumer_tag, callback, _vhost_name, _qname, auto_ack);_consumer_vec.push_back(cp);return cp;}void removeConsumer(const std::string &consumer_tag){// 加锁并删除该消费者std::unique_lock<std::mutex> ulock(_mutex);for (auto iter = _consumer_vec.begin(); iter != _consumer_vec.end(); ++iter){if ((*iter)->_consumer_tag == consumer_tag){_consumer_vec.erase(iter);return;}}}Consumer::ptr selectConsumer(){// 0. 加锁std::unique_lock<std::mutex> ulock(_mutex);if (_consumer_vec.empty()){default_warning("负载均衡式获取消费者出现问题,因为该队列的消费者为空");return Consumer::ptr();}int index = _seq++;_seq %= _consumer_vec.size();return _consumer_vec[index];}bool exist(const std::string &consumer_tag){std::unique_lock<std::mutex> ulock(_mutex);for (auto iter = _consumer_vec.begin(); iter != _consumer_vec.end(); ++iter){if ((*iter)->_consumer_tag == consumer_tag)return true;}return false;}bool empty(){std::unique_lock<std::mutex> ulock(_mutex);return _consumer_vec.empty();}void clear(){std::unique_lock<std::mutex> ulock(_mutex);_consumer_vec.clear();_seq = 0;}private:std::string _vhost_name;std::string _qname;std::mutex _mutex;std::vector<Consumer::ptr> _consumer_vec;size_t _seq;
};

2.总体消费者管理模块

1.pair<string,string>的哈希函数书写

我们采用BKDR字符串哈希函数,从而给pair<string,string>写出这样的哈希函数

struct HashFunc
{size_t hash(const std::string &str) const{size_t hash_val = 0;for (auto &e : str){hash_val *= 31; // BKDR哈希函数hash_val += e;}return hash_val;}size_t operator()(const std::pair<std::string, std::string> &elem) const{size_t hash1 = hash(elem.first), hash2 = hash(elem.second);// 使用两个字符串的哈希值,通过位运算和加法来生成最终的哈希值// 注意:这里使用了不同的质数来乘加,以减少哈希碰撞的可能性// 使用位运算和加法结合的方式// 使用了31和131作为乘数,它们都是常见的质数,常用于哈希函数中return (hash1 * 31 + hash2) * 131;}
};

BKDR哈希函数的介绍:
在这里插入图片描述

2.代码

就是增、删、复用

为何消息管理模块那里的key无需pair呢?无需存虚拟机名称呢?
因为消息管理模块在虚拟机模块当中,被虚拟机模块所整合了

class ConsumerManager
{
public:using ptr = std::shared_ptr<ConsumerManager>;void initQueueConsumerManager(const std::string &vhost_name, const std::string &qname){// 加锁,查找std::unique_lock<std::mutex> ulock(_mutex);std::pair<std::string, std::string> vqpair = {vhost_name, qname};if (_consumer_map.count(vqpair))return;_consumer_map.insert(std::make_pair(vqpair, std::make_shared<QueueConsumerManager>(vhost_name, qname)));}void destroyQueueConsumerManager(const std::string &vhost_name, const std::string &qname){std::unique_lock<std::mutex> ulock(_mutex);_consumer_map.erase({vhost_name, qname});}Consumer::ptr createConsumer(const std::string &vhost_name, const std::string &qname, const std::string &tag, const ConsumerCallback &callback, bool auto_ack){std::ostringstream oss;oss << "创建消费者失败,因为未能找到该队列消费者管理模块,qname = " << qname << "\n";QueueConsumerManager::ptr qcmp = getQueueConsumerManager(vhost_name, qname, oss);if (qcmp.get() != nullptr){return qcmp->createConsumer(tag, callback, auto_ack);}return Consumer::ptr();}bool removeConsumer(const std::string &vhost_name, const std::string &qname, const std::string &tag){std::ostringstream oss;oss << "删除消费者失败,因为未能找到该队列消费者管理模块,qname = " << qname << "\n";QueueConsumerManager::ptr qcmp = getQueueConsumerManager(vhost_name, qname, oss);if (qcmp.get() != nullptr){qcmp->removeConsumer(tag);return true;}return false;}Consumer::ptr selectConsumer(const std::string &vhost_name, const std::string &qname){std::ostringstream oss;oss << "负载均衡式获取消费者失败,因为未能找到该队列消费者管理模块,qname = " << qname << "\n";QueueConsumerManager::ptr qcmp = getQueueConsumerManager(vhost_name, qname, oss);if (qcmp.get() != nullptr){return qcmp->selectConsumer();}return Consumer::ptr();}bool exists(const std::string &vhost_name, const std::string &qname, const std::string &tag){std::ostringstream oss;oss << "判断是否存在消费者失败,因为未能找到该队列消费者管理模块,qname = " << qname << "\n";QueueConsumerManager::ptr qcmp = getQueueConsumerManager(vhost_name, qname, oss);if (qcmp.get() != nullptr){return qcmp->exist(tag);}return false;}bool empty(const std::string &vhost_name, const std::string &qname){std::ostringstream oss;oss << "判断队列消费者是否为空失败,因为未能找到该队列消费者管理模块,qname = " << qname << "\n";QueueConsumerManager::ptr qcmp = getQueueConsumerManager(vhost_name, qname, oss);if (qcmp.get() != nullptr){return qcmp->empty();}return false;}void clear(){std::unique_lock<std::mutex> ulock(_mutex);_consumer_map.clear();}QueueConsumerManager::ptr getQueueConsumerManager(const std::string &vhost_name, const std::string &qname, const std::ostringstream &oss){std::unique_lock<std::mutex> ulock(_mutex);auto iter = _consumer_map.find({vhost_name, qname});if (iter == _consumer_map.end()){default_error("%s",oss.str().c_str());return QueueConsumerManager::ptr();}return iter->second;}private:std::mutex _mutex;std::unordered_map<std::pair<std::string, std::string>, QueueConsumerManager::ptr, HashFunc> _consumer_map;
};

3.测试代码

#include "../mqserver/consumer.hpp"
#include <gtest/gtest.h>using namespace ns_mq;ConsumerManager::ptr cmp;class ConsumerTest : public testing::Environment
{
public:virtual void SetUp(){cmp = std::make_shared<ConsumerManager>();}virtual void TearDown(){cmp->clear();}
};TEST(consumer_test, init_test)
{cmp->initQueueConsumerManager("vhost1","queue1");cmp->initQueueConsumerManager("vhost1","queue2");cmp->initQueueConsumerManager("vhost1","queue3");std::ostringstream oss;ASSERT_NE(cmp->getQueueConsumerManager("vhost1","queue1", oss).get(), nullptr);ASSERT_NE(cmp->getQueueConsumerManager("vhost1","queue2", oss).get(), nullptr);ASSERT_NE(cmp->getQueueConsumerManager("vhost1","queue3", oss).get(), nullptr);
}TEST(consumer_test, insert_test)
{cmp->createConsumer("vhost1","queue1", "consumer1", nullptr, false);cmp->createConsumer("vhost1","queue1", "consumer2", nullptr, false);cmp->createConsumer("vhost1","queue1", "consumer3", nullptr, false);cmp->createConsumer("vhost1","queue1", "consumer4", nullptr, false);ASSERT_EQ(cmp->exists("vhost1","queue1", "consumer1"), true);ASSERT_EQ(cmp->exists("vhost1","queue1", "consumer2"), true);ASSERT_EQ(cmp->exists("vhost1","queue1", "consumer3"), true);ASSERT_EQ(cmp->exists("vhost1","queue1", "consumer4"), true);
}TEST(consumer_test, select_test)
{Consumer::ptr cp = cmp->selectConsumer("vhost1","queue1");ASSERT_EQ(cp->_consumer_tag, std::string("consumer1"));cp = cmp->selectConsumer("vhost1","queue1");ASSERT_EQ(cp->_consumer_tag, std::string("consumer2"));cp = cmp->selectConsumer("vhost1","queue1");ASSERT_EQ(cp->_consumer_tag, std::string("consumer3"));cp = cmp->selectConsumer("vhost1","queue1");ASSERT_EQ(cp->_consumer_tag, std::string("consumer4"));
}TEST(consumer_test, erase_test)
{cmp->removeConsumer("vhost1","queue1", "consumer1");cmp->removeConsumer("vhost1","queue1", "consumer2");ASSERT_EQ(cmp->exists("vhost1","queue1", "consumer1"), false);ASSERT_EQ(cmp->exists("vhost1","queue1", "consumer2"), false);ASSERT_EQ(cmp->exists("vhost1","queue1", "consumer3"), true);ASSERT_EQ(cmp->exists("vhost1","queue1", "consumer4"), true);
}TEST(consumer_test, destroy_test)
{cmp->destroyQueueConsumerManager("vhost1","queue1");cmp->destroyQueueConsumerManager("vhost1","queue2");cmp->destroyQueueConsumerManager("vhost1","queue3");std::ostringstream oss;ASSERT_EQ(cmp->getQueueConsumerManager("vhost1","queue1", oss).get(), nullptr);ASSERT_EQ(cmp->getQueueConsumerManager("vhost1","queue2", oss).get(), nullptr);ASSERT_EQ(cmp->getQueueConsumerManager("vhost1","queue3", oss).get(), nullptr);
}int main(int argc, char *argv[])
{testing::AddGlobalTestEnvironment(new ConsumerTest);testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}

以上就是项目第七弹:消费者管理模块的全部内容

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

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

相关文章

【计算机组成原理】主存储器深度解析

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由 JohnKi 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f4e2;未来很长&#…

DETR论文翻译与理解

DETR&#xff08;Detection with transformer&#xff09; DETR&#xff1a;End to End Object Detection with Transformer 论文链接&#xff1a;2005.12872 (arxiv.org) 参考视频&#xff1a;https://www.bilibili.com/video/BV1GB4y1X72R/?spm_id_from333.788&vd_…

JBoss反序列化漏洞CVE-2017-12149

1.环境搭建 cd vulhub-master/jboss/CVE-2017-12149 docker-compose up -d 2.访问漏洞地址 3.漏洞验证 http://47.121.211.205:8080/invoker/readonly 返回500说明漏洞存在 4.使用漏洞进行利用 直接执行命令

人脸识别换装技术实现记录-1

最近,研究了下人脸识别换装,确定了技术方案和技术路线,并最终实现了想要达成的效果,现将制作过程中遇到的问题以及实现的过程记录下来,以便回顾总结的同时,也和其他想实现人脸识别换装的同学分享下经验,避免踩坑。 本项目主要是在Android系统上实现人脸换装的效…

简单图解一下线性注意力机制

知乎&#xff1a;刀刀宁链接&#xff1a;https://zhuanlan.zhihu.com/p/718156896 线性注意力机制的文章有很多了&#xff0c;在本篇笔记中&#xff0c;我们简单地对各种方法进行一下图解比较&#xff0c;串一下当前的线性注意力机制&#xff0c;涉及的公式极少&#xff0c;主要…

【Python从入门到进阶】65、Pandas如何批量拆分与合并Excel文件

接上篇《64、Pandas如何实现数据的Concat合并》 上一篇我们学习了Pandas如何实现数据的Concat合并&#xff0c;本篇我们来继续学习Pandas如何批量拆分与合并Excel文件。 一、引言 在当今数据驱动的时代&#xff0c;Excel文件作为数据处理和分析的基石&#xff0c;扮演着不可或…

【YOLO目标检测手势识别数据集】共55952张、已标注txt格式、有训练好的yolov5的模型

目录 说明图片示例 说明 数据集格式&#xff1a;YOLO格式 图片数量&#xff1a;55952 标注数量(txt文件个数)&#xff1a;55952 标注类别数&#xff1a;7 标注类别名称&#xff1a; one two three four five good ok 数据集下载&#xff1a;手势识别数据集 图片示例 数…

猫头虎 分享:Python库 Bottle 的简介、安装、用法详解入门教程

&#x1f42f; 猫头虎 分享&#xff1a;Python库 Bottle 的简介、安装、用法详解入门教程 大家好&#xff0c;今天猫头虎给大家带来一篇关于Python库 Bottle 的详细入门教程。这是我在开发中经常使用的一款轻量级Web框架&#xff0c;特别适合快速搭建小型应用程序或者API服务。…

自定义类是否能正常运行、类加载器、JIT

一、自定义类是否都能正常运行 1、自定义与系统类同名的类不能正常运行 package java.lang;public class String {public void print(){System.out.println("中秋节快乐");}public static void main(String[] args) {new String().print();} } 解析&#xff1a;在编…

Arthas heapdump(dump java heap, 类似 jmap 命令的 heap dump 功能)

文章目录 二、命令列表2.1 jvm相关命令### 2.1.8 heapdump&#xff08;dump java heap, 类似 jmap 命令的 heap dump 功能&#xff09;举例1&#xff1a;假设你想生成一个只包含活动对象的堆转储文件&#xff0c;并将其保存为 /tmp/heapdump.hprof举例2&#xff1a;如果你想要进…

加密pdf如何解除加密?pdf解除密码只需掌握这7个方法!(图文详解)

pdf文件通常会设置密码保护&#xff0c;以维护其机密性和隐私。这意味着除了被授权查看文件的人之外&#xff0c;其他任何人都无法访问这些内容。然而&#xff0c;有时候您可能希望与他人分享这些 pdf文档&#xff0c;让他们能够方便地查看&#xff0c;而不必麻烦地输入密码。因…

专业的屏幕录像和视频编辑的软件Camtasia 2024安装激活图文教程

‌Camtasia 2024是一款专业的屏幕录像和视频编辑的软件套装。它由TechSmith公司开发‌&#xff0c;提供了强大的屏幕录像、视频剪辑和编辑、视频菜单制作、视频剧场、视频播放等功能。 Camtasia Studio 2024是该软件套装的核心部分&#xff0c;支持在PC和Mac平台上运行&#xf…

【JavaEE初阶】文件IO(上)

欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 目录 路径 绝对路径 相对路径 文件类型 文件的操作 File类 文件系统操作 创建文件,获取路径 删除文件 列出所有路径 路径修改 创建目录 mkdir和mkdirs 服务器领域,机械…

【永磁同步电机(PMSM)】 5. PMSM 的仿真模型

【永磁同步电机&#xff08;PMSM&#xff09;】 5. PMSM 的仿真模型 1. 基于 Simulink 的仿真模型1.1 PMSM 的数学模型1.2 Simulink 仿真模型1.3 模块封装&#xff08;mask&#xff09;1.4 三相PMSM矢量控制仿真模型 2. Simscape 的 PMSM 模块2.1 PMSM 模块的配置2.2 PMSM 模块…

Cpp类和对象(中续)(5)

文章目录 前言一、赋值运算符重载运算符重载赋值运算符重载赋值运算符不可重载为全局函数前置和后置的重载 二、const修饰成员函数三、取地址及const取地址操作符重载四、日期类的实现构造函数日期 天数日期 天数日期 - 天数日期 - 天数日期类的大小比较日期类 > 日期类日…

device靶机详解

靶机下载地址 https://www.vulnhub.com/entry/unknowndevice64-1,293/ 靶机配置 主机发现 arp-scan -l 端口扫描 nmap -sV -A -T4 192.168.229.159 nmap -sS -Pn -A -p- -n 192.168.229.159 这段代码使用nmap工具对目标主机进行了端口扫描和服务探测。 -sS&#xff1a;使用…

AI 智能名片链动 2+1 模式商城小程序中的体验策略

摘要&#xff1a;本文探讨了在 AI 智能名片链动 21 模式商城小程序中&#xff0c;体验策略如何服务于用户体验&#xff0c;以及与产品策略的区别。重点分析了该小程序如何通过关注用户在使用过程中的流畅度、视觉体感等方面&#xff0c;实现“让用户用得爽”的目标&#xff0c;…

华为HarmonyOS地图服务 1 -- 如何实现地图呈现?

如何使用地图组件MapComponent和MapComponentController呈现地图&#xff0c;效果如下图所示。 MapComponent是地图组件&#xff0c;用于在您的页面中放置地图。MapComponentController是地图组件的主要功能入口类&#xff0c;用来操作地图&#xff0c;与地图有关的所有方法从此…

【小程序】微信小程序课程 -1 安装与配置

目录 1 微信小程序概述 1.1 什么是微信小程序 1.2 注册微信小程序账号 1.3 微信小程序配置 1.4 小程序开发流程 1.5 小程序成员 2、创建微信小程序项目 2.1 创建项目流程 2.2 创建项目 2.3 本地开发支持http 3 项目目录结构 3.1项目目录结构 3.1.1 目录介绍 3.1.2…

爬虫过程 | 蜘蛛程序爬取数据流程(初学者适用)

蜘蛛程序&#xff08;也称网络爬虫&#xff0c;是搜索引擎的重要组成部分&#xff09; 主要功能&#xff1a;遍历互联网&#xff0c;抓取网站信息并建立索引&#xff0c;便于用户在搜索引擎中检索到最新的网页内容工作原理&#xff1a;从初始网站页面的URL开始&#xff0c;发送…