当前位置: 首页 > article >正文

C# MQTT性能优化:工业级高可靠低带宽实战指南

上个月给某汽车零部件厂做产线改造差点栽在MQTT上。现场环境你懂的几百个传感器同时发数据带宽只有可怜的2Mbps还时不时断网。一开始用的是网上随便找的MQTT客户端代码结果上线第一天就炸了。消息延迟最高到了30秒服务器CPU直接干到100%更要命的是关键数据还丢了好几包。客户那边的生产经理脸都绿了指着我鼻子说“威哥你这系统要是再出问题我们整条线都得停”我当时压力山大连续熬了三个通宵把MQTT协议从里到外扒了个遍又把客户端代码重构了三遍终于把问题解决了。现在系统稳定运行了一个多月带宽占用降到了原来的1/5消息延迟控制在100ms以内再也没丢过一包数据。今天就把我踩过的坑和总结出来的优化技巧分享给大家都是实打实的工业级实战经验保证你看完就能用。先搞清楚你的MQTT为什么慢很多人一上来就瞎优化改这个参数调那个配置结果越改越乱。我告诉你MQTT性能问题90%都出在这三个地方连接管理混乱频繁重连导致服务器压力过大消息设计不合理大量冗余数据占用带宽客户端线程模型有问题高并发下直接卡死先给大家看一张我画的MQTT通信性能瓶颈分析图一目了然。我当时就是犯了这个错误以为只要用了MQTTnet这个库就万事大吉了结果根本没考虑工业现场的特殊情况。几百个客户端同时连接每个客户端每秒发10条消息每条消息几十KB你算算这带宽得多大更别说还有心跳包、确认包这些开销。连接层优化让连接稳如狗连接是一切的基础连接都不稳定谈什么性能心跳机制的正确打开方式很多人设置心跳都是随便写个30秒、60秒这其实是大错特错。心跳间隔不是越小越好也不是越大越好。太小了会增加带宽占用太大了又不能及时发现连接断开。我给大家一个经验公式心跳间隔 网络平均延迟 × 3比如现场网络平均延迟是50ms那心跳间隔就设为150ms不对不对我是说如果网络平均延迟是1秒那心跳间隔就设为3秒。哦对了还有一个更重要的参数超时时间。超时时间一定要大于心跳间隔的1.5倍否则会出现误判。我之前就是把超时时间设成了和心跳间隔一样结果网络稍微有点波动就断开重连服务器直接被打崩。// 错误的写法varoptionsnewMqttClientOptionsBuilder().WithTcpServer(192.168.1.100,1883).WithKeepAlivePeriod(TimeSpan.FromSeconds(30)).Build();// 正确的写法varoptionsnewMqttClientOptionsBuilder().WithTcpServer(192.168.1.100,1883).WithKeepAlivePeriod(TimeSpan.FromSeconds(30)).WithTimeout(TimeSpan.FromSeconds(45))// 超时时间大于心跳间隔1.5倍.Build();智能重连机制MQTTnet自带的重连机制其实很垃圾就是简单的每隔几秒重连一次。在网络不稳定的情况下这会导致大量的连接请求同时涌向服务器形成连接风暴。我自己写了一个指数退避重连算法效果非常好。privateint_reconnectAttempts0;privatereadonlyRandom_randomnewRandom();privateasyncTaskReconnectAsync(){if(_mqttClient.IsConnected)return;// 指数退避 随机抖动避免连接风暴vardelayMath.Min(1000*Math.Pow(2,_reconnectAttempts),30000);delay_random.Next(0,1000);awaitTask.Delay((int)delay);try{await_mqttClient.ConnectAsync(_mqttClientOptions);_reconnectAttempts0;// 重连成功重置计数器}catch{_reconnectAttempts;// 最多重试10次然后报警if(_reconnectAttempts10){// 发送报警通知AlertService.Instance.SendAlert(MQTT连接失败已重试10次);}}}这个算法的核心是重连间隔会随着失败次数指数增长同时加入随机抖动避免多个客户端同时重连。TLS优化如果你的MQTT通信需要加密那TLS的性能开销绝对不能忽视。我测试过开启TLS 1.2会让消息传输延迟增加30%-50%CPU占用也会明显上升。这里有几个优化技巧优先使用TLS 1.3比TLS 1.2快很多禁用不必要的密码套件启用会话恢复机制varoptionsnewMqttClientOptionsBuilder().WithTcpServer(192.168.1.100,8883).WithTlsOptions(o{o.UseTls(true);o.SslProtocolSslProtocols.Tls13;// 优先使用TLS 1.3o.AllowUntrustedCertificatesfalse;o.IgnoreCertificateChainErrorsfalse;o.IgnoreCertificateRevocationErrorsfalse;}).Build();消息层优化把带宽用到极致这才是低带宽优化的核心。很多人根本不关心消息大小随便把一个大对象序列化成JSON就发出去了结果带宽直接被占满。QoS级别选择的艺术MQTT有三个QoS级别0、1、2。很多人图省事全部用QoS 2以为这样最可靠。大错特错QoS 2的开销是QoS 0的4倍以上而且会增加消息延迟。我给大家一个明确的选择标准QoS 0非关键数据比如传感器的实时温度、湿度QoS 1重要数据比如设备状态变化、报警信息QoS 2极其重要的数据比如控制指令、交易信息在我那个产线项目里90%的传感器数据都用QoS 0只有报警和控制指令用QoS 1完全没有用QoS 2的地方。这样一来带宽占用直接降了一半。消息压缩立竿见影的效果如果你的消息体比较大压缩绝对是性价比最高的优化手段。我测试过JSON消息用Gzip压缩通常能达到5:1的压缩比Protobuf消息也能达到2:1左右。publicstaticbyte[]Compress(byte[]data){usingvaroutputStreamnewMemoryStream();usingvargzipStreamnewGZipStream(outputStream,CompressionLevel.Optimal);gzipStream.Write(data,0,data.Length);gzipStream.Close();returnoutputStream.ToArray();}publicstaticbyte[]Decompress(byte[]data){usingvarinputStreamnewMemoryStream(data);usingvaroutputStreamnewMemoryStream();usingvargzipStreamnewGZipStream(inputStream,CompressionMode.Decompress);gzipStream.CopyTo(outputStream);returnoutputStream.ToArray();}注意只有当消息体大于1KB时压缩才有意义。太小的消息压缩后反而会变大。批量发送减少协议开销MQTT协议本身有固定的头部开销每条消息至少2个字节。如果你有很多小消息要发批量发送能显著减少协议开销。比如原来每秒发10条100字节的消息总大小是10×(2100)1020字节。如果把这10条消息合并成一条发送总大小是210×1001002字节节省了18字节。别小看这18字节几百个客户端加起来就是好几KB在低带宽环境下非常可观。privatereadonlyQueuebyte[]_messageQueuenewQueuebyte[]();privatereadonlyobject_locknewobject();privateTimer_batchTimer;publicvoidEnqueueMessage(byte[]message){lock(_lock){_messageQueue.Enqueue(message);}}privateasyncvoidBatchSendCallback(objectstate){Listbyte[]messagesToSend;lock(_lock){if(_messageQueue.Count0)return;messagesToSend_messageQueue.ToList();_messageQueue.Clear();}// 合并消息varmergedMessageMergeMessages(messagesToSend);// 发送合并后的消息await_mqttClient.PublishAsync(sensor/batch,mergedMessage,MqttQualityOfServiceLevel.AtMostOnce);}我一般设置批量发送间隔为100ms这样既能减少协议开销又不会增加太多延迟。二进制序列化比JSON快10倍这是我最推荐的优化手段没有之一。JSON虽然方便但序列化和反序列化速度慢而且体积大。在工业场景下我强烈推荐使用Protobuf或者MessagePack。我做过一个对比测试同一个对象JSON序列化120字节耗时1msProtobuf序列化32字节耗时0.1msMessagePack序列化28字节耗时0.08ms差距就是这么大// 使用MessagePack序列化publicstaticbyte[]SerializeT(Tobj){returnMessagePackSerializer.Serialize(obj);}publicstaticTDeserializeT(byte[]data){returnMessagePackSerializer.DeserializeT(data);}客户端层优化榨干C#的性能很多人不知道MQTT客户端本身的性能也会成为瓶颈。特别是在高并发场景下如果客户端的线程模型设计不合理很容易出现消息堆积、内存泄漏等问题。异步处理别阻塞主线程MQTTnet是基于异步的所以你的消息处理代码也必须是异步的。千万不要在消息处理回调里写同步代码更不要做耗时操作。// 错误的写法_mqttClient.ApplicationMessageReceivedAsynce{// 耗时操作会阻塞MQTT客户端的线程ProcessMessageSync(e.ApplicationMessage);returnTask.CompletedTask;};// 正确的写法_mqttClient.ApplicationMessageReceivedAsyncasynce{// 异步处理不会阻塞MQTT客户端的线程awaitProcessMessageAsync(e.ApplicationMessage);};如果你的消息处理确实很耗时应该把它放到单独的线程池里处理。privatereadonlyChannelMqttApplicationMessage_messageChannelChannel.CreateUnboundedMqttApplicationMessage();publicasyncTaskStartProcessingAsync(){_mqttClient.ApplicationMessageReceivedAsynce{_messageChannel.Writer.TryWrite(e.ApplicationMessage);returnTask.CompletedTask;};// 启动多个消费者线程处理消息for(inti0;iEnvironment.ProcessorCount;i){_Task.Run(async(){awaitforeach(varmessagein_messageChannel.Reader.ReadAllAsync()){awaitProcessMessageAsync(message);}});}}这里我用了System.Threading.Channels这是.NET Core 3.0引入的一个高性能通道比ConcurrentQueue好用多了。内存管理避免GC频繁回收在高并发场景下频繁的GC回收会导致严重的性能问题。MQTT客户端会频繁地创建和销毁字节数组这是GC的重灾区。这里有几个优化技巧使用ArrayPool租用字节数组避免不必要的内存拷贝使用Memory和Span处理数据publicasyncTaskPublishAsync(stringtopic,byte[]payload,MqttQualityOfServiceLevelqos){// 从ArrayPool租用字节数组varbufferArrayPoolbyte.Shared.Rent(payload.Length);try{Buffer.BlockCopy(payload,0,buffer,0,payload.Length);varmessagenewMqttApplicationMessageBuilder().WithTopic(topic).WithPayload(buffer.AsMemory(0,payload.Length)).WithQualityOfServiceLevel(qos).Build();await_mqttClient.PublishAsync(message);}finally{// 归还字节数组ArrayPoolbyte.Shared.Return(buffer);}}我测试过使用ArrayPool后GC回收次数减少了80%以上系统运行更加平稳。限流机制防止消息雪崩如果服务器出现问题或者网络突然中断客户端会积累大量的待发送消息。当网络恢复时这些消息会同时涌向服务器导致服务器崩溃。所以客户端必须有限流机制。privatereadonlySemaphoreSlim_publishSemaphorenewSemaphoreSlim(10);// 最多同时发送10条消息publicasyncTaskPublishAsync(stringtopic,byte[]payload,MqttQualityOfServiceLevelqos){await_publishSemaphore.WaitAsync();try{varmessagenewMqttApplicationMessageBuilder().WithTopic(topic).WithPayload(payload).WithQualityOfServiceLevel(qos).Build();await_mqttClient.PublishAsync(message);}finally{_publishSemaphore.Release();}}这个信号量限流机制简单有效能防止客户端在短时间内发送大量消息。高可靠保障关键数据绝不丢失在工业场景下数据丢失是不可接受的。哪怕网络断了几个小时恢复后也必须把所有丢失的数据补传上去。本地消息持久化这是最关键的一步。所有待发送的消息都必须先持久化到本地磁盘然后再发送。我用SQLite做本地持久化简单可靠。publicasyncTaskEnqueueMessageAsync(stringtopic,byte[]payload,MqttQualityOfServiceLevelqos){// 先保存到数据库varmessagenewPendingMessage{Topictopic,Payloadpayload,Qos(int)qos,CreatedAtDateTime.Now};await_dbContext.PendingMessages.AddAsync(message);await_dbContext.SaveChangesAsync();// 然后尝试发送_TrySendPendingMessagesAsync();}privateasyncTaskTrySendPendingMessagesAsync(){if(!_mqttClient.IsConnected)return;varpendingMessagesawait_dbContext.PendingMessages.OrderBy(mm.CreatedAt).Take(100).ToListAsync();foreach(varmessageinpendingMessages){try{await_mqttClient.PublishAsync(message.Topic,message.Payload,(MqttQualityOfServiceLevel)message.Qos);// 发送成功从数据库删除_dbContext.PendingMessages.Remove(message);await_dbContext.SaveChangesAsync();}catch{// 发送失败下次再试break;}}}这样一来哪怕程序崩溃或者设备断电重启后也能从数据库里读取未发送的消息继续发送。消息去重QoS 1和QoS 2都可能导致消息重复所以服务端必须有消息去重机制。最简单的方法是给每条消息加一个唯一ID服务端记录已经处理过的消息ID。publicasyncTaskEnqueueMessageAsync(stringtopic,byte[]payload,MqttQualityOfServiceLevelqos){varmessageIdGuid.NewGuid().ToString();// 把消息ID加到消息头里varmessagenewMqttApplicationMessageBuilder().WithTopic(topic).WithPayload(payload).WithQualityOfServiceLevel(qos).WithUserProperty(MessageId,messageId).Build();await_mqttClient.PublishAsync(message);}服务端处理消息时先检查MessageId是否已经存在如果存在就直接丢弃。最后说几句MQTT性能优化是一个系统工程不是改一两个参数就能解决的。你需要从连接、消息、客户端、服务端四个层面综合考虑根据自己的实际情况选择合适的优化手段。我上面分享的这些技巧都是我在无数个项目中踩坑踩出来的绝对不是纸上谈兵。按照这些方法优化后你的MQTT通信性能至少能提升5倍在低带宽环境下也能稳定运行。当然还有一些更高级的优化技巧比如使用UDP传输、自定义协议等这些就留到以后再讲了。

