【C++第三方库】Muduo库结合ProtoBuf库搭建服务端和客户端的过程和源码

news2024/11/28 8:23:57


每日激励:“不设限和自我肯定的心态:I can do all things。 — Stephen Curry”

绪论​:
本章我将结合之前的这俩个第三方库快速上手protobuf序列化和反序列化框架和muduo网络,来去实现muduo库在protocol协议搭建服务端和客户端。
————————
早关注不迷路,话不多说安全带系好,发车啦(建议电脑观看)。

第三方框架muduo库protobuf通信

在muduo库中的数据格式:
在这里插入图片描述

  1. len:代表后面的长度

protobuf根据我们的proto⽂件⽣成的代码中,会⽣成对应类型的类,⽐如TranslateRequest 对应了⼀个TranslateRequest 类,⽽不仅仅如此,protobuf⽐我们想象中做的事情更多,每个对应的类中,
都包含有⼀个描述结构的指针:

static const ::google::protobuf::Descriptor* descriptor();

这个描述结构⾮常重要,其内部可以获取到当前对应类类型名称,以及各项成员的名称,因此通过这些名称,加上协议中的typename字段,就可以实现完美的对应关系了


基于muduo库对于protobuf结构的数据处理进行的网络通信逻辑图:
在这里插入图片描述
上图我们能理解成:
将TcpClient部分看成 : 客户端
将ProtobufDispatch部分看出 : 服务端
客户端和服务端都要进行的流程:
将数据就绪绑定codec协议处理器,而在协议处理器中又绑定上事件分发器,对不同事件就绪处理进行分发处理
具体如下图:
请添加图片描述
了解了上述关系,接下来就可以通过muduo库中陈硕⼤佬提供的接⼝来编写我们的客⼾端/服务器端通信了,其最为简便之处就在于我们可以把更多的精⼒放到业务处理函数的实现上,⽽不是服务器的搭
建或者协议的解析处理上了


具体流程从代码 中边看边敲边学。

实操(编写服务端和客户端流程)

目的:基于muduo库中,对于Protobuf协议的处理代码,实现翻译 + 加法服务端与客户端

  1. 编写proto文件,生成相关的结构

创建一个protobuf目录(假如你写了muduo的字典实操:创建一个dic目录将dic*文件都移道内部)
在内部创建所需的文件:

  1. request.proto
  2. protobuf_client.cpp
  3. protobuf_server.cpp
  4. makefile

编写proto文件

  1. 协议版本
  2. 创建包
  3. 创建message结构
    1. TranslateRequest
      1. msg
    2. TranslateResponse
      1. msg
    3. AddRequest
      1. num1
      2. num2
    4. AddResponse
      1. result
  4. protoc生成文件

编写protobuf服务端

使用muduo库中的样例代码
第三方库的muduo库关于Protobuf相关的代码拷贝进来,在third/muduo-master/examples/protobf,将protobuf目录拷贝一份到系统默认寻找头文件目录下(/user/include),这样就能使用里面code.cc、code.h、dispatcher.h 文件

头文件包含使code.h/dispatcher.h/request.pb.h
在这里插入图片描述
编写方法:学习server.cc的内容

创建Server类
针对不同的请求定义不同的智能指针类

  1. 类型重命名:定义一智能指针方便后面的使用
    如:TranslateRequestPtr:指针:TranslateRequest(该指针在刚刚生成的request.pb.h)
    AddRequestPtr、MessagePtr(Message指针在google::protobuf::下)
  2. 构造
    1. 参数传入端口:
    2. 初始化:
      1. _server(loop、InetAddress、name)
      2. _dispatch(在ProtobufCode类内,并初始化绑定自定义onUnknowMessage函数)
      3. _codec(在ProtobufCode类内,并绑定ProtobufDispatchmeow命名空间内的onProtobuffmessage函数,并将 分发器 绑定给该函数)
      4. 定义onUnknowMessage,收到未知消息事件就绪后的回调方法
    3. 注册业务请求处理函数
      1. 使用registerMessageCallback<填写类型>,类型有你protobuf结构中定义的两种请求报文 TranslateRequest、AddRequest
      2. 给分发器绑定onTranslate、onAdd
      3. 定义回调函数:
        1. onTranslate(TcpConnnectionPtr,TranslateRequestPtr,TimeStamp)
        2. onAdd (同上,只不过注意 AddRequestPtr)
      4. 同样给消息事件就绪回调 函数
        1. _server绑定ProctobufCodec中的onMessageh函数(为了处理得到数据后的序列化工作!),并且把 协议处理器 绑定
        2. _server绑定自定义的onConnection函数

