新闻详情

新闻详情

首页 / 资讯中心 / 详情

Unity渲染优化:看懂状态切换,把SetPass Calls压下去

发布时间:2026/9/30 7:53:18来源:尧图网络
Unity渲染优化:看懂状态切换,把SetPass Calls压下去
你有过这种经历吗项目做到中后期功能不增不减场景也谈不上多豪华突然一夜之间帧率掉了一半。我遇过最典型的一次一辆拖车上面堆了六十多个“长得一模一样”的货箱美术同学为了调色方便每个箱子都点了“Instantiate”生成了一份独立材质。运行时Profiler一看Draw Call不多不少但“SetPass Calls”飙到了每帧一百多次GPU有一半时间花在了“换笔”而不是“画画”上。这里说的“换笔”就是Unity渲染优化里常被一笔带过、实际却极其昂贵的状态切换State Change。GPU画一个物体不光是“往屏幕上扔几百个三角形”那么简单它必须按照当前设定的完整渲染状态来执行用哪一段顶点数据、配哪个Shader、采样哪几张纹理、按什么方式混合、要不要写深度、模板值是多少……这些状态构成了一组“当前画笔”只要其中任何一项发生变动GPU就必须放下手里还没排完的活先把新的状态装配好。这篇文章我想把这件很多人听了一百遍、却很少真正搞懂的事说透状态切换到底是什么、为什么它比想象的贵、是谁在一帧里不断制造切换以及怎么用Static Batching、GPU Instancing、SRP Batcher三套主流方案把它压下去。整个过程会配合Frame Debugger和Profiler的实操排查适合正在做移动端优化、PC大世界搭建以及被Unity批处理问题反复折磨的开发者、技术美术和经验不算长的项目主程阅读。1. 我们到底在优化什么先把渲染状态说清楚1.1 一次Draw Call不只是一次“绘制”状态切换听着抽象我先举个更贴近生活的类比。把GPU想象成一条全自动的流水线厨房。厨师能同时做好几口锅并行但每一道菜都有自己的配方放多少油混合模式、用哪把刀Shader、用哪种食材纹理、按什么火候深度/模板状态。一道菜做完、换下一道菜的时候如果配方完全一样厨师只需要换个食材丢进去灶台都是热的动作非常快。可一旦配方变了比如从红烧变成清蒸他可能要换锅、洗锅、重新调火候、换一套调料罐这段“换配置”的时间就是状态切换的开销。在Unity和底层图形API的语境里GPU的渲染状态通常分为“通用状态”和“专用状态”。状态类别示例影响面通用状态渲染目标RenderTarget、深度缓冲、视口切换渲染目标时会触发较大的管线排空专用状态当前Shader、顶点布局、纹理绑定、混合/深度/模板设置每次切换都会影响后续Draw Call的执行方式很多老教程会跟你说优化就是“减少Draw Call”这句话不能说是错的但它把因果关系搞反了。我们真正想减少的并不是“调用次数”这个数字而是因为每次绘制前改变状态而被迫付出的“装配成本”。如果十个物体用同一个材质、同一段纹理Draw Call再多GPU也能像流水线一样连续吞下去反过来哪怕每帧只有二三十次Draw Call只要每个物体都绑定了不同的材质和纹理GPU每画一个就要拆一次管线帧率照样会被拖死。1.2 “SetPass Calls”才是核心指标所以在整个渲染优化体系里我从来不看裸的Draw Call数量而是看两个衍生指标Batches和SetPass Calls。Batches引擎交给底层API时按合批情况计算的绘制批次可以粗略理解成“最终提交的Draw Call”数量。SetPass Calls每一帧里GPU真正切换渲染状态主要是Shader/Pass切换的次数。这两个数字的关系在项目优化的不同阶段差别很大。早期场景杂乱Batches和SetPass Calls几乎同步上涨等做了合批之后Batches可能掉得很快但SetPass Calls不一定同步下降——因为有些合批只是把顶点缓冲合并了切换Shader/材质的次数并没有减少。只有SetPass Calls也压下来了才说明状态切换带来的开销真正被控制住了。用Frame Debugger或Profiler的Rendering模块看这两个数字的时候我希望大家聊的不是“我降了多少Batches”而是“我降了多少SetPass Calls”。这就像做性能预算面数当然重要但真正决定腰包的是每帧的装配次数。2. 一次状态切换的隐性成本CPU、驱动层和GPU管线重建2.1 三层视角理解切换开销状态切换的昂贵之处要分三层看Unity引擎层、图形API驱动层、GPU硬件层。Unity每帧渲染时会先把一堆渲染命令整理进命令缓冲再批量提交到底层API。如果一个物体需要切换材质引擎就得在命令流里插入一次“Shader变更”和“状态绑定”的指令。这些指令本身不重但它会打断一部分批处理逻辑——比如原本连续提交的材质相同的命令中间插进另一套状态后后续相同材质的命令也不一定能合并了。也就是说一次状态切换不只是“切换那一下”的费用还会在排序和合批阶段产生连锁反应把本来能合批的批次拆碎。到了驱动层开销更明显。现代图形API普遍采用状态机模型驱动内部要做“差异比较”确认哪些状态确实发生了变化、哪些还能复用。一部分驱动还在CPU侧维护着一套状态缓存状态变更时先查缓存命中则跳过验证不命中就要做一遍完整的参数校验。再往上如果发生渲染目标切换、纹理重新绑定驱动还可能请求CPU和GPU同步等待GPU排完当前的流水线才能继续往外发指令。这一步一旦发生CPU会像高速路上突然踩刹车一样出现明显的stall。GPU端的代价最容易被忽视。GPU拿到一个Draw Call之后要先确认管线上所有阶段是否就绪顶点着色器、光栅化、片段着色器各自的绑定对不对纹理是否已上传、缓存是否命中。状态一变管线里正在执行的并行阶段可能要做无效化处理等于才热好的锅又凉了一半。尤其在不同Shader之间切换时GPU往往需要重新填充着色器单元这段停顿通常比连续绘制几个同材质的物体还要耗时。2.2 为什么“现代API不鼓励频繁切换”这也是为什么DirectX 12 / Vulkan这类现代底层API把很多状态打包成了PSO管线状态对象要求你尽可能把一组完整状态“预编译”好然后用很小的句柄切换。直接的好处是驱动层不再需要在绘制时逐项校验上百个参数GPU硬件也能更快地锁定目标管线状态。你去看VR或者高性能端游的优化清单里面几乎都有一条避免在热渲染路径里创建或切换PSO。Unity的SRP Batcher可以说就是把这种思路搬到了引擎层。它把材质数据集中到一个统一的缓冲区切换材质时不再重配整条管线只更新缓冲区里的数据从而大幅降低每次材质切换的装配开销。这是后面会讲到的重点。3. 谁在制造状态切换材质、纹理、Shader变体和绘制顺序3.1 材质最容易被忽视的“实例爆炸”绝大多数状态切换罪魁祸首不是Mesh而是材质。我翻了太多项目的Frame Debugger发现合批失败的常见原因都是同一个运行时有太多“同一个长相、不同内核”的材质实例。典型场景就是美术在编辑器里给某个Prefab换色右键改了参数后顺手把整个Prefab复制了五六十份。每个实例都持有一份自己的材质其实它们原本是同一个Material Asset。运行时你看到的会是五六十份材质每份还可能连纹理都不同。GPU每画一个箱子都要重新装配请问怎么顶得住避免材质实例爆炸的第一原则不要到处改renderer.material。在Unity里拿材质推荐用.sharedMaterial获取共享材质如果你只是想让某个实例偏色、换金属度正确的做法是MaterialPropertyBlock。它能让同材质下不同实例的渲染参数保持差异而不用真正克隆出新的材质资源。如果你能用代码给个示例会更直观。通常我会在最开始写一个公共的初始化方法把差异数据的传递全部收敛到PropertyBlock上private static readonly int ColorProperty Shader.PropertyToID(_Color); var block new MaterialPropertyBlock(); renderer.GetPropertyBlock(block); block.SetColor(ColorProperty, targetColor); renderer.SetPropertyBlock(block);千万不要在循环里写renderer.material.color ...。material属性每次访问都会让引擎复制一份材质实例循环五十次场景里就多五十个状态元凶。这个坑我见过太多团队掉进去而且往往发生在完全不自知的情况下。3.2 纹理换一张图可能比你想的贵很多人以为“纹理切换”就是换一张图片那么轻巧。实际上纹理绑定包括几件并不轻的事上传纹理数据、绑定采样器状态、设置索引/层/Mip Bias、挂到对应着色器阶段。绑定一旦切换GPU的纹理缓存命中率也会受影响尤其当两批物体分别使用两张完全不同、在内存中不连续的贴图时缓存基本等于重新热身。这也是“图集Atlas”和“纹理合批”的核心用意让几十个小对象共享一张大图这样它们即使每个都有自己的UV区域仍能挂到同一条纹理状态上整批绘制的过程中根本不需要切图。我见过一个很典型的移动端项目UI上每个按钮都单独一张贴图一个界面一百多个控件每帧产生上百次纹理切换。后来美术把同屏控件全部打到一个1024图集里Draw Call没怎么变但SetPass Calls从一百多降到了二十几帧率立刻稳了下来。类似的事放到3D场景也一样不要把同一区域内风格相近的物件拆到七八张完全不同的Albedo贴图里。3.3 Shader变体和Pass状态切换的“开关”材质一样不代表状态一样。同一个Shader里的关键词Keyword、多个Pass、不同LightMode都可能在底层解开成完全不同的管线状态。先说Pass。通常我们写Shader会写多个Pass比如描边、镜面反射、后处理效果。每个Pass对GPU来说都是一套独立的管线状态。而且Unity会在渲染流程中穿插不同LightMode的Pass如ShadowCaster、DepthOnly、ForwardBase等这些Pass之间切换同样算状态切换。一个物体哪怕材质完全相同只要跑两个PassGPU也要停两次。再说关键词。我们用#pragma multi_compile开启雾效、描边、溶解这类开关时如果没做变体剔除Variant Stripping最终生成的Shader变体数量会非常可观。每切一个变体GPU可能就要重新装配一份状态。常规做法是把多个变体尽量拆成运行时统一判断的常数减少真正需要在同帧里切换的关键词维度然后在Project Settings里剔除掉用不到的组合。3.4 绘制顺序你手里的排序权大部分新手不知道Unity的批处理对“顺序”特别敏感。同一个材质的一批物体中间只要插进一个不同材质的物体就会断成两批。因此渲染顺序的规划是减少状态切换里成本最低也最容易立刻生效的手段。在Editor里Unity默认按渲染队列Render Queue、深度排序、材质ID、距离排序决定绘制顺序。到了自写渲染管线你可以把Renderer.SortingLayer和SortingOrder用在特定层级的控制上更彻底的办法是拿Camera的opaqueSortMode来控制不透明物体的排序优先级比如按材质优先、距离次之让同材质的物件尽可能连续绘制。一个在强耦合项目里很实用的经验把同屏物体的渲染顺序分成“材质组”——先画同一套材质的大地面再画同一套材质的墙体再画同一套材质的角色。只要每组内部别乱插即使不是同材质组间切换次数也相对可控。这块看起来没有技术含量收益却往往立竿见影属于典型的“先排序再合批”。4. 减少状态切换的三种武器Static Batching、GPU Instancing与SRP Batcher4.1 Static Batching把同一状态焊死静态合批是解决“不可移动物体”状态切换的办法之一。原理不复杂把场景里所有标记为Static且材质相同的Mesh在构建时合并进一个大Mesh绘制时只需提交一次Draw Call和一次状态绑定。启用方式很简单选中GameObject后在Inspector右上把Static开关打开也可以代码里设置gameObject.isStatic true。要注意的是编辑器里运行和真机发布的合批效果并不完全一样编辑器下Unity会在内存里动态组合游戏包里则会在构建阶段预合并。两者的渲染结果基本一致但内存占用和预处理时间可能有差异。Static Batching的代价是内存。为每个标记静态的物体做一个合并网格的副本意味着其顶点数据会被复制一份到合并结果中而且合并后的网格不能做逐物体的局部变换。所以别把“所有东西都勾Static”当成万能解药。我的经验是地形、墙体、道路这类永远不会动的物件适合静态合批可以破坏倒塌的箱子、能开合的柜门这类动态物体就不应该参与否则一动它整个合并组就要切出来重画反而更亏。4.2 GPU Instancing动态重复物体的最优解如果物体很多且重复度高比如草、石头、人群靠静态合批是不现实的——它们往往需要动或者数量大到根本无法合并成一个渲染单位。这时候用GPU Instancing。GPU Instancing的核心是把“相同Mesh 相同材质 相同Shader”的多个实例合并进一次Draw Call每个实例之间的差异位置、旋转、缩放、颜色等作为实例数据传给GPU由GPU根据实例ID分别执行。由于管线状态完全一致它在减少状态切换方面的收益非常高。实操时需要注意GPU Instancing不是一个勾选就能自动起效的功能首先需要在材质Inspector的顶部勾选Enable GPU Instancing。如果Shader不支持实例化这个按钮会变灰。其次写自定义Shader时要引入UNITY_INSTANCING_BUFFER_START/END之类的宏把每实例属性如_Color打包到实例化常量块里。第三如果实例之间确实有不同的颜色、缩放不要直接改共享材质用MaterialPropertyBlock给渲染器传差异数据。为什么这里反复强调MaterialPropertyBlock因为如果直接复制材质你就又回到了“材质实例爆炸”的怪圈Instancing层面的合批也会被打断。我自己踩过一回一个跑酷项目的路面和护栏材质是共享的代码却给每个预制体set了一个_Color属性的克隆材质结果GPU Instancing根本没生效Frame Debugger全部碎成了单Draw Call。改成PropertyBlock之后Batch数立刻降了一个数量级。4.3 SRP BatcherURP/HDRP下的主流强援SRP Batcher是Unity在Scriptable Render Pipeline时代提供的批处理机制目标是直接降低“材质切换”时最重的那些开销。它的实现思路跟PSO类似把材质属性集中到一块名为UnityPerMaterial的CBUFFER里。在切换共享Shader但参数不同的材质时SRP Batcher不需要重新装配几乎所有状态只需要更新数据缓冲即可管线主体保留着原样。启用很简单选中URP/HDRP的Asset打开SRP Batcher开关。之后在Frame Debugger里你能看到合批条目被标注成“SRP Batch”。但要注意几个常见坑自定义Shader如果不遵守CBUFFER约定SRP Batcher会自动跳过它且不报错。如果你在Shader里用了内置管线的_MainTex/_Color这类变量而不放在CBUFFER_START(UnityPerMaterial)内合批可能失效。出现在Properties里但没进CBUFFER的变量会变成单独的新UV/属性通道状态照样要切换。如果你的场景里本来就有大量不同ShaderSRP Batcher并不会帮你解决它们之间的切换它擅长的是“同Shader不同参数”这一维度的优化。所以一套下来优先级一般是这样先保证共享材质再让共享Shader的数量尽量少然后用PropertyBlock做实例差异最后才用SRP Batcher收尾。在URP项目里SRP Batcher和GPU Instancing是可以叠加的前者管“材质间切换”后者管“同材质多实例”两者合在一起效果最好。4.4 三个方案怎么选一张表说话方案解决的切换类型适用对象主要代价/限制Static Batching同材质物体的“多次装配”合并永不移动的静态物体如地形、墙体内存翻倍、不能拖动、构建时间增加GPU Instancing同材质同Mesh多实例重复出现的动态物体如草、石头、人群Shader需支持、网格不能混合、PropertyBlock注意写法SRP Batcher同Shader不同材质时的高开销切换URP/HDRP项目中的材质内部属性切换仅适配SRP、Shader要符合CBUFFER约定在做一个大型场景时我不建议一开始就三个同时上。正确的步骤是先看看哪段路径上状态切换最密集然后按切换类型选一个主方案。大多数项目的瓶颈集中在动态小物件重复度高或静态烘焙物体的材质杂乱你只要找准主因往往一个方案就能解决大半。5. 用Frame Debugger和Profiler定位真凶一组完整排查链路5.1 上手打开Frame Debugger逐层拆解状态切换的问题光靠猜是永远猜不出来的。我最常用的工具组合是Frame Debugger Profiler的Rendering模块。在编辑器里Window Analysis Frame Debugger点Enable。左侧出现每一帧的绘制事件树。逐级往下点可以看每个Draw Call之前的“绑定”记录以及它合批前的位置。关键是右侧的“Batch Break Cause”合批中断原因。它会直接告诉你Different Material、Different Mesh、Different Shader、Transform change等每一句都是状态切换的直接证据。5.2 实测案例如何从45次SetPass压到9次我放一个最近在优化中实际跑过的案例参数可以给你做参考。场景很简单一片铺满草地的野外地块上面有三种箱子、两种地面、若干角色、一堆可拾取的小药瓶。第一版Frame Debugger数据Batches约420SetPass Calls约45帧耗时平均14ms肉眼可见的卡顿排查的第一刀是从Frame Debugger里把所有Different Material的断点数了一遍发现其中大量是同一份“箱子材质”被实例化出的几十个副本。回去翻代码确认是某段初始化逻辑里用GetComponentRenderer().material.color ...改颜色造成的。把这里的.material改成MaterialPropertyBlock之后Batches约210SetPass Calls约31第二刀场景里的小药瓶有六十多个网格一样、材质一样但没有任何合批痕迹。检查后发现它们的Shader没启用GPU Instancing的关键词且材质上的Enable GPU Instancing没勾。勾上、清理掉多余材质后Batches约60SetPass Calls约12第三刀把整个渲染管线切到URP开启SRP Batcher再把地块静态物件的Static勾选补上Batches约25SetPass Calls约6帧耗时降到大约5ms30帧稳定毫无压力。这个例子不是说每个人都能复现同样的数字而是想告诉你状态切换是可以被逐层拆掉的且拆的顺序非常重要。先查材质实例爆炸再查Shader变体和Instancing开关最后考虑静态合批和SRP Batcher——一个萝卜一个坑按这个顺序走最不容易做无用功。5.3 别被编辑器骗了真机数据才可信Frame Debugger在编辑器里是逐帧回放的利器但它并不代表真机实际运行的数字。编辑器下的GPU和内存行为与真机差得远尤其是移动端CPU侧的状态切换开销、GPU PSO缓存行为都不一致。所以我的习惯是先用Frame Debugger在编辑器里定“原因类型”再真机上用Profiler抓Rendering.SetPass Calls、Draw Calls和GPU时间线做二次确认。真机采样时还要留意一个问题Unity的Profiler默认以脚本生命周期为分帧单位GPU时间并不总是和脚本帧完全对齐。如果需要更精确的数据建议打开Deep Profiling或者在Player Settings里把Profiler连接和录制选项调整干净保证采样数据尽可能稳定避免误判。6. 把优化固化到项目规范材质、纹理、Shader与团队习惯6.1 材质规范从源头掐死实例爆炸优化的尽头是规范没有规范的技术优化早晚会被新加的需求打回原形。我在团队里最常强调的三条材质铁律是运行时禁止直接给renderer.material赋值或修改属性统一走MaterialPropertyBlock。每个资源接入时检查材质引用是否落在公共材质库内不在库内的材质必须写明理由。同一资源的所有变体尽量合并成一张材质并使用参数区分而不是复制出三五个Material Asset。这三条听着机械却是最容易在半年后救你于水火的东西。一个项目到后期性能问题有一半以上不是开发时的技术选型错误而是平时看似无害的“临时改一下”积累出来的。6.2 纹理与图集同局面料能共则共纹理层面的规范核心是“同屏同组共图集”把同场景、同区域、同风格的小物件贴图尽量打进同一张图集避免同一批合批对象里出现两张完全不相关的贴图。图集不是越大越好还要考虑内存占用、Mipmap开销和UV边界问题。移动端常用10242048上限特殊大纹理单独评估。不要为了省事把UI和3D物件混进同一张图集它们的过滤、压缩和渲染队列要求完全不同。顺带提一句带透明通道的图集要特别留意Alpha slice和padding问题。图集打得太满相邻两张子图在某些GPU的采样插值下会出现半透明的“出血边”反而让你不得不为个别物件单独拆图状态切换又回来了。留足pad区域一次做对后面省心。6.3 Shader规范少关键字少Pass兼容SRPShader层面的关键动作包括在上线前用Shader Variant Stripping把不需要的multi_compile组合剔除掉能明显减少运行时的变体选择成本。自定义Shader统一按URP/HDRP的CBUFFER规范书写确保SRP Batcher能覆盖到你手上的每一个材质。少用“为了某个小功能而多写一个Pass”的方案能合并到主Pass的尽量合并。这里我想多提醒一句很多人写自定义Shader时只关心画面效果不看Frame Debugger里这个材质有没有被打上“NOT SRP BATCHABLE”的标签。这个标签一旦出现说明你前面的所有批处理努力对这个材质全部失效。养成写完Shader就打开Frame Debugger看一眼的习惯比事后追责省十倍时间。6.4 团队机制性能预算不是口号最后从团队角度说说怎样让状态切换优化长期有效。像做内存预算一样做渲染预算定出每帧SetPass Calls的目标值比如移动端要求60FPS时SetPass Calls不高于15次可放宽场景除外然后写进项目文档每周检查一次。真机性能测试不要只在发布前做建议在CI里或用一个定时任务跑固定关卡采集Batches、SetPass Calls、帧耗时异常即报警。新场景评审时技术负责人至少要看一眼Frame Debugger确认所有预制体没有共享材质被复制、没有私自启用多Pass、没有明显冗余的纹理切换。这些机制都不难难的是坚持。但经历过一两次“上线前两周被逼着重写材质管理”的团队自然会明白规范的价值。最后说一点个人的体会。大概三年前我接手一个中大型模拟经营项目时对状态切换还没有现在这么敏感。后来有次接了个2D UI渲染混乱的任务在Frame Debugger里连续翻了三天突然意识到很多时候我们不是缺优化技巧而是缺一种“把问题按状态归类”的直觉。现在只要进入任何一个Unity项目我第一件事先打开Frame Debugger看Batches和SetPass Calls的比值一旦SetPass Calls明显高于材质数量基本就能断定存在某种状态闪变——再来回定位往往两三刀就能解决。希望这篇内容也能帮你少走一段弯路把自己项目里那些“看不见的换笔”真正拎出来。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