相关文章:

C# MQTT性能优化:工业级高可靠低带宽实战指南

上个月给某汽车零部件厂做产线改造,差点栽在MQTT上。 现场环境你懂的,几百个传感器同时发数据,带宽只有可怜的2Mbps,还时不时断网。一开始用的是网上随便找的MQTT客户端代码,结果上线第一天就炸了。 消息延迟最高到了3…...

GORM 标签详解(数据库字段映射核心)

很多人刚学 GORM: 会觉得: gorm:"primaryKey" gorm:"index" gorm:"not null"这些东西: 像“魔法字符串”。 其实: 它本质上是在告诉 GORM: 数据库这一列应该怎么创建也就是:…...

快速从 Excel 文件导入 SQL 数据库的方法与分析

引言 在日常数据处理、数据迁移或系统初始化工作中,我们经常需要将存储在 Excel 文件中的数据导入到 SQL 数据库(如 MySQL, PostgreSQL, SQL Server 等)中。手动逐条录入不仅效率低下,而且容易出错。本文将系统性地分析几种主流、高效的 Excel 导入 SQL 方法,并对比其优缺…...

uniAPP 所有章节知识体系概述和网站播放器落地一体方案

uniAPP 前十章知识体系 由于 uni-app 并没有官方统一的"前十章"教程划分,不同教材的章节结构有所不同。以下基于多本主流教材内容,整合出一套通用的 uni-app 学习路线,涵盖从入门基础到后端通信的核心知识。 第1章 初识 uni-app 本章是学习的起点,帮助建立对 …...