对于上面的onTranslate、onAdd、onMessageh、onConnec

  1. onUnknowMessage:
    查看server.cc内的写法
  2. onConnection (conn)
    判断连接是否存在
    成功 LOG_INFO << 新连接建立成功!
    连接即将关闭
  3. onTranslate
    从TranslateRequestPtr类的message对象中的有效信息,也就是需要翻译的内容(使用message对象的msg函数)
    进行翻译得到结果(translte将从muduo中拷贝过来使用 )
    组织protobuf的响应(定义TranslateResponse resp对象调用set_msg函数组织响应)
    发送响应(使用codec对象send()codec会在内部进行序列化然后发送)
  4. onAdd
    通过message对象的num1…函数获取数据
    计算结果result
    创建Add的应答类response,设置结果使用set_result函数、最后再次使用codec发送
  1. start
    1. _server 开启
    2. _baselopp 开启监听

成员变量:

服务器对象:_server
请求分发器:_dispatcher
protobuf协议处理器:_codec
循环监视器:_baseloop

主函数

创建server对象
启动

编写protobuf客户端

打开client.cc,头文件拷贝过来,带上request.pb.h
类Client

成员变量:

  1. _client
  2. _dispatcher(ProtobufCodec类)
  3. codec
  4. 在从muduo中拷贝:
    1. 实现同步:latch
    2. 异步循环处理线程loopthread(头文件 muoduo/eventloopthread.h)
    3. 客户端对应的链接conn(CountDownlatch.h)

请求的智能指针:

  1. MessagePtr(对该对象创建google::protobuf::Message)
  2. AddResponsePtr
  3. TranslateResponsePtr

构造:

  1. 参数(ip、port)(设置所要请求的主机的socket{ip,port})
  2. 初始化:
    1. latch(1)
    2. _client(loop,inetaddress(ip,port),name)
    3. dispatcher(雷同server略,同样是给个默认处理函数,onknowMessage,this,_1,_2,_3)
    4. codec(雷同server略,同样是把dispatch给到codec,&ProtobufDispatch::onProtobufMessage,&_dispatch,_1,_2,_3)
  3. 注册回调函数( 雷同拷贝server的过来)
    1. 注意:对于dispatch注册的处理函数的类型为Response,并且知道其所需要的函数有几个参数(看源码的构造,得知该适配器中的函数需要有几个参数)
    2. 设置回调函数(onTranslate、onAdd)
  4. connect链接的接口
    1. client调用connect函数
    2. latch 等待 连接到来
  5. Tranlate(string)(这是用户用来发送请求的)
    1. 定义请求 req(使用request::TranslateRequeset,因为适配send的Message父类)
    2. 设置信息数据
    3. 调用send
  6. Add(n1.n2)
    1. AddRequest req;(使用request::AddRequest ,因为适配send的Message父类)
    2. 设置num1、num2
    3. 再send

private内容:
send发送的接口(Message父类指针对象 * message)

  1. 判断连接是否存在
  2. 存在:修改send:使用codec调用send
  3. 不存在返回false

响应处理函数:

  1. onTranslate(这是接受到返回结果的)(参数:TcpConnectionPtr、TranslateResponse、Timestamp)
    1. cout 翻译结果 message->msg() endl
  2. onAdd
    1. cout 加法结果 message->result() endl

主函数:

  1. client对象
  2. 链接服务器
  3. 调用翻译
  4. 调用加法
  5. 休眠1s

makefile

改成文件名protobuf_client / …_server
生成的可执行程序为 client / server
注意联合编译:codec.cc
链接库protobuf

需要注意的点(注意其中一些地方会用到文件权限的修改,以及sudo提升权限,这些就不诉了,可以评论提问)

  1. /third/build/release-install-cpp11/include/muduo头文件放到/usr/include下在这里插入图片描述
  2. 将库文件放到库文件内,在编译时-L寻找库
    将该库文件third/build/release-install-cpp11/lib重命名为muduo,放到/lib下
    在这里插入图片描述
  3. 需要添加-lz的库文件,否则回报下图的错误在这里插入图片描述
  4. 主要要加上request.pb.cc联合编译,否则回报下图问题在这里插入图片描述