电力系统暂态能量函数法:理论、实现与工程避坑 2026/9/30 8:59:54

电力系统暂态能量函数法:理论、实现与工程避坑

简介:电力系统暂态能量函数法暂态稳定分析学习教案PPT,面向电力系统专业高年级本科生、研究生及电网稳定分析技术人员,系统讲解基于暂态能量函数的稳定分析原理与应用方法。资源包内含1个PPT课件,约1.23MB,便于直接用于…

阅读更多 →
Laravel框架入门与实践:路由、Eloquent ORM与中间件核心指南 2026/9/30 8:59:54

Laravel框架入门与实践:路由、Eloquent ORM与中间件核心指南

先说个结论:Laravel 是目前 PHP 生态里综合体验最好的现代框架,没有之一。如果你刚入行 PHP 还在纠结用原生写还是用 ThinkPHP,又或者从其他语言转过来想找一套“正规军”工具链,Laravel 几乎就是标准答案。它内置了路由、ORM、队…

阅读更多 →
【2018-11-06】【转】git flow的使用 2026/9/30 8:59:54

【2018-11-06】【转】git flow的使用

[历史归档] 本文原发布于 cstriker1407.info 个人博客,内容为历史存档,仅供参考。 发布时间: 2018-11-06 | 标题:【转】git flow的使用 | 分类: 编程 | 标签: git …