AI Agent Harness多租户数据隔离

AI Agent Harness多租户数据隔离:构建企业级智能协作平台的安全基石 1. 引入与连接:从一场云端智能客服泄露事故谈起 核心概念: AI Agent(智能代理):具备自主感知、推理决策、行动执行能力的软件实体,可代表个人/组织完成特定任务,是当前大模型应用落地的核心载体 AI …...

用 AutoGen 编排多智能体协作,让 AI 团队帮你干活

🧑‍💻 博主介绍 & 诚邀关注 作者:专注于 Java、Python、前端开发的技术博主 | 全网粉丝 30 万 在校期间协助导师完成毕业设计课题分类、论文格式初审及代码整理工作;工作后持续分享毕设思路,助力毕业生顺利完成…...

如何免费将PPTX转换为HTML?探索纯JavaScript解决方案的完整指南

如何免费将PPTX转换为HTML?探索纯JavaScript解决方案的完整指南 【免费下载链接】PPTX2HTML Convert pptx file to HTML by using pure javascript 项目地址: https://gitcode.com/gh_mirrors/pp/PPTX2HTML 在数字化办公时代,PPTX2HTML作为一款纯…...

5分钟掌握WebPShop:Photoshop终极WebP插件完全指南

5分钟掌握WebPShop:Photoshop终极WebP插件完全指南 【免费下载链接】WebPShop Photoshop plug-in for opening and saving WebP images 项目地址: https://gitcode.com/gh_mirrors/we/WebPShop 还在为Photoshop无法原生处理WebP格式而烦恼吗?WebP…...

