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


每日激励:“不设限和自我肯定的心态: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 serverclient:protobuf_client.cpp request.pb.ccg++ -o $@ $^ -std=c++14  -lmuduo_net -lmuduo_base -pthread -lprotobuf -L/lib/muduo -lz
server:protobuf_server.cpp request.pb.ccg++ -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,因为在客户端不能发生阻塞没导致无法sendClient(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,因为他是阻塞式的循环,这样会导致无法sendmuduo::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.mzph.cn/web/60968.shtml

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

相关文章

Jenkins的使用

文章目录 一、Jenkins是什么\有什么用\与GitLab的对比二、Jenkins的安装与配置Jenkins的安装方式在Linux上安装Jenkins&#xff1a;在Windows上安装Jenkins&#xff1a;配置Jenkins&#xff1a; &#xff08;可选&#xff09;配置启动用户为root&#xff08;一定要是root吗??…

Qml-TabBar类使用

Qml-TabBar类使用 TabBar的概述 TabBar继承于Container 由TabButton进行填充&#xff0c;可以与提供currentIndex属性的任何容器或布局控件一起使用&#xff0c;如StackLayout 或 SwipeView&#xff1b;contentHeight : real:TabBar的内容高度&#xff0c;用于计算标签栏的隐…

Cyberchef 辅助网络安全运营-数据格式转换

在网络安全的世界中&#xff0c;经常会遇到各种格式的数据&#xff0c;比如二进制&#xff0c;比如说16进制&#xff0c;URL编码&#xff0c;HTML编码&#xff0c;Unicode编码&#xff0c;Base格式的编码。网络安全运营一个明确的目标就是把这些不同的数据格式换成为可读的字符…

C语言——指针初阶(一)

目录 一.什么是指针&#xff1f;&#xff1f;&#xff1f; 指针是什么&#xff1f; 指针变量&#xff1a; 总结&#xff1a; 总结&#xff1a; 二.指针和指针类型 指针-整数&#xff1a; 总结&#xff1a; 指针的解引用 总结&#xff1a; 三.野指针 如何规避野指针 往期…

Tcon技术和Tconless技术介绍

文章目录 TCON技术&#xff08;传统时序控制器&#xff09;定义&#xff1a;主要功能&#xff1a;优点&#xff1a;缺点&#xff1a; TCONless技术&#xff08;无独立时序控制器&#xff09;定义&#xff1a;工作原理&#xff1a;优点&#xff1a;缺点&#xff1a; TCON与TCONl…

World of Warcraft /script SetRaidTarget(“target“, n, ““) n=8,7,6,5,4,3,2,1,0

魔兽世界执行当前目标标记方法 /script SetRaidTarget("target", n, "") n8,7,6,5,4,3,2,1,0 解析这个lua脚本 D:\Battle.net\World of Warcraft\_classic_\Interface\AddOns\wMarker wMarker.lua /script SetRaidTarget("target", 8, &quo…

学习笔记035——MySQL索引

数据库索引 索引是为了提高数据的查询速度&#xff0c;相当于给数据进行编号&#xff0c;在查找数据的时候就可以通过编号快速找到对应的数据。 索引内部数据结构&#xff1a;B Tree 主键自带索引。 如&#xff1a; insert into user (id, name) values (1,f); insert int…

在Unity中实现物体动画的完整流程

在Unity中&#xff0c;动画是游戏开发中不可或缺的一部分。无论是2D还是3D游戏&#xff0c;动画都能为游戏增添生动的视觉效果。本文将详细介绍如何在Unity中为物体添加动画&#xff0c;包括资源的准备、播放组件的添加、动画控制器的创建以及动画片段的制作与调度。 1. 准备动…

Python数据分析实例五、US 大选捐款数据分析

美国联邦选举委员会 (FEC) 公布了对政治竞选活动的贡献数据。这包括投稿人姓名、职业和雇主、地址和投款金额。2012 年美国总统大选的贡献数据以单个 150 MB 的 CSV 文件P00000001-ALL.csv形式提供,该文件可以通过以下pandas.read_csv加载: import pandas as pdfec = pd.r…

vue3项目搭建-3-Pinia的使用

Pinia 是集中状态管理工具 基本用法 Pinia 是 Vue 的专属的最新状态管理库&#xff0c;是 Vuex 状态管理工具的替代品 官方文档&#xff1a;pinia官方文档 找到开始目录&#xff0c;根据文档安装和入门 pinia&#xff0c;启用一个新的终端&#xff0c;输入指令 npm install…

SAP开发语言ABAP开发入门

1. 了解ABAP开发环境和基础知识 - ABAP简介 - ABAP&#xff08;Advanced Business Application Programming&#xff09;是SAP系统中的编程语言&#xff0c;主要用于开发企业级的业务应用程序&#xff0c;如财务、物流、人力资源等模块的定制开发。 - 开发环境搭建 - 首先需…

修改bag的frame_id的工具srv_tools

在使用数据集导航或者建图时&#xff0c;bag中的点云或者其他话题的frame_id没有和需要的对应 1.创建工作空间 2.cd xxxx/src 3.git clone https://github.com/srv/srv_tools.git cd .. catkin_make source ./devel/setup.bash rosrun bag_tools change_frame_id.py -t /要改…

IDEA2023版本配置项目全局编码

IDEA默认的项目编码是UTF-8&#xff0c;有时候拿到别人的代码使用的编码是GBK&#xff0c;虽然可以在idea右下角进行修改&#xff0c;但是一个一个的修改太慢了。所以需要去进行该项目的编码全局配置。接下来直接讲步骤&#xff0c;以IDEA2023版本为例。 第一步 File>Sett…

大数据学习18之Spark-SQL

1.概述 1.1.简介 Spark SQL 是 Apache Spark 用于处理结构化数据的模块。 1.2.历史 1.2.1.Shark Hadoop诞生初期&#xff0c;Hive是唯一在Hadoop上运行的SQL-on-Hadoop工具&#xff0c;MR的中间计算过程产生了大量的磁盘落地操作&#xff0c;消耗了大量的I/O&#xff0c;降低…

【Android】Service使用方法:本地服务 / 可通信服务 / 前台服务 / 远程服务(AIDL)

1 本地Service 这是最普通、最常用的后台服务Service。 1.1 使用步骤 步骤1&#xff1a;新建子类继承Service类&#xff1a;需重写父类的onCreate()、onStartCommand()、onDestroy()和onBind()方法步骤2&#xff1a;构建用于启动Service的Intent对象步骤3&#xff1a;调用st…

QML学习 —— 34、视频媒体播放器(附源码)

效果 说明 您可以单独使用MediaPlayer播放音频内容(如音频),也可以将其与VideoOutput结合使用以渲染视频。VideoOutput项支持未转换、拉伸和均匀缩放的视频演示。有关拉伸均匀缩放演示文稿的描述,请参见fillMode属性描述。 播放可能出错问题 出现的问题:      DirectS…

Spring MVC练习(前后端分离开发实例)

White graces&#xff1a;个人主页 &#x1f649;专栏推荐:Java入门知识&#x1f649; &#x1f439;今日诗词:二十五弦弹夜月&#xff0c;不胜清怨却飞来&#x1f439; ⛳️点赞 ☀️收藏⭐️关注&#x1f4ac;卑微小博主&#x1f64f; ⛳️点赞 ☀️收藏⭐️关注&#x1f4…

如何把大模型调教成派大星?

目录 主要内容模型图实验结果如何把大模型变成派大星&#xff1f;chatglm3-6B 数据集准备代码运行微调结果 文章声明&#xff1a;非广告&#xff0c;仅个人体验&#xff1a;参考文献&#xff1a;https://www.aspiringcode.com/content?id17197387451937&uid291a2ae1546b48…

国土安全部发布关键基础设施安全人工智能框架

美国国土安全部 (DHS) 发布建议&#xff0c;概述如何在关键基础设施中安全开发和部署人工智能 (AI)。 https://www.dhs.gov/news/2024/11/14/groundbreaking-framework-safe-and-secure-deployment-ai-critical-infrastructure 关键基础设施中人工智能的角色和职责框架 https:/…

QML TableView 实例演示 + 可能遇到的一些问题(Qt_6_5_3)

一、可能遇到的一些问题 Q1&#xff1a;如何禁用拖动&#xff1f; 在TableView下加一句代码即可&#xff1a; interactive: false 补充&#xff1a;这个属性并不专属于TableView&#xff0c;而是一个通用属性。很多Controls下的控件都可以使用&#xff0c;其主要作用就是控…