Android12 显示框架之Transaction----server端

news/2024/9/18 14:57:09/ 标签: android, 嵌入式软件, 图形图像

目录:Android显示终极宝典

上篇讲完了在client端Transaction的内容,最后调用setTransactionState()把所有的参数都交给了surfaceflinger,那么任务就交给server来完成了。本节我们一起接着看看下面的内容。

setTransactionState()

//frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
status_t SurfaceFlinger::setTransactionState(const FrameTimelineInfo& frameTimelineInfo, const Vector<ComposerState>& states,const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId) {ATRACE_CALL();uint32_t permissions =callingThreadHasUnscopedSurfaceFlingerAccess() ? Permission::ACCESS_SURFACE_FLINGER : 0;// Avoid checking for rotation permissions if the caller already has ACCESS_SURFACE_FLINGER// permissions.if ((permissions & Permission::ACCESS_SURFACE_FLINGER) ||callingThreadHasRotateSurfaceFlingerAccess()) {permissions |= Permission::ROTATE_SURFACE_FLINGER;}if (!(permissions & Permission::ACCESS_SURFACE_FLINGER) &&(flags & (eEarlyWakeupStart | eEarlyWakeupEnd))) {ALOGE("Only WindowManager is allowed to use eEarlyWakeup[Start|End] flags");flags &= ~(eEarlyWakeupStart | eEarlyWakeupEnd);}const int64_t postTime = systemTime();IPCThreadState* ipc = IPCThreadState::self();const int originPid = ipc->getCallingPid();const int originUid = ipc->getCallingUid();TransactionState state{frameTimelineInfo,  states,displays,           flags,applyToken,         inputWindowCommands,desiredPresentTime, isAutoTimestamp,uncacheBuffer,      postTime,permissions,        hasListenerCallbacks,listenerCallbacks,  originPid,originUid,          transactionId};// Check for incoming buffer updates and increment the pending buffer count.state.traverseStatesWithBuffers([&](const layer_state_t& state) {mBufferCountTracker.increment(state.surface->localBinder());});queueTransaction(state);// Check the pending state to make sure the transaction is synchronous.if (state.transactionCommittedSignal) {waitForSynchronousTransaction(*state.transactionCommittedSignal);}return NO_ERROR;
}

这段代码主要看queueTransaction(),其他内容大概知道怎么回事就可以了。client传进来的参数会和其他临时创建的变量一起被构建成一个TransactionState,将其送入queueTransaction()基于此来执行后续流程。

queueTransaction()

//frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::queueTransaction(TransactionState& state) {Mutex::Autolock _l(mQueueLock);// If its TransactionQueue already has a pending TransactionState or if it is pendingauto itr = mPendingTransactionQueues.find(state.applyToken);// if this is an animation frame, wait until prior animation frame has// been applied by SFif (state.flags & eAnimation) {while (itr != mPendingTransactionQueues.end()) {status_t err = mTransactionQueueCV.waitRelative(mQueueLock, s2ns(5));if (CC_UNLIKELY(err != NO_ERROR)) {ALOGW_IF(err == TIMED_OUT,"setTransactionState timed out ""waiting for animation frame to apply");break;}itr = mPendingTransactionQueues.find(state.applyToken);}}// Generate a CountDownLatch pending state if this is a synchronous transaction.if ((state.flags & eSynchronous) || state.inputWindowCommands.syncInputWindows) {state.transactionCommittedSignal = std::make_shared<CountDownLatch>((state.inputWindowCommands.syncInputWindows? (CountDownLatch::eSyncInputWindows | CountDownLatch::eSyncTransaction): CountDownLatch::eSyncTransaction));}mTransactionQueue.emplace(state);ATRACE_INT("TransactionQueue", mTransactionQueue.size());const auto schedule = [](uint32_t flags) {if (flags & eEarlyWakeupEnd) return TransactionSchedule::EarlyEnd;if (flags & eEarlyWakeupStart) return TransactionSchedule::EarlyStart;return TransactionSchedule::Late;}(state.flags);setTransactionFlags(eTransactionFlushNeeded, schedule, state.applyToken);
}