94、【Agent】【OpenCode】edit 工具提示词(参数内容)

【声明】本博客所有内容均为个人业余时间创作,所述技术案例均来自公开开源项目(如Github,Apache基金会),不涉及任何企业机密或未公开技术,如有侵权请联系删除 背景 上篇 blog 【Agent】【OpenCode】edit 工…...

93、【Agent】【OpenCode】edit 工具提示词(二)

【声明】本博客所有内容均为个人业余时间创作,所述技术案例均来自公开开源项目(如Github,Apache基金会),不涉及任何企业机密或未公开技术,如有侵权请联系删除 背景 上篇 blog 【Agent】【OpenCode】edit 工…...

新手教程使用curl命令快速测试Taotoken的OpenAI兼容接口

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 新手教程:使用curl命令快速测试Taotoken的OpenAI兼容接口 基础教程类,面向刚注册Taotoken的开发者&#xf…...

DeepSeek数据脱敏与联邦学习实战方案(2024最新版零信任架构白皮书)

更多请点击: https://intelliparadigm.com 第一章:DeepSeek数据隐私保护概述 DeepSeek系列大模型在训练与推理过程中严格遵循数据最小化、目的限定及用户可控原则,其隐私保护机制贯穿数据采集、预处理、模型训练、服务部署与日志管理全生命周…...