一些头文件问题:

  1. 在头文件处加上:#include “protobuf/codec/codec.cc”,一起编译
  2. 并且修改/include/protobuf/codec/codec.cc内部头文件路劲问题,以及将google-inl.h文件从/third/muduo-master/muduo/net/protorpc拷贝到当前目录下在这里插入图片描述
    在这里插入图片描述
.PHONY:all
all: client server
	
client:protobuf_client.cpp request.pb.cc
	g++ -o $@ $^ -std=c++14  -lmuduo_net -lmuduo_base -pthread -lprotobuf -L/lib/muduo -lz
server:protobuf_server.cpp request.pb.cc
	g++ -o $@ $^ -std=c++14  -lmuduo_net -lmuduo_base -pthread -lprotobuf -L/lib/muduo -lz

.PHONY:clean
clean:
	rm  -f  server  client

服务端所有源码:

#include "protobuf/codec/codec.h"
#include "protobuf/codec/dispatcher.h"
#include "muduo/base/Logging.h"
#include "muduo/base/Mutex.h"
#include "muduo/net/EventLoop.h"
#include "muduo/net/TcpServer.h"

#include "protobuf/codec/codec.cc"
#include "request.pb.h"
#include <iostream>
#include <functional>


class Server{
public:
//创建指针指针方便后面使用
typedef std::shared_ptr<request::TranslateRequest> TranslateRequestPtr;
typedef std::shared_ptr<request::AddRequest> AddRequestPtr;
typedef std::shared_ptr<google::protobuf::Message> MessagePtr;

Server(uint16_t port):_server(&_baseloop,muduo::net::InetAddress("0.0.0.0",port),"TcpServer"),
    _dispatcher(std::bind(&Server::onUnknownMessage,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3)),
    _codec(std::bind(&ProtobufDispatcher::onProtobufMessage,&_dispatcher,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3))
    {
      //给dispatcher内部注册请求处理函数:
      
      //1. 如当监听到Translate请求后就会执行该函数
      _dispatcher.registerMessageCallback<request::TranslateRequest>(std::bind(&Server::onTranslate,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
      //2. .... 
      _dispatcher.registerMessageCallback<request::AddRequest>(std::bind(&Server::onAdd,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));

      //再给server 注册处理函数 ; 连接事件就绪 / 正常事件就绪

      //连接就绪:
      _server.setConnectionCallback(std::bind(&Server::onConnection,this,std::placeholders::_1));

      //事件就绪:
      //对于绑定ProtobufCodec里面的onMessage函数,是因为需要进行数据的序列化操作,所以使用Protobuf
      _server.setMessageCallback(std::bind(&ProtobufCodec::onMessage,&_codec,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
    }

    void start()
    {
      //启动服务器
      _server.start();
      //开启循环监听
      _baseloop.loop();
    }
private:
//未知事件就绪:
  void onUnknownMessage(const muduo::net::TcpConnectionPtr& conn,
                        const MessagePtr& message,
                        muduo::Timestamp)
  {
    LOG_INFO << "onUnknownMessage: " << message->GetTypeName();
    conn->shutdown();
  }

  std::string Translate(std::string english){
        std::unordered_map<std::string,std::string> dict_map = {
            {"apple","苹果"},
            {"hello","你好"}
        };      
        // for(auto k : dict_map)
        //     std::cout << k.first << std::endl;

        std::cout <<"eglish:" << english << std::endl; 
        // english.resize(english.size() - strlen("\n"));//注意tcp协议中粘包问题的\r\n,需要把\r\n字符去除,才能算原本的字符串也才能正确的判断
        auto iter = dict_map.find(english);
        
        if(iter == dict_map.end())
        {
            return "查不到此单词\n";
        }

        return iter->second + "\n";
    } 
  void onTranslate(const muduo::net::TcpConnectionPtr& conn,const TranslateRequestPtr& message,muduo::Timestamp){
    std::string req = message->msg();
    std::string translate_res = Translate(req);

    request::TranslateResponse resp;
    resp.set_msg(translate_res);
    _codec.send(conn,resp);
  }

  void onAdd(const muduo::net::TcpConnectionPtr& conn,const AddRequestPtr& message,muduo::Timestamp){
    uint32_t num1 = message->num1();
    uint32_t num2 = message->num2();
    uint32_t result = num1 + num2;

    request::AddResponse resp;
    resp.set_result((uint32_t)result);
    _codec.send(conn,resp);
  }

  void onConnection(const muduo::net::TcpConnectionPtr &conn){
        if(!conn->connected())
        {
            std::cout << "新连接关闭" << std::endl;
        }
        else
        {
            std::cout << "新连接成功" << std::endl;
        }   
  }

private:
    ProtobufDispatcher _dispatcher;
    ProtobufCodec _codec;
    muduo::net::EventLoop _baseloop;
    muduo::net::TcpServer _server;
};

int main()
{
  Server server(8080);
  server.start();

  return 0;
}


客户端源码:

#include "protobuf/codec/codec.h"
#include "protobuf/codec/dispatcher.h"
#include "muduo/base/Logging.h"
#include "muduo/base/Mutex.h"
#include "muduo/net/EventLoop.h"
#include "muduo/net/TcpServer.h"
#include "muduo/net/TcpClient.h"
#include "muduo/base/CountDownLatch.h"
#include "muduo/net/EventLoopThread.h"

#include "protobuf/codec/codec.cc"

#include "request.pb.h"
#include <iostream>
#include <functional>
#include <string>


class Client{
public:
typedef std::shared_ptr<google::protobuf::Message> MessagePtr; 
typedef std::shared_ptr<request::AddResponse> AddResponsePtr;
typedef std::shared_ptr<request::TranslateResponse> TranslateResponsePtr;

//使用 _loopthread.startLoop() 获取loop,因为在客户端不能发生阻塞没导致无法send
    Client(std::string sip,int16_t sport):
    _client(_loopthread.startLoop(),muduo::net::InetAddress(sip,sport),"TcpClient"),
    _dispatcher(std::bind(&Client::onknowMessage,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3)),
    _codec(std::bind(&ProtobufDispatcher::onProtobufMessage,&_dispatcher,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3)),
    _latch(1)
    {
        //注册消息处理函数
        _dispatcher.registerMessageCallback<request::TranslateResponse>(std::bind(&Client::onTranslate,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
      //2. .... 
      _dispatcher.registerMessageCallback<request::AddResponse>(std::bind(&Client::onAdd,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));

      //再给server 注册处理函数 ; 连接事件就绪 / 正常事件就绪

      //连接就绪:
      _client.setConnectionCallback(std::bind(&Client::onConnection,this,std::placeholders::_1));

      //事件就绪:
      //对于绑定ProtobufCodec里面的onMessage函数,是因为需要进行数据的序列化操作,所以使用Protobuf
      _client.setMessageCallback(std::bind(&ProtobufCodec::onMessage,&_codec,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
    }

    void connect()
    {
        _client.connect();//建立连接
        _latch.wait();//等待连接
    }



    void Translate(const std::string& message)
    {
        request::TranslateRequest resp;
        resp.set_msg(message);
        send(&resp);
    }

    void Add(int num1,int num2){
        request::AddRequest resp;
        resp.set_num1(num1);
        resp.set_num2(num2);

        send(&resp);
    }

private:

    bool send(const google::protobuf::Message* msg)
    {
        if(_conn->connected())
        {
            _codec.send(_conn,*msg);
            return true;
        }
        return false;
    }


  void onConnection(const muduo::net::TcpConnectionPtr &conn){
        if(!conn->connected())
        {
            std::cout << "新连接关闭" << std::endl;
        }
        else
        {
            std::cout << "新连接成功" << std::endl;
            _latch.countDown();//当连接成功后,唤醒 
            _conn = conn;//注意别忘了!
        }   
    }   


    void onTranslate(const muduo::net::TcpConnectionPtr&,
                                const TranslateResponsePtr& message,
                                muduo::Timestamp)
    {
        std::cout << "翻译结果" << message->msg() << std::endl;
    }

    void onAdd(const muduo::net::TcpConnectionPtr&,
                                const AddResponsePtr& message,
                                muduo::Timestamp){
        std::cout << "加法结果" << message->result() << std::endl;
    }

    void onknowMessage(const muduo::net::TcpConnectionPtr& conn,
                                const MessagePtr& message,
                                muduo::Timestamp)
    {
        LOG_INFO << "onUnknownMessage: " << message->GetTypeName();
        conn->shutdown();
    }

private:
    ProtobufDispatcher _dispatcher;
    ProtobufCodec _codec;
    muduo::net::EventLoopThread _loopthread;//不在像中服务器使用eventloop,因为他是阻塞式的循环,这样会导致无法send
    muduo::net::TcpClient _client;

    muduo::CountDownLatch _latch;//进行防止等待连接导致的阻塞
    muduo::net::TcpConnectionPtr _conn;
};

int main()
{
    Client client("127.0.0.1",8080);
    client.connect();

    client.Translate("apple");
    client.Add(1,2);

    sleep(1);

    return 0;
}

运行结果:


本章完。预知后事如何,暂听下回分解。

如果有任何问题欢迎讨论哈!

如果觉得这篇文章对你有所帮助的话点点赞吧!

持续更新大量C++细致内容,早关注不迷路。

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

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

相关文章

Scala—Map用法详解

Scala—Map用法详解 在 Scala 中&#xff0c;Map 是一种键值对的集合&#xff0c;其中每个键都是唯一的。Scala 提供了两种类型的 Map&#xff1a;不可变 Map 和可变 Map。 1. 不可变集合&#xff08;Map&#xff09; 不可变 Map 是默认的 Map 实现&#xff0c;位于 scala.co…

文本处理之sed

1、概述 sed是文本编辑器&#xff0c;作用是对文本的内容进行增删改查。 和vim不一样&#xff0c;sed是按行进行处理。 sed一次处理一行内容&#xff0c;处理完一行之后紧接着处理下一行&#xff0c;一直到文件的末尾 模式空间&#xff1a;临时储存&#xff0c;修改的结果临…

了解网络威胁情报:全面概述

网络威胁情报 CTI 是指系统地收集和分析与威胁相关的数据&#xff0c;以提供可操作的见解&#xff0c;从而增强组织的网络安全防御和决策过程。 在数字威胁不断演变的时代&#xff0c;了解网络威胁情报对于组织来说至关重要。复杂网络攻击的兴起凸显了制定强有力的策略以保护敏…

Python 海龟绘图 turtle 的介绍

python的计算生态中包含标准库和第三方库 标准库&#xff1a;随着解释器直接安装到操作系统中的功能模块 第三方库&#xff1a;需要经过安装才能使用的功能模块 库Library 包 Package 模块Module 统称为模块 turtle 是一个图形绘制的函数库&#xff0c;是标准库&#…

学习日志017--python的几种排序算法

冒泡排序 def bubble_sort(alist):i 0while i<len(alist):j0while j<len(alist)-1:if alist[j]>alist[j1]:alist[j],alist[j1] alist[j1],alist[j]j1i1l [2,4,6,8,0,1,3,5,7,9] bubble_sort(l) print(l) 选择排序 def select_sort(alist):i 0while i<len(al…

java集合及源码

目录 一.集合框架概述 1.1集合和数组 数组 集合 1.2Java集合框架体系 常用 二. Collection中的常用方法 添加 判断 删除 其它 集合与数组的相互转换 三Iterator(迭代器)接口 3.0源码 3.1作用及格式 3.2原理 3.3注意 3.4获取迭代器(Iterator)对象 3.5. 实现…

⭐️ GitHub Star 数量前十的工作流项目

文章开始前&#xff0c;我们先做个小调查&#xff1a;在日常工作中&#xff0c;你会使用自动化工作流工具吗&#xff1f;&#x1f64b; 事实上&#xff0c;工作流工具已经变成了提升效率的关键。其实在此之前我们已经写过一篇博客&#xff0c;跟大家分享五个好用的工作流工具。…

【Jenkins】自动化部署 maven 项目笔记

文章目录 前言1. Jenkins 新增 Maven 项目2. Jenkins 配置 Github 信息3. Jenkins 清理 Workspace4. Jenkins 配置 后置Shell脚本后记 前言 目标&#xff1a;自动化部署自己的github项目 过程&#xff1a;jenkins 配置、 shell 脚本积累 相关连接 Jenkins 官方 docker 指导d…

杂7杂8学一点之多普勒效应

最重要的放在最前面&#xff0c;本文学习资料&#xff1a;B站介绍多普勒效应的优秀视频。如果上学时老师这么讲课&#xff0c;我估计会爱上上课。 目录 1. 多普勒效应 2. 多普勒效应对通信的影响 3. 多普勒效应对低轨卫星通信的影响 1. 多普勒效应 一个小石头扔进平静的湖面…

【python数据结构算法】排序算法 #冒泡 #选择排序 #快排 #插入排序

思维导图 一、经典冒泡 冒泡排序&#xff1a;是一种简单的排序算法&#xff0c;它重复的遍历要排序的序列&#xff0c;一次比较两个元素&#xff0c;如果他们的顺序错误&#xff0c;就把他们交换过来。 冒泡排序算法的运作如下&#xff1a; 比较相邻的元素。如果第一个比第二…

Linux系统之fuser命令的基本使用

Linux系统之fuser命令的基本使用 一、fuser命令介绍二、fuser命令使用帮助2.1 help帮助信息2.1 基本语法①通用选项②文件/设备相关选项③网络相关选项④进程操作选项⑤其他选项 三、fuser命令的基本使用3.1 查找挂载点的进程3.2 查看指定设备进程信息3.3 查找监听特定端口的进…

stable Diffusion官方模型下载

v2-1_768-ema-pruned.safetensors 下载地址&#xff1a; https://huggingface.co/stabilityai/stable-diffusion-2-1/tree/main 下载完成后&#xff0c;放到&#xff1a;E:\AITOOLS\stable-diffusion-webui\models\Stable-diffusion 模型&#xff1a;sd_xl_base_1.0.safetens…

《并查集算法详解及实用模板》

《重生我要成为并查集高手&#x1f354;&#x1f354;&#x1f354;》 并查集&#xff1a;快速查询和快速合并&#xff0c; 路径压缩&#xff0c; 按大小&#xff0c;高度&#xff0c;秩合并。 静态数组实现 &#x1f607;前言 在数据的海洋中&#xff0c;有一种悄然流淌的力量…

群聊前选择患者功能的实现

和普通群聊不同&#xff0c;开启一个图文会话聊天&#xff0c;必须先选择患者、团队、医生。 原来是集成到腾讯IM当中&#xff0c;现在需要单独写一个页面 原来的代码在这里&#xff1a; const handleShow () > {uni.navigateTo({url: /pageB/active-home/active-home})}…

基于边缘智能网关的机房安全监测应用

随着我国工业互联网的扎实推进&#xff0c;越来越多地区积极建设信息基础设施&#xff0c;以充沛算力支撑产业物联网的可持续发展&#xff0c;数据机房就是其中的典型代表。而且随着机房规模的扩大&#xff0c;对于机房的安全管理难题挑战也日益增加。 面向数据机房安全监测与管…

unity 使用UI上的数字按钮,给text添加数字,并且显示光标,删除光标前数字,

今天有个需求&#xff0c;输入身份证&#xff0c;但是不用键盘&#xff0c;要点击按钮输入数字&#xff0c;并且可以控制光标&#xff0c; 1、数字按钮&#xff1a;点击后text添加数字内容 2、删除按钮&#xff1a;删除光标前的一个字符 3、左箭头&#xff1a;移动光标向左移动…

C++设计模式(单例模式)

一、介绍 1.动机 在软件系统中&#xff0c;经常有这样一些特殊的类&#xff0c;必须保证它们在系统中只存在一个实例&#xff0c;才能确保它们的逻辑正确性、以及良好的效率。 如何绕过常规的构造器&#xff0c;提供一种机制来保证一个类只有一个实例? 这应该是类设计者的…

Could not locate device support files.

报错信息&#xff1a;Failure Reason: The device may be running a version of iOS (13.6.1 17G80) that is not supported by this version of Xcode.[missing string: 869a8e318f07f3e2f42e11d435502286094f76de] 问题&#xff1a;xcode15升级到xcode16之后&#xff0c;13.…

【Webgl_glslThreejs】制作流水效果/毛玻璃效果材质

效果预览 shadertory源码 source&#xff1a; https://www.shadertoy.com/view/lldyDs 材质代码 import { DoubleSide, ShaderChunk, ShaderMaterial, TextureLoader } from "three"; /** * * source https://www.shadertoy.com/view/lldyDs */export default fu…

海康VsionMaster学习笔记(学习工具+思路)

一、前言 VisionMaster算法平台集成机器视觉多种算法组件&#xff0c;适用多种应用场景&#xff0c;可快速组合算法&#xff0c;实现对工件或被测物的查找测量与缺陷检测等。VM算法平台依托海康威视在图像领域多年的技术积淀&#xff0c;自带强大的视觉分析工具库&#xff0c;可…