OpenBMC sdbusplus接口实战:从服务注册到多接口管理

news2026/3/14 12:31:21
1. 初识sdbusplus你的BMC服务开发起点如果你正在为OpenBMC开发一个新的管理功能比如监控机箱温度、控制风扇转速或者实现一个自定义的硬件健康检查服务那么你迟早要和D-Bus打交道。在OpenBMC的世界里sdbusplus就是那个帮你搞定D-Bus通信的“瑞士军刀”。它不是一个新的D-Bus实现而是对底层sd-bus库的一个C封装层把那些繁琐的、容易出错的底层API调用包装成了更符合C开发者习惯的、面向对象的接口。我刚开始接触OpenBMC时看到D-Bus相关的代码也是一头雾水。什么总线、服务、对象路径、接口、方法、信号、属性……一堆概念扑面而来。但后来我发现其实可以把它想象成一个公司内部的电话系统。总线Bus就是公司的内部电话网络服务Service就像是公司的某个部门比如“硬件维护部”对象路径Object Path是这个部门里的具体工位比如“/硬件维护部/服务器A区”接口Interface就是这个工位能提供的服务合同规定了你能打电话来问什么方法、工位会主动广播什么消息信号、以及工位上有什么状态牌可以查看或设置属性。sdbusplus的作用就是帮你快速地在电话系统里注册一个部门、布置好工位、挂上服务合同和状态牌并且接听和处理打进来的电话。你不用自己去操心电话线怎么接、信号协议怎么定这些脏活累活它都帮你封装好了。我们这篇文章就是要手把手带你走完这个完整的流程从在总线上挂上你部门的牌子服务注册到在工位上摆出你能提供的服务清单添加接口与属性再到监听其他部门的重要广播并做出反应事件匹配最后扩展到管理一个拥有多个工位和复杂服务的大部门多接口管理。我会用大量我实际踩过的坑和调试过的代码来举例目标是让你看完就能动手把自己的BMC服务跑起来。2. 服务注册让你的模块在总线上“挂牌营业”万事开头难但服务注册这一步sdbusplus让它变得异常简单。我们先来看一个最最基础的、能让你的进程在D-Bus系统总线上“现身”的代码骨架。这个例子虽然不干任何实事但它是所有后续功能的基石。#include sdbusplus/asio/connection.hpp #include sdbusplus/asio/object_server.hpp #include boost/asio/io_context.hpp #include iostream static const std::string busServiceName xyz.openbmc_project.MyDemoService; int main() { // 1. 创建ASIO I/O上下文这是处理异步事件的核心 boost::asio::io_context io; // 2. 创建并获取一个到系统D-Bus的连接 auto conn std::make_sharedsdbusplus::asio::connection(io); // 3. 申请独占这个服务名相当于“挂牌” conn-request_name(busServiceName.c_str()); // 4. 启动事件循环开始等待和处理消息 io.run(); return 0; }就这么几行代码一个D-Bus服务进程的框架就搭好了。我们来拆解一下每一步背后发生了什么这对后续调试和理解至关重要。首先boost::asio::io_context io;这一行创建了一个异步I/O调度器。D-Bus通信本质上是异步的客户端发来一个方法调用请求服务端需要去处理处理完了再异步地回复。io_context就是管理所有这些异步任务比如等待网络消息、定时器、回调函数的“大脑”。你必须保证它在整个程序生命周期内都存在。第二行std::make_sharedsdbusplus::asio::connection(io)是重头戏。它做了两件关键事一是调用底层的sd_bus_default_system()或sd_bus_default_user()来连接到系统或用户D-Bus总线二是将这个D-Bus连接的文件描述符fd注册到刚才的io_context中并设置好读回调。这样当总线上有消息发给我们时ASIO就能自动唤醒并处理。你可以在sdbusplus/asio/connection.hpp的构造函数里看到它最后调用了read_immediate()这个函数就是用来开始监听总线消息的。第三行conn-request_name是真正的“注册”动作。它向D-Bus守护进程dbus-daemon发送一个请求“我要使用xyz.openbmc_project.MyDemoService这个名字请把它分配给我”。这里用的标志位SD_BUS_NAME_ALLOW_REPLACEMENT | SD_BUS_NAME_REPLACE_EXISTING很实用它允许其他服务稍后替换我们ALLOW_REPLACEMENT并且如果这个名字已经被占用了我们会尝试替换掉它REPLACE_EXISTING。这在开发调试时非常方便你不需要手动去杀掉旧进程。最后io.run()是启动事件循环。程序会阻塞在这里不断地处理D-Bus消息、定时器回调等直到你主动调用io.stop()或者所有异步操作都完成。编译运行这个程序后你可以在BMC的shell里用busctl list命令查看应该就能找到xyz.openbmc_project.MyDemoService这个服务名了。不过现在这个服务还是个“空壳”它没有任何对象、接口所以还无法提供任何功能。这就好比公司里挂了个部门牌子但里面既没工位也没人电话打进来也没人接。2.1 深入request_name名字争夺战与唯一标识你可能会有疑问如果两个程序同时请求同一个服务名谁会赢D-Bus有一套名字管理机制。request_name是一个同步调用它会一直等待直到名字分配结果确定。如果名字当前无人使用申请者直接获得。如果名字已被占用且原持有者没有设置SD_BUS_NAME_ALLOW_REPLACEMENT标志那么新的请求会失败。如果原持有者允许替换那么当原持有者释放名字比如进程退出或主动放弃时等待队列中的第一个请求者将获得该名字。每个成功连接到总线的连接还会自动获得一个“唯一名”Unique Name格式像:1.65这样。这个名称由总线守护进程分配在整个总线生命周期内是唯一且不可变的即使你的服务名被替换了这个唯一名也不会变。它主要用于消息的sender字段标识消息的真正来源。在调试时通过busctl monitor看到的sender后面跟的就是这个唯一名。理解这一点对后续分析事件匹配和消息流向非常重要。3. 添加接口与属性赋予服务真正的能力服务名注册好了接下来就要布置“工位”和定义“服务合同”了。在D-Bus中一个服务下可以有多个对象路径Object Path每个路径下可以有多个接口Interface每个接口里则包含了具体的方法Method、信号Signal和属性Property。我们接下来要做的就是在我们的服务下创建一个对象并为其添加一个功能接口。假设我们要创建一个简单的“计算器”服务它提供一个对象路径/xyz/openbmc_project/calculator在这个对象上提供一个接口xyz.openbmc_project.Calculator该接口有一个属性LastResult记录上次计算结果以及一个方法Add执行加法。#include sdbusplus/asio/connection.hpp #include sdbusplus/asio/object_server.hpp #include sdbusplus/asio/property.hpp #include boost/asio/io_context.hpp #include iostream static const std::string busServiceName xyz.openbmc_project.CalculatorService; static const std::string objectPath /xyz/openbmc_project/calculator; static const std::string interfaceName xyz.openbmc_project.Calculator; int main() { boost::asio::io_context io; auto conn std::make_sharedsdbusplus::asio::connection(io); conn-request_name(busServiceName.c_str()); // 关键步骤1创建对象服务器 auto objServer std::make_sharedsdbusplus::asio::object_server(conn); // 关键步骤2在指定路径上添加一个接口 auto calculatorIface objServer-add_interface(objectPath, interfaceName); // 关键步骤3为接口注册一个属性 int lastResult 0; calculatorIface-register_property(LastResult, lastResult, sdbusplus::asio::PropertyPermission::readWrite); // 关键步骤4为接口注册一个方法 calculatorIface-register_method(Add, [lastResult](int a, int b){ lastResult a b; std::cout Add called: a b lastResult std::endl; return lastResult; }); // 关键步骤5初始化接口将其真正发布到总线上 calculatorIface-initialize(); std::cout Calculator service is running... std::endl; io.run(); return 0; }现在我们来深入看看这几个关键步骤。sdbusplus::asio::object_server是一个辅助类它帮你管理多个对象和接口。它的构造函数默认会调用add_manager(/)。这个ObjectManager接口是D-Bus的一个标准接口用于动态管理对象。当你的服务添加或删除对象时它会自动发送InterfacesAdded和InterfacesRemoved信号方便其他客户端感知服务状态变化。在OpenBMC中很多上层管理工具都依赖这个机制来发现服务所以通常保留这个默认行为是好的。add_interface是核心。它创建了一个dbus_interface对象并将其保存在object_server内部的一个向量中。此时这个接口对象还只是一个“草稿”它记录了路径名、接口名以及后续你通过register_property和register_method添加的各种回调函数但总线和其他客户端还感知不到它的存在。register_property和register_method这两个函数用起来非常直观。它们将属性名/方法名、对应的C变量/函数绑定起来。注意注册属性时指定的PropertyPermission::readWrite这表示该属性可读可写。你还可以设为readOnly。对于属性sdbusplus会自动生成Get和Set方法。对于方法你注册的lambda函数或普通函数就是其实现。lambda捕获[lastResult]让我们可以修改外部的lastResult变量这样属性值就能随着方法调用而更新。最最重要的一步是initialize()。我见过不少新手掉进这个坑注册了一堆属性和方法但忘记调用initialize或者调用的时机不对。这个方法做了什么呢它会把之前“草稿”阶段收集的所有信息属性、方法、信号组装成D-Bus底层需要的sd_bus_vtable结构数组。这个vtable虚函数表是sd-bus用来描述一个接口完整能力的元数据。然后它调用sd_bus_add_object_vtable将这个vtable注册到总线上对应的对象路径。最后它还会发射一个InterfacesAdded信号通知总线上的其他监听者“嗨我这儿有个新接口上线了”切记initialize()必须在所有属性和方法都注册完毕之后调用并且一个接口对象只能调用一次。如果你先initialize再尝试register_method新注册的方法是无效的因为vtable已经固化并提交给总线了。正确的模式永远是创建接口 - 注册属性/方法/信号 - 调用initialize- 启动事件循环。4. 事件匹配与信号监听让服务变得“智能”一个只会被动响应请求的服务是不够的。在真实的BMC开发中你的服务经常需要感知系统其他部分的变化并做出反应。比如风扇控制服务需要监听温度传感器的读数变化电源管理服务需要监听开机按钮的状态变化。这就需要用到D-Bus的信号Signal和匹配规则Match。信号是一种单向的、广播式的通信机制。一个服务可以发射Emit信号任何其他服务或客户端都可以通过添加匹配规则来监听它而不需要知道发射者是谁。这很像发布-订阅模式。4.1 发射信号首先我们看看如何在自己的接口里定义和发射一个信号。接着上面的计算器例子我们希望在每次计算结果更新时发射一个信号通知所有监听者。// ... 前面的头文件和定义 ... int main() { boost::asio::io_context io; auto conn std::make_sharedsdbusplus::asio::connection(io); conn-request_name(busServiceName.c_str()); auto objServer std::make_sharedsdbusplus::asio::object_server(conn); auto calculatorIface objServer-add_interface(objectPath, interfaceName); int lastResult 0; calculatorIface-register_property(LastResult, lastResult, sdbusplus::asio::PropertyPermission::readWrite); // 注册一个信号名为“ResultChanged”携带一个整数参数新的结果值 calculatorIface-register_signalvoid(int)(ResultChanged); calculatorIface-register_method(Add, [calculatorIface, lastResult](int a, int b){ lastResult a b; std::cout Add called: a b lastResult std::endl; // 在方法执行后发射信号 calculatorIface-signal_property(LastResult); // 自动发射属性变化信号 // 或者发射我们自定义的信号 calculatorIface-emit_signal(ResultChanged).value(lastResult); return lastResult; }); calculatorIface-initialize(); io.run(); return 0; }这里有两个关键点。第一register_signalvoid(int)(ResultChanged)定义了一个信号它不返回值void但携带一个int类型的参数。第二在Add方法里我们演示了两种发射信号的方式signal_property是专门为属性设计的便捷函数它会自动发射D-Bus标准的Properties.PropertiesChanged信号这对于那些遵循属性接口规范的客户端如WebUI来说是必需的。而emit_signal则是发射我们自定义的信号后面用.value(...)来传递参数。4.2 监听信号添加匹配规则现在我们创建另一个服务或同一个服务内的另一个部分来监听这个信号。#include sdbusplus/asio/connection.hpp #include sdbusplus/bus/match.hpp #include boost/asio/io_context.hpp #include iostream static const std::string interfaceToWatch xyz.openbmc_project.Calculator; int main() { boost::asio::io_context io; auto conn std::make_sharedsdbusplus::asio::connection(io); // 定义匹配规则监听特定接口的属性变化信号 std::unique_ptrsdbusplus::bus::match_t matchPropChanged std::make_uniquesdbusplus::bus::match_t( *conn, sdbusplus::bus::match::rules::propertiesChanged(/xyz/openbmc_project/calculator, interfaceToWatch), [](sdbusplus::message_t msg){ std::string interfaceName; std::unordered_mapstd::string, sdbusplus::message::variantint changedProps; std::vectorstd::string invalidatedProps; try { msg.read(interfaceName, changedProps, invalidatedProps); std::cout [PropertyChanged] Interface: interfaceName std::endl; for (const auto [propName, variantValue] : changedProps) { std::cout Property propName changed to: ; if (propName LastResult) { std::cout std::getint(variantValue) std::endl; } } } catch (const std::exception e) { std::cerr Error reading PropertiesChanged signal: e.what() std::endl; } } ); // 定义匹配规则监听自定义信号 std::unique_ptrsdbusplus::bus::match_t matchCustomSignal std::make_uniquesdbusplus::bus::match_t( *conn, sdbusplus::bus::match::rules::signalMember(ResultChanged).path(/xyz/openbmc_project/calculator).interface(interfaceToWatch), [](sdbusplus::message_t msg){ int newResult; try { msg.read(newResult); std::cout [CustomSignal] ResultChanged signal received. New value: newResult std::endl; } catch (const std::exception e) { std::cerr Error reading ResultChanged signal: e.what() std::endl; } } ); std::cout Signal listener started. Waiting for events... std::endl; io.run(); return 0; }这段代码创建了两个匹配规则。sdbusplus::bus::match_t是核心类它的构造函数接受三个参数总线连接、匹配规则字符串、回调函数。当总线上有符合规则的消息时回调函数就会被触发。匹配规则字符串的构建使用了sdbusplus::bus::match::rules命名空间下的辅助函数这比手动拼接字符串更安全、更易读。propertiesChanged函数帮我们生成了监听标准Properties.PropertiesChanged信号的规则并限定了对象路径和接口名这样我们就不会收到无关的信号。signalMember函数则用于监听我们自定义的ResultChanged信号。在回调函数中我们通过msg.read(...)来解析信号携带的参数。这里必须注意参数的顺序和类型必须与信号发射时完全一致否则会抛出异常。好的实践是总是用try-catch包裹读取逻辑。一个重要的坑匹配规则是基于字符串的并且是由D-Bus守护进程 (dbus-daemon) 进行过滤的。这意味着如果你的规则写错了比如路径拼写错误守护进程不会报错只是你永远收不到信号。调试时可以先用busctl monitor命令裸跑看看实际发出的信号路径、接口、成员名到底是什么再对照着写规则。5. 多接口管理与复杂对象模型在实际的BMC模块中一个对象路径下往往不止一个接口而且对象之间可能存在层级关系。例如一个风扇托盘/xyz/openbmc_project/inventory/fantray0可能同时实现Item库存项、Asset资产信息、State状态等多个接口。又或者一个电源单元/xyz/openbmc_project/power/psu0下面可能有多个子对象分别代表不同的传感器/xyz/openbmc_project/power/psu0/voltage/xyz/openbmc_project/power/psu0/current。5.1 同一对象路径下的多个接口这其实很简单因为add_interface可以在同一个路径上被多次调用每次添加一个不同的接口。// 假设我们在同一个对象路径上提供计算器和日志两个接口 auto calcIface objServer-add_interface(objectPath, xyz.openbmc_project.Calculator); auto logIface objServer-add_interface(objectPath, xyz.openbmc_project.Logger); calcIface-register_property(LastResult, lastResult, sdbusplus::asio::PropertyPermission::readWrite); calcIface-register_method(Add, [](int a, int b){ return a b; }); logIface-register_method(LogMessage, [](const std::string msg){ std::cout LOG: msg std::endl; return true; }); // 分别初始化 calcIface-initialize(); logIface-initialize();这样客户端就可以通过同一个对象路径调用不同接口下的方法。例如通过Calculator接口调用Add通过Logger接口调用LogMessage。ObjectManager在发送InterfacesAdded信号时会包含这个对象路径上所有新增的接口名。5.2 父子对象路径与层次结构更常见的情况是树状结构。比如我们要模拟一个传感器集合根路径是/xyz/openbmc_project/sensors下面有温度传感器/xyz/openbmc_project/sensors/temperature/cpu0和风扇传感器/xyz/openbmc_project/sensors/fan/fan0。// 创建根对象可能只是一个容器不提供具体功能接口或者提供集合管理接口 auto sensorRootIface objServer-add_interface(/xyz/openbmc_project/sensors, xyz.openbmc_project.SensorAggregator); sensorRootIface-register_method(GetAllSensorPaths, [](){ // 返回所有子传感器路径的逻辑 std::vectorstd::string paths {/xyz/openbmc_project/sensors/temperature/cpu0, /xyz/openbmc_project/sensors/fan/fan0}; return paths; }); sensorRootIface-initialize(); // 创建CPU温度传感器子对象 auto cpuTempIface objServer-add_interface(/xyz/openbmc_project/sensors/temperature/cpu0, xyz.openbmc_project.Sensor.Value); double cpuTemp 45.0; cpuTempIface-register_property(Value, cpuTemp, sdbusplus::asio::PropertyPermission::readOnly); cpuTempIface-register_property(Unit, std::string(DegreesC), sdbusplus::asio::PropertyPermission::readOnly); cpuTempIface-initialize(); // 创建风扇转速传感器子对象 auto fanSpeedIface objServer-add_interface(/xyz/openbmc_project/sensors/fan/fan0, xyz.openbmc_project.Sensor.Value); int fanSpeed 3200; fanSpeedIface-register_property(Value, fanSpeed, sdbusplus::asio::PropertyPermission::readOnly); fanSpeedIface-register_property(Unit, std::string(RPM), sdbusplus::asio::PropertyPermission::readOnly); fanSpeedIface-initialize();这里有一个至关重要的细节ObjectManager的路径。还记得object_server构造函数默认会调用add_manager(/)吗这意味着它会在根路径/上注册ObjectManager接口。当你在/xyz/openbmc_project/sensors/temperature/cpu0上添加接口并初始化时object_server会向总线发送InterfacesAdded信号但信号的发送者路径sender path是ObjectManager所在的路径也就是/。如果你在另一个服务里添加匹配规则像这样sdbusplus::bus::match::rules::interfacesAdded(/xyz/openbmc_project/sensors/temperature/cpu0)你是收不到信号的因为信号是从/路径发出的而不是从子对象路径发出的。正确的匹配规则应该监听ObjectManager所在的路径并在回调函数中检查消息体里新增的接口路径是否是你关心的。// 监听任何对象上新增接口的信号来自根路径的ObjectManager std::unique_ptrsdbusplus::bus::match_t matchAdded std::make_uniquesdbusplus::bus::match_t( *conn, sdbusplus::bus::match::rules::interfacesAdded(/), // 注意路径是根路径 [](sdbusplus::message_t msg){ sdbusplus::message::object_path objPath; // 消息体里包含了新增接口的对象路径和接口名列表 // 需要在这里解析msg判断objPath是不是我们关心的 // ... 解析逻辑 ... } );这个坑我踩过好几次现象就是监听器死活收不到对象添加的通知。后来用busctl monitor仔细看原始信号才发现sender和path字段的奥秘。所以在调试D-Bus信号时一定要先用监控工具看原始数据不要盲目相信自己的代码逻辑。5.3 动态对象管理有时候对象路径是动态生成的比如根据探测到的硬件数量来创建。这时就需要在运行时动态地添加和删除对象。object_server提供了add_interface和remove_interface来管理接口但删除一个接口后记得也要调用emit_interfaces_removed来通知总线这样监听InterfacesRemoved信号的客户端才能知道。// 动态创建一个新的传感器对象 std::string dynamicPath /xyz/openbmc_project/sensors/voltage/psu std::to_string(id); auto dynamicIface objServer-add_interface(dynamicPath, xyz.openbmc_project.Sensor.Value); // ... 配置属性和方法 ... dynamicIface-initialize(); // 对象服务器会自动发送 InterfacesAdded 信号 // 当需要删除这个对象时 objServer-remove_interface(dynamicIface); // 从内部管理列表中移除 // 需要手动通知总线该对象上的接口已被移除 conn-emit_interfaces_removed(dynamicPath.c_str(), {xyz.openbmc_project.Sensor.Value});管理好对象的生命周期确保信号发送的时机正确是构建健壮的BMC服务的关键。尤其是在服务重启或硬件热插拔场景下清晰的对象添加/移除信号流能保证上层管理界面或依赖服务状态的一致性。

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

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

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…