DeepSeek长上下文能力解密(官方未公开的context-aware attention调度机制)

更多请点击: https://codechina.net 第一章:DeepSeek长上下文能力解密(官方未公开的context-aware attention调度机制) DeepSeek系列模型在128K token上下文场景中展现出远超同规模模型的稳定性与推理一致性,其核心并…...

现在不掌握AI视频学习底层逻辑,3个月内将被淘汰:基于LinkedIn人才数据的技能贬值倒计时分析

更多请点击: https://intelliparadigm.com 第一章:AI视频生成工具学习曲线分析 AI视频生成工具的学习曲线呈现出显著的非线性特征——入门门槛看似平缓,但跨越“可用”到“可控”阶段往往遭遇陡峭的认知断崖。初学者常误以为上传文本提示即可…...

5大AI音频处理插件:用OpenVINO为Audacity注入本地智能处理能力

5大AI音频处理插件:用OpenVINO为Audacity注入本地智能处理能力 【免费下载链接】openvino-plugins-ai-audacity A set of AI-enabled effects, generators, and analyzers for Audacity. 项目地址: https://gitcode.com/gh_mirrors/op/openvino-plugins-ai-audaci…...

G-Helper完整指南:轻量级华硕笔记本控制工具,开源替代Armoury Crate的明智之选

G-Helper完整指南:轻量级华硕笔记本控制工具,开源替代Armoury Crate的明智之选 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops with nearly the same functionality. Works with ROG Zephyrus, Flow, TUF, Strix, S…...

在Windows电脑上完整体验AirPods功能:终极解决方案AirPodsDesktop

在Windows电脑上完整体验AirPods功能:终极解决方案AirPodsDesktop 【免费下载链接】AirPodsDesktop ☄️ AirPods desktop user experience enhancement program, for Windows and Linux (WIP) 项目地址: https://gitcode.com/gh_mirrors/ai/AirPodsDesktop …...

基于Silvaco的β-氧化镓(β-Ga₂O₃)基MSM型日盲紫外光电探测器仿真研究