这段代码主要看三点:

  • mTransactionQueue.emplace(state):将state存储起来,后面在另一个线程处理。
  • 获取schedule值:这个一般情况比如本地播放等结果为TransactionSchedule::Late,EarlyStart和EarlyEnd则是WMS在切换窗口时设置,这里不考虑这种特殊case。
  • setTransactionFlags():根据flags及mTransactionFlags旧值决定本次是否发起invalidate消息。该函数内的modulateVsync()在当前内容中暂时不关心。
//frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::signalTransaction() {mScheduler->resetIdleTimer();mPowerAdvisor.notifyDisplayUpdateImminent();mEventQueue->invalidate();
}

假设本次执行signalTransaction(),跑完mEventQueue->invalidate()之后,后面的执行流程是个什么样子呢?简单画图会比较直白方便大家理解一些:

具体代码大家可以依据图中连线上的接口名称去看code,这里简单描述下过程:signalTransaction()执行之后,代码逻辑会经有MessageQueue+VSyncDispatch+Timer这三者,最终把Transaction需要处理的信息送达surfaceflinger主线程进行处理。

handleMessageTransaction()

接下来直接看handleMessageTransaction():

//frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
bool SurfaceFlinger::handleMessageTransaction() {ATRACE_CALL();if (getTransactionFlags(eTransactionFlushNeeded)) {flushTransactionQueues();}uint32_t transactionFlags = peekTransactionFlags();bool runHandleTransaction =((transactionFlags & (~eTransactionFlushNeeded)) != 0) || mForceTraversal;if (runHandleTransaction) {handleTransaction(eTransactionMask);}if (transactionFlushNeeded()) {setTransactionFlags(eTransactionFlushNeeded);}return runHandleTransaction;
}

这里注意getTransactionFlags()函数传入的eTransactionFlushNeeded,也就是说之前必须设置过eTransactionFlushNeeded标志位才会真正的执行flushTransactionQueues(),执行完以后还要清除掉mTransactionFlags中的eTransactionFlushNeeded标志位。

flushTransactionQueues()

//frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::flushTransactionQueues() {// to prevent onHandleDestroyed from being called while the lock is held,// we must keep a copy of the transactions (specifically the composer// states) around outside the scope of the lockstd::vector<const TransactionState> transactions;// Layer handles that have transactions with buffers that are ready to be applied.std::unordered_set<sp<IBinder>, ISurfaceComposer::SpHash<IBinder>> bufferLayersReadyToPresent;{Mutex::Autolock _l(mStateLock);{Mutex::Autolock _l(mQueueLock);// Collect transactions from pending transaction queue.auto it = mPendingTransactionQueues.begin();while (it != mPendingTransactionQueues.end()) {auto& [applyToken, transactionQueue] = *it;while (!transactionQueue.empty()) {auto& transaction = transactionQueue.front();if (!transactionIsReadyToBeApplied(transaction.frameTimelineInfo,transaction.isAutoTimestamp,transaction.desiredPresentTime,transaction.originUid, transaction.states,bufferLayersReadyToPresent)) {setTransactionFlags(eTransactionFlushNeeded);break;}transaction.traverseStatesWithBuffers([&](const layer_state_t& state) {bufferLayersReadyToPresent.insert(state.surface);});transactions.emplace_back(std::move(transaction));transactionQueue.pop();}if (transactionQueue.empty()) {it = mPendingTransactionQueues.erase(it);mTransactionQueueCV.broadcast();} else {it = std::next(it, 1);}}// Collect transactions from current transaction queue or queue to pending transactions.// Case 1: push to pending when transactionIsReadyToBeApplied is false.// Case 2: push to pending when there exist a pending queue.// Case 3: others are ready to apply.while (!mTransactionQueue.empty()) {auto& transaction = mTransactionQueue.front();bool pendingTransactions = mPendingTransactionQueues.find(transaction.applyToken) !=mPendingTransactionQueues.end();if (pendingTransactions ||!transactionIsReadyToBeApplied(transaction.frameTimelineInfo,transaction.isAutoTimestamp,transaction.desiredPresentTime,transaction.originUid, transaction.states,bufferLayersReadyToPresent)) {mPendingTransactionQueues[transaction.applyToken].push(std::move(transaction));} else {transaction.traverseStatesWithBuffers([&](const layer_state_t& state) {bufferLayersReadyToPresent.insert(state.surface);});transactions.emplace_back(std::move(transaction));}mTransactionQueue.pop();ATRACE_INT("TransactionQueue", mTransactionQueue.size());}}// Now apply all transactions.for (const auto& transaction : transactions) {applyTransactionState(transaction.frameTimelineInfo, transaction.states,transaction.displays, transaction.flags,transaction.inputWindowCommands, transaction.desiredPresentTime,transaction.isAutoTimestamp, transaction.buffer,transaction.postTime, transaction.permissions,transaction.hasListenerCallbacks, transaction.listenerCallbacks,transaction.originPid, transaction.originUid, transaction.id);if (transaction.transactionCommittedSignal) {mTransactionCommittedSignals.emplace_back(std::move(transaction.transactionCommittedSignal));}}}
}