阅读更多 →
磁盘调度底层原理:从寻道时间到Linux内核调度器 2026/9/30 8:59:54

磁盘调度底层原理:从寻道时间到Linux内核调度器

1. 这不是“抄答案”,而是打通磁盘管理底层逻辑的实战拆解 你搜到“计算机操作系统第四版第八章磁盘存储器的管理—课后习题答案”,点开前心里是不是已经预判了:一堆公式、几个调度算法名称、几行伪代码,再加个“综上所述”就完事…

阅读更多 →
HTML5语义标签指南:从div到SEO与无障碍的最佳实践 2026/9/30 8:59:54

HTML5语义标签指南:从div到SEO与无障碍的最佳实践

1. 语义标签到底解决了什么问题先抛个场景&#xff1a;你随手拿起一个三年前的老网页&#xff0c;按下F12看它的HTML结构&#xff0c;大概率会看到满屏的<div>套<div>&#xff0c;从顶部导航到侧边栏&#xff0c;从文章正文到页面底部版权信息&#xff0c;清一色全…

阅读更多 →
【2018-10-04】【转】SSH的2种验证方式 2026/9/30 8:59:46

【2018-10-04】【转】SSH的2种验证方式

[历史归档] 本文原发布于 cstriker1407.info 个人博客&#xff0c;内容为历史存档&#xff0c;仅供参考。 发布时间&#xff1a; 2018-10-04 &#xff5c; 标题&#xff1a;【转】SSH的2种验证方式 &#xff5c; 分类&#xff1a; 编程 &#xff5c; 标签&#xff1a; SS…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