基于Silvaco的β-氧化镓(β-Ga₂O₃)基MSM型日盲紫外光电探测器仿真研究 摘要 日盲紫外光电探测技术在导弹预警、火灾监测、紫外通信等军用和民用领域具有重要的应用价值。β-氧化镓(β-Ga₂O₃)作为一种超宽禁带半导体材料,因其禁带宽度约为4.8-4.9 eV(对应吸收截止边约25…...

徒手撸极简前后端分离Demo!吃透原生JS动态渲染底层

之前一直觉得前后端分离是个特别高大上的工程化概念,总以为得学一堆框架、接口规范、部署流程才能上手。 直到昨天我没用Vue、没用React,纯靠原生JSHTMLCSSjson-server,手写了一套最朴素的前后端分离小案例,瞬间把底层逻辑彻底打通…...

化学教学平台——数据可视化与电化学AI动画推演

化学教学平台——数据可视化与电化学AI动画推演 1 项目概述 本化学教学平台是一套完整的前端Web应用,旨在为化学教育工作者和学生提供两个核心功能模块:数据智能查询与化学性质可视化(基于ECharts和D3.js),以及反应模拟预判——基于电化学原理的AI动画推演(基于Three.j…...

终极AMD Ryzen调试工具:免费开源的硬件掌控神器

终极AMD Ryzen调试工具:免费开源的硬件掌控神器 【免费下载链接】SMUDebugTool A dedicated tool to help write/read various parameters of Ryzen-based systems, such as manual overclock, SMU, PCI, CPUID, MSR and Power Table. 项目地址: https://gitcode.…...

利用 Taotoken 为不同业务场景动态选择最合适的大模型

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 利用 Taotoken 为不同业务场景动态选择最合适的大模型 在构建一个集成了大模型能力的应用时,一个常见的挑战是如何为不…...

企业级多模型聚合平台选型,如何通过用量看板实现成本精细化管理

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 企业级多模型聚合平台选型,如何通过用量看板实现成本精细化管理 当企业技术团队决定将大模型能力深度融入业务流程时&a…...

告别账单惊吓,Taotoken Token Plan 如何让成本更可控

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 告别账单惊吓,Taotoken Token Plan 如何让成本更可控 对于依赖大模型 API 进行开发的团队和个人而言,项目成…...

终极指南:如何5步免费使用Cursor Pro破解工具实现永久免费AI编程

终极指南:如何5步免费使用Cursor Pro破解工具实现永久免费AI编程 【免费下载链接】cursor-free-vip [Support 0.45](Multi Language 多语言)自动注册 Cursor Ai ,自动重置机器ID , 免费升级使用Pro 功能: Youve reache…...

m4s-converter技术解析:跨平台B站缓存视频无损转换方案

m4s-converter技术解析:跨平台B站缓存视频无损转换方案 【免费下载链接】m4s-converter 一个跨平台小工具,将bilibili缓存的m4s格式音视频文件合并成mp4 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter m4s-converter是一个专业的开…...

使用Taotoken后API调用延迟与账单透明度的实际体验

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 使用Taotoken后API调用延迟与账单透明度的实际体验 1. 引言 对于需要调用多种大模型API的开发者而言,统一接入和成本管…...

免费在线去水印软件推荐(2026保姆级教程):别让水印毁了你的好素材

你是不是也遇到过这种抓狂瞬间?刷到一段绝美空镜,想存下来做壁纸却挂着硕大的水印;朋友发来一张搞笑表情包,转发前发现左下角Logo碍眼得要命;好不容易找到一张配图素材,精心裁了半天还是绕不开那行半透明的…...

本地大语言模型推理新选择:为什么llama-cpp-python成为开发者首选?

本地大语言模型推理新选择:为什么llama-cpp-python成为开发者首选? 【免费下载链接】llama-cpp-python Python bindings for llama.cpp 项目地址: https://gitcode.com/gh_mirrors/ll/llama-cpp-python 在人工智能快速发展的今天,能够…...

明日方舟桌宠Ark-Pets显卡优化配置指南:3步实现流畅桌面动画

明日方舟桌宠Ark-Pets显卡优化配置指南:3步实现流畅桌面动画 【免费下载链接】Ark-Pets Arknights Desktop Pets | 明日方舟桌宠 (ArkPets) 项目地址: https://gitcode.com/gh_mirrors/ar/Ark-Pets Ark-Pets是一款基于《明日方舟》角色模型的桌面宠物软件&am…...