第一个while循环检查mPendingTransactionQueues中的transaction是否已经准备好被apply,如果是则记录到临时transactions的vector中,否则重新设置eTransactionFlushNeeded。

第二个while循环则是对queueTransaction()中新添加的mTransactionQueue遍历检查是否可以被apply,如果可以则将其移入transactions中,否则放入mPendingTransactionQueues中。

mTransactionQueue数量的变化过程可以在perfetto中直观的看到,它在哪个阶段增加的哪个阶段消耗的一目了然:

两个循环中都使用到了transactionIsReadyToBeApplied()方法来辅助判断是否能被apply,该函数的主体功能就是:通过检查期望显示的时间戳、vsync时间戳是否与帧率同步、acquireFence是否已signaled以及buffer的状态来判断当前事务是否已经准备好。

最后一个for循环则是对上面收集到的transactions开始进行apply。

applyTransactionState()

//frameworks/native/services/surfaceflinger/SurfaceFlinger.cppvoid SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelineInfo,const Vector<ComposerState>& states,const Vector<DisplayState>& displays, uint32_t flags,const InputWindowCommands& inputWindowCommands,const int64_t desiredPresentTime, bool isAutoTimestamp,const client_cache_t& uncacheBuffer,const int64_t postTime, uint32_t permissions,bool hasListenerCallbacks,const std::vector<ListenerCallbacks>& listenerCallbacks,int originPid, int originUid, uint64_t transactionId) {uint32_t transactionFlags = 0;for (const DisplayState& display : displays) {transactionFlags |= setDisplayStateLocked(display);}// start and end registration for listeners w/ no surface so they can get their callback.  Note// that listeners with SurfaceControls will start registration during setClientStateLocked// below.for (const auto& listener : listenerCallbacks) {mTransactionCallbackInvoker.startRegistration(listener);mTransactionCallbackInvoker.endRegistration(listener);}std::unordered_set<ListenerCallbacks, ListenerCallbacksHash> listenerCallbacksWithSurfaces;uint32_t clientStateFlags = 0;for (const ComposerState& state : states) {clientStateFlags |=setClientStateLocked(frameTimelineInfo, state, desiredPresentTime, isAutoTimestamp,postTime, permissions, listenerCallbacksWithSurfaces);if ((flags & eAnimation) && state.state.surface) {if (const auto layer = fromHandle(state.state.surface).promote(); layer) {mScheduler->recordLayerHistory(layer.get(),isAutoTimestamp ? 0 : desiredPresentTime,LayerHistory::LayerUpdateType::AnimationTX);}}}for (const auto& listenerCallback : listenerCallbacksWithSurfaces) {mTransactionCallbackInvoker.endRegistration(listenerCallback);}// If the state doesn't require a traversal and there are callbacks, send them nowif (!(clientStateFlags & eTraversalNeeded) && hasListenerCallbacks) {mTransactionCallbackInvoker.sendCallbacks();}transactionFlags |= clientStateFlags;if (permissions & Permission::ACCESS_SURFACE_FLINGER) {transactionFlags |= addInputWindowCommands(inputWindowCommands);} else if (!inputWindowCommands.empty()) {ALOGE("Only privileged callers are allowed to send input commands.");}if (uncacheBuffer.isValid()) {ClientCache::getInstance().erase(uncacheBuffer);}// If a synchronous transaction is explicitly requested without any changes, force a transaction// anyway. This can be used as a flush mechanism for previous async transactions.// Empty animation transaction can be used to simulate back-pressure, so also force a// transaction for empty animation transactions.if (transactionFlags == 0 &&((flags & eSynchronous) || (flags & eAnimation))) {transactionFlags = eTransactionNeeded;}if (transactionFlags) {if (mInterceptor->isEnabled()) {mInterceptor->saveTransaction(states, mCurrentState.displays, displays, flags,originPid, originUid, transactionId);}// We are on the main thread, we are about to preform a traversal. Clear the traversal bit// so we don't have to wake up again next frame to preform an unnecessary traversal.if (transactionFlags & eTraversalNeeded) {transactionFlags = transactionFlags & (~eTraversalNeeded);mForceTraversal = true;}if (transactionFlags) {setTransactionFlags(transactionFlags);}if (flags & eAnimation) {mAnimTransactionPending = true;}}
}

