【Qt源码】窥视信号槽实现机制

为了便于通过调试进源码探究下Qt信号槽实现原理,这里简单写一段代码如下所示。

1.自定义信号槽连接

MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow) {ui->setupUi(this);QObject::connect(ui->pushButton,&QPushButton::clicked,this,&MainWindow::on_pushButton_clicked);
}
MainWindow::~MainWindow() { delete ui;}
void MainWindow::on_pushButton_clicked() { qDebug() << "this is slot function"; }

2.调试进入源码观察槽函数调用流程

为了观察QPushButton::clicked()信号发射后如何自动调用on_pushButton_clicked槽函数,首先在源码里打上断点,通过如下流程最后实现由信号发射到槽函数调用。

// step1.这里打上断点
void QAbstractButton::mouseReleaseEvent(QMouseEvent *e)
{Q_D(QAbstractButton);if (e->button() != Qt::LeftButton) {e->ignore();return;}d->pressed = false;if (!d->down) {// refresh is required by QMacStyle to resume the default button animationd->refresh();e->ignore();return;}if (hitButton(e->pos())) {d->repeatTimer.stop();d->click(); // 这里发射click信号e->accept();} else {setDown(false);e->ignore();}
}
// step2. 进入click函数
void QAbstractButtonPrivate::click()
{Q_Q(QAbstractButton);down = false;blockRefresh = true;bool changeState = true;if (checked && queryCheckedButton() == q) {// the checked button of an exclusive or autoexclusive group cannot be unchecked
#if QT_CONFIG(buttongroup)if (group ? group->d_func()->exclusive : autoExclusive)
#elseif (autoExclusive)
#endifchangeState = false;}QPointer<QAbstractButton> guard(q);if (changeState) {q->nextCheckState();if (!guard)return;}blockRefresh = false;refresh();q->repaint();if (guard)emitReleased();if (guard)emitClicked();
}
// step3. 发射clciked
void QAbstractButtonPrivate::emitClicked()
{Q_Q(QAbstractButton);QPointer<QAbstractButton> guard(q);// 这里F11进入activate函数,该函数在moc_abstractbutton.cpp文件中,该文件不可见emit q->clicked(checked); 
#if QT_CONFIG(buttongroup)if (guard && group) {emit group->buttonClicked(group->id(q));if (guard && group)emit group->buttonClicked(q);}
#endif
}
// step4. 元对象自动生成的activate函数
void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index,void **argv)
{activate(sender, QMetaObjectPrivate::signalOffset(m), local_signal_index, argv);
}
// step5 进入activate函数
void QMetaObject::activate(QObject *sender, int signalOffset, int local_signal_index, void **argv)
{int signal_index = signalOffset + local_signal_index;if (sender->d_func()->blockSig)return;Q_TRACE_SCOPE(QMetaObject_activate, sender, signal_index);if (sender->d_func()->isDeclarativeSignalConnected(signal_index)&& QAbstractDeclarativeData::signalEmitted) {Q_TRACE_SCOPE(QMetaObject_activate_declarative_signal, sender, signal_index);QAbstractDeclarativeData::signalEmitted(sender->d_func()->declarativeData, sender,signal_index, argv);}if (!sender->d_func()->isSignalConnected(signal_index, /*checkDeclarative =*/ false)&& !qt_signal_spy_callback_set.signal_begin_callback&& !qt_signal_spy_callback_set.signal_end_callback) {// The possible declarative connection is done, and nothing else is connected, so:return;}void *empty_argv[] = { 0 };if (qt_signal_spy_callback_set.signal_begin_callback != 0) {qt_signal_spy_callback_set.signal_begin_callback(sender, signal_index,argv ? argv : empty_argv);}{QMutexLocker locker(signalSlotLock(sender));struct ConnectionListsRef {QObjectConnectionListVector *connectionLists;ConnectionListsRef(QObjectConnectionListVector *connectionLists) : connectionLists(connectionLists){if (connectionLists)++connectionLists->inUse;}~ConnectionListsRef(){if (!connectionLists)return;--connectionLists->inUse;Q_ASSERT(connectionLists->inUse >= 0);if (connectionLists->orphaned) {if (!connectionLists->inUse)delete connectionLists;}}QObjectConnectionListVector *operator->() const { return connectionLists; }};// 1.获取sender里所有connection列表ConnectionListsRef connectionLists = sender->d_func()->connectionLists;if (!connectionLists.connectionLists) {locker.unlock();if (qt_signal_spy_callback_set.signal_end_callback != 0)qt_signal_spy_callback_set.signal_end_callback(sender, signal_index);return;}const QObjectPrivate::ConnectionList *list;if (signal_index < connectionLists->count())list = &connectionLists->at(signal_index);elselist = &connectionLists->allsignals;Qt::HANDLE currentThreadId = QThread::currentThreadId();// 2.在sender的connection列表中遍历do {QObjectPrivate::Connection *c = list->first;if (!c) continue;// We need to check against last here to ensure that signals added// during the signal emission are not emitted in this emission.QObjectPrivate::Connection *last = list->last;do {if (!c->receiver)continue;QObject * const receiver = c->receiver;const bool receiverInSameThread = currentThreadId == receiver->d_func()->threadData->threadId.load();// determine if this connection should be sent immediately or// put into the event queueif ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)|| (c->connectionType == Qt::QueuedConnection)) {queued_activate(sender, signal_index, c, argv ? argv : empty_argv, locker);continue;
#if QT_CONFIG(thread)} else if (c->connectionType == Qt::BlockingQueuedConnection) {if (receiverInSameThread) {qWarning("Qt: Dead lock detected while activating a BlockingQueuedConnection: ""Sender is %s(%p), receiver is %s(%p)",sender->metaObject()->className(), sender,receiver->metaObject()->className(), receiver);}QSemaphore semaphore;QMetaCallEvent *ev = c->isSlotObject ?new QMetaCallEvent(c->slotObj, sender, signal_index, 0, 0, argv ? argv : empty_argv, &semaphore) :new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction, sender, signal_index, 0, 0, argv ? argv : empty_argv, &semaphore);QCoreApplication::postEvent(receiver, ev);locker.unlock();semaphore.acquire();locker.relock();continue;
#endif}QConnectionSenderSwitcher sw;if (receiverInSameThread) {sw.switchSender(receiver, sender, signal_index);}if (c->isSlotObject) {c->slotObj->ref();QScopedPointer<QtPrivate::QSlotObjectBase, QSlotObjectBaseDeleter> obj(c->slotObj);locker.unlock();{Q_TRACE_SCOPE(QMetaObject_activate_slot_functor, obj.data());obj->call(receiver, argv ? argv : empty_argv);}// Make sure the slot object gets destroyed before the mutex is locked again, as the// destructor of the slot object might also lock a mutex from the signalSlotLock() mutex pool,// and that would deadlock if the pool happens to return the same mutex.obj.reset();locker.relock();} else if (c->callFunction && c->method_offset <= receiver->metaObject()->methodOffset()) {//we compare the vtable to make sure we are not in the destructor of the object.const int methodIndex = c->method();const int method_relative = c->method_relative;const auto callFunction = c->callFunction;locker.unlock();if (qt_signal_spy_callback_set.slot_begin_callback != 0)qt_signal_spy_callback_set.slot_begin_callback(receiver, methodIndex, argv ? argv : empty_argv);{Q_TRACE_SCOPE(QMetaObject_activate_slot, receiver, methodIndex);callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv ? argv : empty_argv);}if (qt_signal_spy_callback_set.slot_end_callback != 0)qt_signal_spy_callback_set.slot_end_callback(receiver, methodIndex);locker.relock();} else {const int method = c->method_relative + c->method_offset;locker.unlock();if (qt_signal_spy_callback_set.slot_begin_callback != 0) {qt_signal_spy_callback_set.slot_begin_callback(receiver,method,argv ? argv : empty_argv);}{Q_TRACE_SCOPE(QMetaObject_activate_slot, receiver, method);// 这里元调用metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv); }if (qt_signal_spy_callback_set.slot_end_callback != 0)qt_signal_spy_callback_set.slot_end_callback(receiver, method);locker.relock();}if (connectionLists->orphaned)break;} while (c != last && (c = c->nextConnectionList) != 0);if (connectionLists->orphaned)break;} while (list != &connectionLists->allsignals &&//start over for all signals;((list = &connectionLists->allsignals), true));}if (qt_signal_spy_callback_set.signal_end_callback != 0)qt_signal_spy_callback_set.signal_end_callback(sender, signal_index);
}
// step6. 进入metalcall
int QMetaObject::metacall(QObject *object, Call cl, int idx, void **argv)
{if (object->d_ptr->metaObject)return object->d_ptr->metaObject->metaCall(object, cl, idx, argv);elsereturn object->qt_metacall(cl, idx, argv);
}
// step7. Q_OBJECT扩展,MOC.exe自动生成
int MainWindow::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{_id = QMainWindow::qt_metacall(_c, _id, _a);if (_id < 0)return _id;if (_c == QMetaObject::InvokeMetaMethod) {if (_id < 1)// 这里调用静态元调用qt_static_metacall(this, _c, _id, _a); _id -= 1;} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {if (_id < 1)*reinterpret_cast<int*>(_a[0]) = -1;_id -= 1;}return _id;
}
// step8 Q_OBJECT扩展,MOC.exe自动生成
void MainWindow::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{if (_c == QMetaObject::InvokeMetaMethod) {auto *_t = static_cast<MainWindow *>(_o);Q_UNUSED(_t)switch (_id) {// 这里最终调用槽函数case 0: _t->on_pushButton_clicked(); break; default: ;}}Q_UNUSED(_a);
}

从代码里可以看出Qt源码中大量使用d指针,从上面可以看出Qt元对象系统内维护了sender的连接链表,接下来看下connect函数是如何将信号和槽函数加入链表中去的。

3.观察connect函数实现

打开QObject::connect函数,进入源码。

// step 1 进入connect源码中实现template <typename Func1, typename Func2>static inline typename std::enable_if<QtPrivate::FunctionPointer<Func2>::ArgumentCount == -1, QMetaObject::Connection>::typeconnect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, const QObject *context, Func2 slot,Qt::ConnectionType type = Qt::AutoConnection){typedef QtPrivate::FunctionPointer<Func1> SignalType;const int FunctorArgumentCount = QtPrivate::ComputeFunctorArgumentCount<Func2 , typename SignalType::Arguments>::Value;Q_STATIC_ASSERT_X((FunctorArgumentCount >= 0),"Signal and slot arguments are not compatible.");const int SlotArgumentCount = (FunctorArgumentCount >= 0) ? FunctorArgumentCount : 0;typedef typename QtPrivate::FunctorReturnType<Func2, typename QtPrivate::List_Left<typename SignalType::Arguments, SlotArgumentCount>::Value>::Value SlotReturnType;Q_STATIC_ASSERT_X((QtPrivate::AreArgumentsCompatible<SlotReturnType, typename SignalType::ReturnType>::value),"Return type of the slot is not compatible with the return type of the signal.");Q_STATIC_ASSERT_X(QtPrivate::HasQ_OBJECT_Macro<typename SignalType::Object>::Value,"No Q_OBJECT in the class with the signal");const int *types = nullptr;if (type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)types = QtPrivate::ConnectionTypes<typename SignalType::Arguments>::types();return connectImpl(sender, reinterpret_cast<void **>(&signal), context, nullptr,new QtPrivate::QFunctorSlotObject<Func2, SlotArgumentCount,typename QtPrivate::List_Left<typename SignalType::Arguments, SlotArgumentCount>::Value,typename SignalType::ReturnType>(std::move(slot)),type, types, &SignalType::Object::staticMetaObject);}
//  step2
QMetaObject::Connection QObject::connectImpl(const QObject *sender, void **signal,const QObject *receiver, void **slot,QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type,const int *types, const QMetaObject *senderMetaObject)
{if (!signal) {qWarning("QObject::connect: invalid null parameter");if (slotObj)slotObj->destroyIfLastRef();return QMetaObject::Connection();}int signal_index = -1;void *args[] = { &signal_index, signal };for (; senderMetaObject && signal_index < 0; senderMetaObject = senderMetaObject->superClass()) {senderMetaObject->static_metacall(QMetaObject::IndexOfMethod, 0, args);if (signal_index >= 0 && signal_index < QMetaObjectPrivate::get(senderMetaObject)->signalCount)break;}if (!senderMetaObject) {qWarning("QObject::connect: signal not found in %s", sender->metaObject()->className());slotObj->destroyIfLastRef();return QMetaObject::Connection(0);}signal_index += QMetaObjectPrivate::signalOffset(senderMetaObject);return QObjectPrivate::connectImpl(sender, signal_index, receiver, slot, slotObj, type, types, senderMetaObject);
}
// step2. 进入connectImpl函数
QMetaObject::Connection QObjectPrivate::connectImpl(const QObject *sender, int signal_index,const QObject *receiver, void **slot,QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type,const int *types, const QMetaObject *senderMetaObject)
{if (!sender || !receiver || !slotObj || !senderMetaObject) {const char *senderString = sender ? sender->metaObject()->className(): senderMetaObject ? senderMetaObject->className(): "Unknown";const char *receiverString = receiver ? receiver->metaObject()->className(): "Unknown";qWarning("QObject::connect(%s, %s): invalid null parameter", senderString, receiverString);if (slotObj)slotObj->destroyIfLastRef();return QMetaObject::Connection();}QObject *s = const_cast<QObject *>(sender);QObject *r = const_cast<QObject *>(receiver);QOrderedMutexLocker locker(signalSlotLock(sender),signalSlotLock(receiver));if (type & Qt::UniqueConnection && slot) {QObjectConnectionListVector *connectionLists = QObjectPrivate::get(s)->connectionLists;if (connectionLists && connectionLists->count() > signal_index) {const QObjectPrivate::Connection *c2 =(*connectionLists)[signal_index].first;while (c2) {if (c2->receiver == receiver && c2->isSlotObject && c2->slotObj->compare(slot)) {slotObj->destroyIfLastRef();return QMetaObject::Connection();}c2 = c2->nextConnectionList;}}type = static_cast<Qt::ConnectionType>(type ^ Qt::UniqueConnection);}QScopedPointer<QObjectPrivate::Connection> c(new QObjectPrivate::Connection);c->sender = s;c->signal_index = signal_index;c->receiver = r;c->slotObj = slotObj;c->connectionType = type;c->isSlotObject = true;if (types) {c->argumentTypes.store(types);c->ownArgumentTypes = false;}// 这里获取sender的d指针并调用addConnect函数,将信号索引和connection数据加入链表中QObjectPrivate::get(s)->addConnection(signal_index, c.data());QMetaObject::Connection ret(c.take());locker.unlock();QMetaMethod method = QMetaObjectPrivate::signal(senderMetaObject, signal_index);Q_ASSERT(method.isValid());s->connectNotify(method);return ret;
}
// step3 维护链表实现
void QObjectPrivate::addConnection(int signal, Connection *c)
{Q_ASSERT(c->sender == q_ptr);if (!connectionLists)connectionLists = new QObjectConnectionListVector();if (signal >= connectionLists->count())connectionLists->resize(signal + 1);ConnectionList &connectionList = (*connectionLists)[signal];if (connectionList.last) {connectionList.last->nextConnectionList = c;} else {connectionList.first = c;}connectionList.last = c;cleanConnectionLists();c->prev = &(QObjectPrivate::get(c->receiver)->senders);c->next = *c->prev;*c->prev = c;if (c->next)c->next->prev = &c->next;if (signal < 0) {connectedSignals[0].store(~0);connectedSignals[1].store(~0);} else if (signal < (int)sizeof(connectedSignals) * 8) {connectedSignals[signal >> 5].store(connectedSignals[signal >> 5].load() | (1 << (signal & 0x1f)));}
}

4.总结

上述两段代码摘自Qt5.12.12的源码,粗略查看下信号槽实现机制,对于很多细节完全没有关注,后续有时间应该要更加细致的拜读下。

5.参考资料

d指针、q指针以及Qt元对象系统相关资料可自行bing查询

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

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

相关文章

六十天前端强化训练之第二天CSS选择器与盒模型深度解析

欢迎来到编程星辰海的博客讲解 目录 一、CSS 核心概念 1. 三种引入方式 2. CSS 注释 3. 常见单位系统 二、CSS选择器核心知识 1. 基础选择器类型 2. 组合选择器 3. 伪类选择器&#xff08;部分示例&#xff09; 4. 优先级计算规则 三、盒模型深度解析 1. 标准盒模型图…

【DeepSeek】-macOS本地终端部署后运行DeepSeek如何分析图片

【DeepSeek】-macOS本地终端部署后运行DeepSeek如何分析图片 根据您的需求&#xff0c;目前需要了解以下几个关键点及分步解决方案&#xff1a; --- 一、现状分析 1. Ollama 的限制&#xff1a; - 目前Ollama主要面向文本大模型&#xff0c;原生不支持直接上传/处理图片 …

【音视频】音视频录制、播放原理

一、音视频录制原理 通常&#xff0c;音视频录制的步骤如下图所示&#xff1a; 我们分别从音频和视频开始采样&#xff0c;通过麦克风和摄像头来接受我们的音频信息和图像信息&#xff0c;这通常是同时进行的&#xff0c;不过&#xff0c;通常视频的采集会比音频的采集慢&…

解锁养生密码,拥抱健康生活

在快节奏的现代生活中&#xff0c;养生不再是一种选择&#xff0c;而是我们保持活力、提升生活质量的关键。它不是什么高深莫测的学问&#xff0c;而是一系列融入日常的简单习惯&#xff0c;每一个习惯都在为我们的健康加分。 早晨&#xff0c;当第一缕阳光洒进窗户&#xff0c…

7种内外网数据交换方案全解析 哪种安全、高效、合规?

内外网数据交换方案主要解决了企业跨网络数据传输中的安全、效率与合规性问题。通过采用先进的加密技术、高效的数据传输协议以及严格的审批和审计机制&#xff0c;该方案确保了数据在内外网之间的安全交换&#xff0c;同时提高了传输效率&#xff0c;并满足了企业对数据合规性…

【WSL2】 Ubuntu20.04 GUI图形化界面 VcXsrv ROS noetic Vscode 主机代理 配置

【WSL2】 Ubuntu20.04 GUI图形化界面 VcXsrv ROS noetic Vscode 主机代理 配置 前言整体思路安装 WSL2Windows 环境升级为 WIN11 专业版启用window子系统及虚拟化 安装WSL2通过 Windows 命令提示符安装 WSL安装所需的 Linux 发行版&#xff08;如 Ubuntu 20.04&#xff09;查看…

监听其他音频播放时暂停正在播放的音频

要实现当有其他音频播放时暂停当前音频&#xff0c;你可以使用全局事件总线或 Vuex 来管理音频播放状态。这里我将展示如何使用一个简单的事件总线来实现这个功能。 首先&#xff0c;你需要创建一个事件总线。你可以在项目的一个公共文件中创建它&#xff0c;例如 eventBus.js…

Android数据库SQLite、Room、Realm、MMKV/DataStore、ObjectBox性能比较

Android主流数据库基础特点核心数据库特性与性能对比维度总结 在 Android 开发中&#xff0c;数据库选型直接影响应用的性能、开发效率和可维护性。不同数据库的存储限制&#xff0c;比如常用的SharedPreferences、SQLite、还有基于SQLite封装的greenDao等&#xff0c;这些似乎…

Solidity study

Solidity 开发环境 Solidity编辑器&#xff1a;Solidity编辑器是一种专门用于编写和编辑Solidity代码的编辑器。常用的Solidity编辑器包括Visual Studio Code、Atom和Sublime Text。以太坊开发环境&#xff1a;以太坊开发环境&#xff08;Ethereum Development Environment&am…

【废物研究生零基础刷算法】DFS与递归(一)典型题型

文章目录 跳台阶递归实现指数级枚举递归实现排列型枚举上面两题总结 递归实现组合型枚举P1036选数 跳台阶 思路&#xff1a; 如果 n 1&#xff0c;只有一种走法&#xff08;走 1 级&#xff09;。如果 n 2&#xff0c;有两种走法&#xff08;11 或 2&#xff09;。对于 n &g…

GCC 和 G++的基本使用

GCC 和 G 命令 GCC 和 G 命令GCC&#xff08;GNU C 编译器&#xff09;基本用法常用选项示例 G&#xff08;GNU C 编译器&#xff09;基本用法常用选项示例 GCC 与 G 的区别选择使用 GCC 还是 G C编译流程1. 预处理&#xff08;Preprocessing&#xff09;2. 编译&#xff08;Co…

HWUI 和 Skia

&#x1f4cc; HWUI 和 Skia 的关系 Skia 是 Android 的底层 2D 图形库&#xff0c;提供 CPU 和 GPU 渲染能力&#xff0c;支持 OpenGL、Vulkan、Metal 等后端。HWUI 是 Android UI 组件的 GPU 渲染引擎&#xff0c;主要用于 加速 View、动画、阴影等 UI 元素的绘制。HWUI 依赖…

编写第一个 C++ 程序 – Hello World 示例

“Hello World”程序是学习任何编程语言的第一步&#xff0c;也是您将学习的最直接的程序之一。它是用于演示编码过程如何工作的基本程序。您所要做的就是在输出屏幕上显示 “Hello World”。 C Hello World 程序 下面是在控制台屏幕上打印 “Hello World” 的 C 程序。 // …

【Python量化金融实战】-第1章:Python量化金融概述:1.1量化金融的定义与发展历程

本小节学习建议&#xff1a;掌握Python编程、统计学&#xff08;时间序列分析&#xff09;、金融学基础&#xff08;资产定价理论&#xff09;三者结合&#xff0c;是进入量化领域的核心路径。 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 文章目录 1.1 量化金…

docker部署GPU环境

使用 Docker 部署 GPU 环境涉及到几个关键步骤,以下是详细步骤: 1. 安装 NVIDIA 驱动程序 确保你的系统已经安装了 NVIDIA GPU 驱动。这是使用 GPU 的前提条件。 2. 安装 Docker 和 nvidia-container-toolkit 首先,确保你已经安装了 Docker。然后,安装 NVIDIA Containe…

Pytorch实现之混合成员GAN训练自己的数据集

简介 简介:提出一种新的MMGAN架构,使用常见生成器分布的混合对每个数据分布进行建模。由于生成器在多个真实数据分布之间共享,高度共享的生成器(通过混合权重反映)捕获分布的公共方面,而非共享的生成器捕获独特方面。 论文题目:MIXED MEMBERSHIP GENERATIVE ADVERSARI…

matlab和java混合编程经验分享

最常用的就是可以查到再控制栏deploytool选择library complier打包&#xff0c;但是有问题就是比如果用了外部的求解器比如yalmip或者cplex的话用这个方法会找不到外部的求解器&#xff0c;网上找了很多&#xff0c;基本都大同小异。 后面分享一个亲测有效的打包方法&#xff0…

观成科技:海莲花“PerfSpyRAT”木马加密通信分析

1.概述 在2024年9月中旬至10月&#xff0c;东南亚APT组织“海莲花”通过GitHub发布开源安全工具项目&#xff0c;针对网络安全人员发起了定向攻击。通过对相关攻击活动进行分析&#xff0c;可以将其与一些海莲花的样本关联起来。这些样本的通信数据结构与海莲花此前使用的攻击…

2024-2025 学年广东省职业院校技能大赛 “信息安全管理与评估”赛项 技能测试试卷(一)

2024-2025 学年广东省职业院校技能大赛 “信息安全管理与评估”赛项 技能测试试卷&#xff08;一&#xff09; 第一部分&#xff1a;网络平台搭建与设备安全防护任务书DCRS:DCFW:DCWS:WAF: 第二部分&#xff1a;网络安全事件响应、数字取证调查、应用程序安全任务书任务 1&…

2月25(信息差)

&#x1f30d;四川省人民医院接入DeepSeek 将AI技术应用于看病全流程 &#x1f384;机器人新风口&#xff01;OpenAI押注公司 采用这种新材料 更轻盈耐磨&#xff01;尼龙概念股名单 ✨小米15 Ultra、小米SU7 Ultra定档2月27日 雷军宣布&#xff1a;向超高端进发 1.深夜王炸&…