第一个for循环暂时不用关注,因为一般情况下transaction不会去设置DisplayState。

第二个for循环也不用看,传进来为空。实际的ListenerCallbacks保存在ComposerState的state.listeners中。

第三个for循环才是重点内容:setClientStateLocked()

setClientStateLocked()

这个函数很长很长,这里不贴代码了。主要做了如下的工作:

  • 提取并设置回调机制的必要步骤。
  • 获取handle对应的layer,根据client设置的layer_state_t信息去填充layer的mDrawingState变量,关于layerstack的信息保存在surfaceflinger的mCurrentState中。
  • 根据listener构建CallbackHandle并将它们保存到mDrawingState.callbackHandles以及TransactionCallbackInvoker的mPendingTransactions中。

剩下来的内容都是围绕transactionFlags的标志来决定是否在主线程再发起setTransactionFlags()。

到此,flushTransactionQueues()执行完毕。

handleTransaction()

//frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::handleTransaction(uint32_t transactionFlags) {ATRACE_CALL();// here we keep a copy of the drawing state (that is the state that's// going to be overwritten by handleTransactionLocked()) outside of// mStateLock so that the side-effects of the State assignment// don't happen with mStateLock held (which can cause deadlocks).State drawingState(mDrawingState);Mutex::Autolock _l(mStateLock);mDebugInTransaction = systemTime();// Here we're guaranteed that some transaction flags are set// so we can call handleTransactionLocked() unconditionally.// We call getTransactionFlags(), which will also clear the flags,// with mStateLock held to guarantee that mCurrentState won't change// until the transaction is committed.modulateVsync(&VsyncModulator::onTransactionCommit);transactionFlags = getTransactionFlags(eTransactionMask);handleTransactionLocked(transactionFlags);mDebugInTransaction = 0;// here the transaction has been committed
}

这里会先做一个动作,把mTransactionFlags给清零。然后再进行handleTransactionLocked():

//frameworks/native/services/surfaceflinger/SurfaceFlinger.cppvoid SurfaceFlinger::handleTransactionLocked(uint32_t transactionFlags) {//省略commitTransaction();
}

这里主要看下commitTransaction()这个函数:

//frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::commitTransaction() {ATRACE_CALL();commitTransactionLocked();signalSynchronousTransactions(CountDownLatch::eSyncTransaction);mAnimTransactionPending = false;
}void SurfaceFlinger::commitTransactionLocked() {if (!mLayersPendingRemoval.isEmpty()) {// Notify removed layers now that they can't be drawn fromfor (const auto& l : mLayersPendingRemoval) {recordBufferingStats(l->getName(), l->getOccupancyHistory(true));// Ensure any buffers set to display on any children are released.if (l->isRemovedFromCurrentState()) {l->latchAndReleaseBuffer();}// If the layer has been removed and has no parent, then it will not be reachable// when traversing layers on screen. Add the layer to the offscreenLayers set to// ensure we can copy its current to drawing state.if (!l->getParent()) {mOffscreenLayers.emplace(l.get());}}mLayersPendingRemoval.clear();}// If this transaction is part of a window animation then the next frame// we composite should be considered an animation as well.mAnimCompositionPending = mAnimTransactionPending;mDrawingState = mCurrentState;// clear the "changed" flags in current statemCurrentState.colorMatrixChanged = false;if (mVisibleRegionsDirty) {for (const auto& rootLayer : mDrawingState.layersSortedByZ) {rootLayer->commitChildList();}}commitOffscreenLayers();if (mNumClones > 0) {mDrawingState.traverse([&](Layer* layer) { layer->updateMirrorInfo(); });}
}

这段代码的核心内容是更新绘制状态mCurrentState->mDrawingState,并根据脏区域标记的情况来遍历所有图层提交子图层列表。其他内容则是对已被移除图层的一些处理工作,且将必要的图层加入offscreen layers,然后遍历并处理所有的offscreen layers。

code走到这里,client端传递过来的Transaction的内容在server端已经基本被处理完了,后面的内容则是surfaceflinger根据各个layer的情况去考虑具体的合成及送显情形了。


http://www.ppmy.cn/news/1516871.html

相关文章

学懂C++(四十五 ):深入详解C++ STL 容器:从基础到进阶

目录 1. 向量&#xff08;Vector&#xff09; 概念 特点 核心点 实现 适用场景 代码解析 2. 双端队列&#xff08;Deque&#xff09; 概念 特点 核心点 实现 适用场景 代码解析 3. 列表&#xff08;List&#xff09; 概念 特点 核心点 实现 适用场景 代码…

大模型备案重难点最详细说明【评估测试题+附件】

2024年3月1日&#xff0c;我国通过了《生成式人工智能服务安全基本要求》&#xff08;以下简称《AIGC安全要求》&#xff09;&#xff0c;这是目前我国第一部有关AIGC服务安全性方面的技术性指导文件&#xff0c;对语料安全、模型安全、安全措施、词库/题库要求、安全评估等方面…

设计模式(二):工厂模式

一&#xff0c;什么是工厂模式 工厂模式&#xff08;Factory Pattern&#xff09; 是一种创建型设计模式&#xff0c;它定义了一个用于创建对象的接口&#xff0c;而不需要显式地指定对象所属的具体类。换句话说&#xff0c;工厂模式将对象的实例化过程延迟到子类或其他工厂方…

【论文阅读】NGD-SLAM: Towards Real-Time SLAM for Dynamic Environments without GPU

arxiv上一篇很新的视觉SLAM论文&#xff0c;能够在不使用GPU的情况下进行语义分割的辅助运算。 一、跟踪流程 作为一个语义结合的视觉SLAM&#xff0c;其基本的思路和以前看过的DynaSLAM基本类似&#xff0c;都是依赖语义分割模型对场景中动态的特征点进行剔除&#xff0c;这…

【jvm】栈是否存在垃圾回收

目录 一、栈的特点1.1 栈内存分配1.2 栈的生命周期1.3 垃圾回收不直接涉及 二、堆与栈的区别三、总结 一、栈的特点 1.1 栈内存分配 1.栈内存分配是自动的&#xff0c;不需要程序员手动分配和释放。 2.每当一个方法被调用时&#xff0c;JVM就会在这个线程的栈上创建一个新的栈…

C++ | Leetcode C++题解之第355题设计推特

题目&#xff1a; 题解&#xff1a; class Twitter {struct Node {// 哈希表存储关注人的 Idunordered_set<int> followee;// 用链表存储 tweetIdlist<int> tweet;};// getNewsFeed 检索的推文的上限以及 tweetId 的时间戳int recentMax, time;// tweetId 对应发送…

使用GDIView工具排查GDI对象泄漏案例的若干细节总结

目录 1、查看任务管理器,发现程序中有明显的GDI对象泄漏 2、使用GDIView工具查看发生泄漏的是哪一种GDI对象 3、尝试找到复现问题的方法,缩小排查范围,逐步地找到GDI对象的泄漏点 4、本案例中的相关细节点的思考与总结(有价值的细节点) 4.1、UI界面无法显示的原因分析…

TypeScript 面试题汇总

引言 TypeScript 是一种由微软开发的开源、跨平台的编程语言&#xff0c;它是 JavaScript 的超集&#xff0c;为 JavaScript 添加了静态类型系统和其他高级功能。随着 TypeScript 在前端开发领域的广泛应用&#xff0c;掌握 TypeScript 已经成为很多开发者必备的技能之一。本文…

Clickhouse集群化(六)clickhosue-operator学习

1. Custom Resource元素 apiVersion: "clickhouse.altinity.com/v1" kind: "ClickHouseInstallation" metadata:name: "clickhouse-installation-test" 这是clickhouse operator自定义的资源ClickHouseInstallation 1.1. .spec.defaults spe…

35次8.23(docker02)

#搜索拉取镜像 docker search centos docker pull centos #创建启动容器 docker run -it --namea0 centod:latest echo "abc" #如果容器中没有正在执行的指令&#xff0c;就会exit docker run -it --namea0 cenyos:latest /bin/bash #查看docker进程 docker ps #发现…

SQL,解析 json

Google BigQuery数据库的data表存储了若干多层的Json串&#xff0c;其中一条形如&#xff1a; [{"active":true,"key":"key1","values":[{"active":true,"value":"value1"}]},{"active":tru…

go 系列实现websocket

一、简介 websocket是个二进制协议&#xff0c;需要先通过Http协议进行握手&#xff0c;从而协商完成从Http协议向websocket协议的转换。一旦握手结束&#xff0c;当前的TCP连接后续将采用二进制websocket协议进行双向双工交互&#xff0c;自此与Http协议无关。 二、websocket…

uni-app 手记集。

1、uni-app 是一个使用 Vue.js 开发的前端应用的框架&#xff0c;所以不会Vue.js的小伙伴可以先去看看Vue.js的基础教学。 2、.vue文件结构 <template><div class"container"></div> </template><script type"text/ecmascript-6&q…

未来城市的科技展望

未来城市&#xff0c;‌将是科技与人文深度融合的产物&#xff0c;‌展现出一个全方位智能化、‌绿色生态且可持续发展的全新面貌。‌随着物联网、‌人工智能等技术的飞速发展&#xff0c;‌未来城市的轮廓逐渐清晰&#xff0c;‌它将为我们带来前所未有的生活体验。‌ 在未来…

吴光明为鱼跃集团指明方向 以用户为核心构建发展战略

鱼跃集团创始人吴光明&#xff0c;始终秉持着以用户需求为核心的发展理念&#xff0c;引领企业构建技术与产品的双轮驱动体系。 在他的远见卓识下&#xff0c;鱼跃集团明确了以呼吸治疗解决方案、糖尿病管理及POCT、感染控制为三大核心支柱的战略布局&#xff0c;同时保持家用…

SAP怎么查找系统全部的增强点呢?

1.在已有的BADI查找程序里面有点手无足措的样子&#xff0c;不知道该如何去找增强&#xff01; 2.这个时候刚刚接触系统还不熟悉&#xff0c;系统里面存在了什么增强&#xff0c;这个时候咋办捏&#xff1f;SE38 -SNIF 此时全部的增强点都在这里面啦&#xff01;&#xff01;&…

bitsandbytes使用错误:CUDA Setup failed despite GPU being available

参考:https://huggingface.co/docs/bitsandbytes/main/en/installation 报错信息 ======================

一文了解机器学习顶会ICML 2024的研究热点

对人工智能研究领域前沿方向的跟踪是提高科研能力和制定科研战略的关键。本文通过图文并茂的方式介绍了ICML 2024的研究热点&#xff0c;帮助读者了解和跟踪机器学习和人工智能的前沿研究方向。本推文的作者是许东舟&#xff0c;审校为邱雪和黄星宇。 1 会议介绍 ICML&#x…

java在实际开发中反常识bug

目录 1.背景 2.案例 1.包装类型拆箱导致空指针异常 2.switch传入null,导致空指针异常 3.Arrays.asList添加异常 4.转BigDecimal类型时精度丢失 5.除以0不一定抛异常 6.Steam filter后集合修改,会修改原数据 3.完美&评论 1.背景 这篇博客,将列举本人在实际开发中看…

复现ssrf漏洞

目录 一、pikachu靶场 1、靶场环境&#xff1a; 使用docker拉取&#xff1a; docker run -d -p 8765:80 8023/pikachu-expect:latest 2、使用dict 3、使用file读取文件 二、redis未授权访问 1、源码 2、使用bp探测端口 3、继续使用bp探测172.18.0.2的端口 4、使用go…