第2天:ROS2通信机制实战——从“单向传数据”到“双向发请求”

做机器人开发时:“为什么控制机器人前进用话题,而让机器人报位置要用服务?”其实答案很简单——ROS2的通信机制是“按需设计”的:话题适合高频、单向的数据传输(比如实时发控制指令),服务适合低频、需要反馈的交互(比如查询状态)。
在这里插入图片描述

文章目录

  • 第2天:ROS2通信机制实战——从“单向传数据”到“双向发请求”
    • 上午:话题通信——让数据“单向流动”
      • 一、先搞懂话题的“底层逻辑”
        • 1. 三个关键特性:记熟了才不会用错
        • 2. 用CLI工具“玩转”话题:先看再动手
      • 二、用C++写话题节点:从发布到订阅
        • 1. 先搭环境:创建功能包
        • 2. 写发布者节点:每500ms发一次“Hello Topic!”
        • 3. 写订阅者节点:接收“chatter”话题的消息
        • 4. 配置编译规则:让ROS2认识你的节点
        • 5. 编译运行:验证通信是否成功
    • 下午:服务通信——需要“反馈”就用它
      • 一、服务的核心逻辑:和话题的关键区别
        • 1. 三个核心特性:别和话题搞混
        • 2. 用CLI工具测试服务:先看后写
      • 二、自定义服务接口:满足个性化需求
        • 1. 创建接口功能包:专门放自定义接口
        • 2. 写自定义服务接口(.srv文件)
        • 3. 配置CMakeLists.txt:生成接口代码
        • 4. 配置package.xml:声明依赖和接口组
        • 5. 编译接口包:生成可调用的代码
      • 三、用C++写服务节点:客户端发请求,服务端处理
        • 1. 创建C++服务功能包
        • 2. 写服务端节点:处理“移动请求”
        • 3. 写客户端节点:发送“移动请求”
        • 4. 配置CMakeLists.txt:编译服务节点
        • 5. 编译运行:验证服务通信

上午:话题通信——让数据“单向流动”

话题是ROS2里用得最多的通信方式,比如激光雷达的点云、机器人的速度指令,都是通过话题传输的。它的核心是“发布-订阅”模式,就像电台广播:一个电台(发布者)发信号,无数收音机(订阅者)都能听——但收音机不能给电台回消息,这就是“单向”的意思。

一、先搞懂话题的“底层逻辑”

1. 三个关键特性:记熟了才不会用错
  • 单向异步:发布者发数据时,不用等订阅者接收,自己该干啥干啥;订阅者收数据时,也不用管发布者是谁——我当年做AGV项目时,激光雷达节点(发布者)就算断了,导航节点(订阅者)最多报“没数据”,不会直接崩溃,这就是异步的优势。
  • 多对多通信:一个话题可以有多个发布者,也可以有多个订阅者。比如“机器人位置”这个话题,既能让导航节点订阅,也能让监控节点订阅;甚至可以有两个发布者(GPS和里程计)同时发位置数据,订阅者按需选择。
  • 消息接口要统一:发布者和订阅者必须用同一个“消息格式”(比如都用geometry_msgs/msg/Twist),不然数据传过去也是“乱码”。这就像电台用FM92.5广播,收音机也得调到这个频率才能听清——我当年就因为把Twist写成Pose,调试了半小时才发现接口不匹配。
2. 用CLI工具“玩转”话题:先看再动手

在写代码前,先用ROS2自带的命令行工具(CLI)熟悉话题的操作——这是排查问题的“神器”,比如节点没收到数据时,先用CLI确认话题是否存在、数据是否正常。

前提:先启动两个节点,做后续测试用

  • 终端1:ros2 run turtlesim turtlesim_node(启动小乌龟仿真节点,作为订阅者,接收速度指令)
  • 终端2:ros2 run turtlesim turtle_teleop_key(启动键盘控制节点,作为发布者,发速度指令)

接下来用三个核心命令实战:

  1. 查话题列表:有哪些话题在运行?
    ros2 topic list,会看到/turtle1/cmd_vel(速度指令话题)、/turtle1/pose(乌龟位置话题)等。加-t参数还能看话题类型:ros2 topic list -t,输出/turtle1/cmd_vel [geometry_msgs/msg/Twist]——这就知道了,/turtle1/cmd_vel用的是Twist格式的消息。

  2. 看话题数据:数据到底长啥样?
    想知道小乌龟的位置,输ros2 topic echo /turtle1/pose,会实时打印x(x坐标)、y(y坐标)、theta(角度)等数据。按终端2的方向键控制乌龟移动,会看到数据跟着变——这就是“订阅者实时接收数据”的直观表现。

  3. 手动发话题:不用代码也能控制节点?
    有时候想快速测试节点是否正常,不用写发布者代码,直接用CLI发数据。先停掉终端2的键盘节点(Ctrl+C),然后输:

ros2 topic pub /turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.5}}"

看仿真窗口:乌龟会以0.2m/s的速度前进,同时以0.5rad/s的速度左转——这说明手动发布的话题被turtlesim节点正确接收了。

二、用C++写话题节点:从发布到订阅

光用CLI不够,实际开发要自己写节点。下面带大家写一个“发布者发字符串消息,订阅者收消息并打印”的例子——这是最基础的模板,后续不管是发激光数据还是速度指令,都能套用这个框架。

1. 先搭环境:创建功能包

首先创建工作空间和C++功能包(依赖rclcppstd_msgsstd_msgs里有常用的字符串、整数等消息类型):

mkdir -p topic_ws/src && cd topic_ws/src
# 创建C++功能包,依赖rclcpp(ROS2 C++核心库)和std_msgs(标准消息库)
ros2 pkg create topic_cpp --build-type ament_cmake --dependencies rclcpp std_msgs
cd ..  # 回到工作空间根目录
2. 写发布者节点:每500ms发一次“Hello Topic!”

发布者的核心逻辑是:初始化节点→创建发布者→用定时器定时发消息。
文件路径:src/topic_cpp/src/topic_publisher.cpp,代码如下(关键步骤已标注释):

#include "rclcpp/rclcpp.hpp"  // ROS2 C++核心头文件
#include "std_msgs/msg/string.hpp"  // 字符串消息头文件// 定义发布者节点类,继承自rclcpp::Node(所有ROS2节点都要继承这个类)
class TopicPublisherNode : public rclcpp::Node
{
public:// 构造函数:初始化节点名、发布者、定时器TopicPublisherNode(std::string node_name) : Node(node_name){// 打印日志,告诉用户节点启动了(RCLCPP_INFO是ROS2的日志宏)RCLCPP_INFO(this->get_logger(), "%s节点已启动!", node_name.c_str());// 创建发布者:话题名"chatter",QoS队列大小10(后面讲QoS,先设10就行)publisher_ = this->create_publisher<std_msgs::msg::String>("chatter", 10);// 创建定时器:每500ms触发一次回调函数(发消息)timer_ = this->create_wall_timer(std::chrono::milliseconds(500),  // 时间间隔500msstd::bind(&TopicPublisherNode::timer_callback, this)  // 绑定回调函数);}private:// 定时器回调函数:每次触发就发布一条消息void timer_callback(){auto msg = std_msgs::msg::String();  // 创建字符串消息对象msg.data = "Hello Topic!";  // 设置消息内容RCLCPP_INFO(this->get_logger(), "发布消息:%s", msg.data.c_str());  // 打印发布的消息publisher_->publish(msg);  // 发布消息到"chatter"话题}// 声明发布者指针(类型是std_msgs::msg::String的发布者)rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;// 声明定时器指针rclcpp::TimerBase::SharedPtr timer_;
};int main(int argc, char **argv)
{rclcpp::init(argc, argv);  // 初始化ROS2环境// 创建发布者节点对象(节点名"topic_publisher")auto node = std::make_shared<TopicPublisherNode>("topic_publisher");rclcpp::spin(node);  // 循环运行节点,处理回调(比如定时器回调)rclcpp::shutdown();  // 关闭ROS2环境return 0;
}
3. 写订阅者节点:接收“chatter”话题的消息

订阅者的核心逻辑是:初始化节点→创建订阅者→绑定回调函数(收到消息后执行)。
文件路径:src/topic_cpp/src/topic_subscriber.cpp,代码如下:

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"// 定义订阅者节点类
class TopicSubscriberNode : public rclcpp::Node
{
public:TopicSubscriberNode(std::string node_name) : Node(node_name){RCLCPP_INFO(this->get_logger(), "%s节点已启动!", node_name.c_str());// 创建订阅者:话题名"chatter",QoS队列大小10,绑定回调函数subscriber_ = this->create_subscription<std_msgs::msg::String>("chatter",  // 要订阅的话题名(必须和发布者一致)10,         // QoS队列大小// 绑定回调函数:收到消息后调用topic_callbackstd::bind(&TopicSubscriberNode::topic_callback, this, std::placeholders::_1));}private:// 话题回调函数:收到消息后执行(msg是收到的消息)void topic_callback(const std_msgs::msg::String::SharedPtr msg){RCLCPP_INFO(this->get_logger(), "收到消息:%s", msg->data.c_str());  // 打印收到的消息}// 声明订阅者指针rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscriber_;
};int main(int argc, char **argv)
{rclcpp::init(argc, argv);auto node = std::make_shared<TopicSubscriberNode>("topic_subscriber");rclcpp::spin(node);  // 循环等待消息,收到消息就触发回调rclcpp::shutdown();return 0;
}
4. 配置编译规则:让ROS2认识你的节点

写好代码后,要告诉编译器“怎么编译这些文件”,需要修改CMakeLists.txt(C++项目的编译配置文件)。
文件路径:src/topic_cpp/CMakeLists.txt,在文件末尾添加以下内容:

# 1. 找到依赖包(告诉编译器需要用rclcpp和std_msgs)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)# 2. 编译发布者节点:把topic_publisher.cpp编译成可执行文件
add_executable(topic_publisher src/topic_publisher.cpp)
# 链接依赖包(让可执行文件能调用rclcpp和std_msgs的函数)
ament_target_dependencies(topic_publisher rclcpp std_msgs)# 3. 编译订阅者节点:同理
add_executable(topic_subscriber src/topic_subscriber.cpp)
ament_target_dependencies(topic_subscriber rclcpp std_msgs)# 4. 安装可执行文件:把编译好的节点放到install目录(ROS2能找到的地方)
install(TARGETStopic_publishertopic_subscriberDESTINATION lib/${PROJECT_NAME}  # 安装路径(固定格式)
)
5. 编译运行:验证通信是否成功
  • 编译:回到工作空间根目录,执行编译命令(只编译topic_cpp功能包,节省时间):
    colcon build --packages-select topic_cpp
    
  • 运行发布者:打开终端1,加载环境变量并启动发布者:
    source install/setup.bash && ros2 run topic_cpp topic_publisher
    
    会看到日志:topic_publisher节点已启动! 发布消息:Hello Topic!(每500ms一条)。
  • 运行订阅者:打开终端2,同理启动订阅者:
    source install/setup.bash && ros2 run topic_cpp topic_subscriber
    
    会看到日志:topic_subscriber节点已启动! 收到消息:Hello Topic!——这说明发布者和订阅者通信成功了!

下午:服务通信——需要“反馈”就用它

如果说话题是“广播”,那服务就是“打电话”:你(客户端)打给客服(服务端),说“我要查账单”(发送请求),客服查完后告诉你“账单金额是XX”(返回响应)——必须等客服回复,你才知道结果,这就是“双向同步”的特点。

一、服务的核心逻辑:和话题的关键区别

1. 三个核心特性:别和话题搞混
  • 双向同步:客户端发送请求后,会一直等服务端的响应,期间不会做其他事;服务端必须处理完请求,才能给客户端回复——我当年做机器人“归位”功能时,客户端发“归位请求”,必须等服务端返回“已归位”,才会执行下一步,这就是同步的必要性。
  • 1对1通信:一个服务名只能对应一个服务端(比如“查位置”服务,不能有两个服务端同时提供),但可以有多个客户端调用同一个服务端——比如多个节点都想查机器人位置,都能调用“查位置”服务。
  • 服务接口分“请求”和“响应”:和话题的“单一消息格式”不同,服务接口(.srv文件)分两部分,用---分隔,上面是请求字段,下面是响应字段。比如“两数相加”服务的接口:
    int64 a  # 请求:第一个数
    int64 b  # 请求:第二个数
    ---
    int64 sum  # 响应:两数之和
    
2. 用CLI工具测试服务:先看后写

前提:启动一个官方的服务端节点(提供“两数相加”服务)
终端1:ros2 run examples_rclpy_minimal_service service——这个节点提供/add_two_ints服务,功能是接收两个整数,返回它们的和。

用三个核心命令实战:

  1. 查服务列表:有哪些服务在运行?
    ros2 service list,会看到/add_two_ints(两数相加服务)——这就是我们要调用的服务。

  2. 查服务类型:服务用的是什么接口?
    ros2 service type /add_two_ints,输出example_interfaces/srv/AddTwoInts——这说明该服务用的是AddTwoInts接口,包含a(请求)、b(请求)、sum(响应)三个字段。

  3. 手动调用服务:不用代码也能发请求?
    ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 5, b: 10}"——意思是“调用/add_two_ints服务,请求参数a=5b=10”。
    终端1(服务端)会输出“收到请求:a=5, b=10”,终端2(客户端)会输出“响应:sum=15”——这说明服务调用成功了。

二、自定义服务接口:满足个性化需求

官方的服务接口(比如AddTwoInts)不够用怎么办?比如我们想让机器人“移动指定距离后返回当前位置”,就需要自定义一个服务接口。下面带大家从0到1创建自定义服务接口。

1. 创建接口功能包:专门放自定义接口

接口功能包必须用ament_cmake编译类型,且依赖rosidl_default_generators(用于生成接口代码):

# 回到topic_ws/src目录
cd topic_ws/src
# 创建接口功能包
ros2 pkg create custom_srv_interfaces --build-type ament_cmake --dependencies rosidl_default_generators
2. 写自定义服务接口(.srv文件)

服务接口文件必须放在srv目录下,文件名就是接口名(比如MoveRobot.srv)。

  • 先创建srv目录:mkdir -p src/custom_srv_interfaces/srv
  • 创建并编辑MoveRobot.srvtouch src/custom_srv_interfaces/srv/MoveRobot.srv
    文件内容如下(请求是“移动距离”,响应是“移动后的位置”):
# 请求:机器人需要移动的距离(正数前进,负数后退)
float32 distance
---
# 响应:移动后的当前位置
float32 pose
3. 配置CMakeLists.txt:生成接口代码

修改src/custom_srv_interfaces/CMakeLists.txt,告诉编译器“要生成这个.srv文件的代码”:

# 找到依赖包
find_package(ament_cmake REQUIRED)
find_package(rosidl_default_generators REQUIRED)# 声明要生成的接口文件(这里是MoveRobot.srv)
rosidl_generate_interfaces(${PROJECT_NAME}"srv/MoveRobot.srv"  # 自定义的服务接口文件
)# 标记功能包为接口包(让其他功能包能找到这个接口)
ament_package()
4. 配置package.xml:声明依赖和接口组

修改src/custom_srv_interfaces/package.xml,添加编译依赖和接口组声明(否则其他功能包找不到这个接口):

<!-- 编译依赖:告诉ROS2编译时需要这些包 -->
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>rosidl_default_generators</depend><!-- 关键:声明这个包是接口包,让其他包能引用它的接口 -->
<member_of_group>rosidl_interface_packages</member_of_group><!-- 测试依赖(可选,新手暂时用不到) -->
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
5. 编译接口包:生成可调用的代码

回到工作空间根目录,编译接口包:

colcon build --packages-select custom_srv_interfaces

编译完成后,ROS2会自动生成C++和Python的接口代码:

  • C++头文件路径:install/custom_srv_interfaces/include/custom_srv_interfaces/srv/move_robot.hpp(后续C++节点会引用这个头文件)
  • 不用手动管生成的代码,后续直接调用就行——我当年第一次自定义接口时,以为要自己写头文件,白忙活了半天。

三、用C++写服务节点:客户端发请求,服务端处理

有了自定义接口,接下来写服务端和客户端节点:服务端模拟机器人移动(接收“移动距离”请求,返回“最终位置”),客户端发送移动请求并接收响应。

1. 创建C++服务功能包

先创建依赖rclcpp和自定义接口的功能包:

cd topic_ws/src
# 创建C++功能包,依赖rclcpp和自定义接口custom_srv_interfaces
ros2 pkg create service_cpp --build-type ament_cmake --dependencies rclcpp custom_srv_interfaces
cd ..
2. 写服务端节点:处理“移动请求”

服务端的核心逻辑是:初始化节点→创建服务端→绑定请求处理回调(收到请求后模拟移动,返回响应)。
文件路径:src/service_cpp/src/service_server.cpp,代码如下:

#include "rclcpp/rclcpp.hpp"
// 引用自定义服务接口的头文件(编译后自动生成)
#include "custom_srv_interfaces/srv/move_robot.hpp"
#include <chrono>
#include <thread>// 给自定义接口起个别名,后续写代码更简洁
using MoveRobot = custom_srv_interfaces::srv::MoveRobot;
// 时间别名,方便用500ms休眠
using namespace std::chrono_literals;class ServiceServerNode : public rclcpp::Node
{
public:ServiceServerNode(std::string node_name) : Node(node_name), current_pose_(0.0){RCLCPP_INFO(this->get_logger(), "%s节点已启动!", node_name.c_str());// 创建服务端:服务名"move_robot_srv",绑定请求处理回调service_ = this->create_service<MoveRobot>("move_robot_srv",  // 服务名(客户端必须和这个一致)// 绑定回调函数:收到请求后调用handle_move_requeststd::bind(&ServiceServerNode::handle_move_request, this, std::placeholders::_1, std::placeholders::_2));}private:// 请求处理回调函数:参数1是请求,参数2是响应void handle_move_request(const std::shared_ptr<MoveRobot::Request> request,const std::shared_ptr<MoveRobot::Response> response){// 打印收到的请求:移动距离和当前位置RCLCPP_INFO(this->get_logger(), "收到请求:移动距离=%.2f,当前位置=%.2f", request->distance, current_pose_);// 模拟机器人移动:每步移动剩余距离的10%,休眠500ms(模拟真实移动过程)float target_pose = current_pose_ + request->distance;  // 目标位置while (std::fabs(target_pose - current_pose_) > 0.01)  // 误差小于0.01时停止{float step = (target_pose - current_pose_) * 0.1;  // 每步移动距离current_pose_ += step;RCLCPP_INFO(this->get_logger(), "移动中:当前位置=%.2f", current_pose_);std::this_thread::sleep_for(500ms);  // 休眠500ms}// 设置响应:返回最终位置response->pose = current_pose_;RCLCPP_INFO(this->get_logger(), "响应请求:最终位置=%.2f", response->pose);}// 服务端指针(类型是自定义的MoveRobot服务)rclcpp::Service<MoveRobot>::SharedPtr service_;float current_pose_;  // 机器人当前位置(初始为0)
};int main(int argc, char **argv)
{rclcpp::init(argc, argv);auto node = std::make_shared<ServiceServerNode>("move_robot_server");rclcpp::spin(node);  // 循环运行,等待请求rclcpp::shutdown();return 0;
}
3. 写客户端节点:发送“移动请求”

客户端的核心逻辑是:初始化节点→创建客户端→等待服务端上线→发送请求→接收响应。
文件路径:src/service_cpp/src/service_client.cpp,代码如下:

#include "rclcpp/rclcpp.hpp"
#include "custom_srv_interfaces/srv/move_robot.hpp"using MoveRobot = custom_srv_interfaces::srv::MoveRobot;
using namespace std::chrono_literals;class ServiceClientNode : public rclcpp::Node
{
public:ServiceClientNode(std::string node_name) : Node(node_name){RCLCPP_INFO(this->get_logger(), "%s节点已启动!", node_name.c_str());// 创建客户端:服务名"move_robot_srv"(必须和服务端一致)client_ = this->create_client<MoveRobot>("move_robot_srv");// 定时器:启动后1s发送请求(确保服务端有时间启动,避免请求发送失败)timer_ = this->create_wall_timer(1s,  // 1s后触发std::bind(&ServiceClientNode::send_move_request, this));}// 发送请求的函数void send_move_request(){timer_->cancel();  // 取消定时器:只发送一次请求(避免重复发)// 等待服务端上线:超时1s,最多等10次(防止一直等下去)while (!client_->wait_for_service(1s)){if (!rclcpp::ok())  // 如果节点被中断(比如按Ctrl+C),就退出{RCLCPP_ERROR(this->get_logger(), "等待服务时节点被中断!");return;}RCLCPP_INFO(this->get_logger(), "等待服务端上线...");}// 构造请求数据:移动距离=5.0(前进5个单位)auto request = std::make_shared<MoveRobot::Request>();request->distance = 5.0;// 发送异步请求:绑定响应回调(收到响应后执行response_callback)auto result_future = client_->async_send_request(request, std::bind(&ServiceClientNode::response_callback, this, std::placeholders::_1));}// 响应回调函数:收到服务端响应后执行void response_callback(const rclcpp::Client<MoveRobot>::SharedFuture future){auto response = future.get();  // 获取响应结果RCLCPP_INFO(this->get_logger(), "收到响应:移动后位置=%.2f", response->pose);}private:rclcpp::Client<MoveRobot>::SharedPtr client_;  // 客户端指针rclcpp::TimerBase::SharedPtr timer_;  // 定时器指针(用于延迟发送请求)
};int main(int argc, char **argv)
{rclcpp::init(argc, argv);auto node = std::make_shared<ServiceClientNode>("move_robot_client");rclcpp::spin(node);  // 循环等待响应rclcpp::shutdown();return 0;
}
4. 配置CMakeLists.txt:编译服务节点

修改src/service_cpp/CMakeLists.txt,添加编译规则(和话题节点类似,只是依赖多了自定义接口):

# 找到依赖包:rclcpp和自定义的custom_srv_interfaces
find_package(rclcpp REQUIRED)
find_package(custom_srv_interfaces REQUIRED)# 编译服务端节点
add_executable(move_robot_server src/service_server.cpp)
ament_target_dependencies(move_robot_server rclcpp custom_srv_interfaces)# 编译客户端节点
add_executable(move_robot_client src/service_client.cpp)
ament_target_dependencies(move_robot_client rclcpp custom_srv_interfaces)# 安装节点到install目录
install(TARGETSmove_robot_servermove_robot_clientDESTINATION lib/${PROJECT_NAME}
)
5. 编译运行:验证服务通信
  • 编译:回到工作空间根目录,编译service_cpp功能包:
    colcon build --packages-select service_cpp
    
  • 运行服务端:终端1执行(必须先启动服务端,客户端才能发请求):
    source install/setup.bash && ros2 run service_cpp move_robot_server
    
    看到日志:move_robot_server节点已启动!,服务端开始等待请求。
  • 运行客户端:终端2执行:
    source install/setup.bash && ros2 run service_cpp move_robot_client
    
    客户端会先打印“等待服务端上线…”(如果服务端已启动,会直接发请求),然后服务端打印“移动中”的日志,最后客户端打印“收到响应:移动后位置=5.00”——服务通信成功!

看展示:
在这里插入图片描述

github源码地址:https://github.com/Timnf/github/tree/main/ros

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

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

相关文章

Function Calling(智能客服)

目录 1.0.思路分析 1.1.基础CRUD 1.1.1.数据库表 1.1.2.引入依赖 1.1.3.配置数据库 1.1.4.基础代码 2.定义Function 2.1.课程表的字段&#xff1a; 2.2.定义Function 2.3.System提示词 2.4.配置ChatClient 3.编写Controller 3.1.解决兼容性问题 3.2.AlibabaOpenA…

探索淀粉深加工的无限可能:2026 济南展览会前瞻

在全球农产品加工的广阔版图中&#xff0c;淀粉深加工产业犹如一颗璀璨的明珠&#xff0c;散发着日益耀眼的光芒。其产品广泛渗透于食品、饮料、医药、化工、能源等诸多领域&#xff0c;宛如一条条无形的纽带&#xff0c;将各个行业紧密相连。随着技术的日新月异、政策的大力扶…

STAGEWISE实战指南:从集成到使用的完整解决方案

文章目录 一、前言 二、集成STAGEWISE的实战过程 1. 初始配置问题 2. 依赖冲突处理 3. 组件导入问题 四、标准集成方案 1. 完整安装步骤 2. Vue项目集成步骤 (1) 修改App.vue文件 (2) 配置文件说明 五、最佳实践 1. 开发规范 2. 常见问题排查 五、总结 一、前言 在前端开发中,…

使用astah制作专业状态图及C/C++实现解析

<摘要> 本文详细解析如何使用astah专业工具绘制高质量的UML状态图&#xff0c;并建立与C/C代码的完整映射关系。内容涵盖状态图核心概念、astah工具实操指南、触发机制(Trigger)、守卫条件(Guard)和动作(Action)的代码实现解析&#xff0c;并通过一个完整的用户登录状态机…

C语言————函数递归(通俗易懂)

我们在学习些新的函数时&#xff0c;首先我们得理解它是什么&#xff1f;是怎么定义的&#xff1f;然后去了解他的用途&#xff0c;最后我们自己要会用&#xff0c;知道用在什么地方&#xff1f;什么时候用&#xff1f;用的时候要注意些什么&#xff1f;有一个条理清晰的学习逻…

路由基础(三):静态路由、动态路由、默认路由

静态路由 静态路由&#xff1a;管理员使用手工方式为路由器添加路由 三种添加静态路由的方式&#xff1a; 配置下一跳配置出接口出接口和下一跳都配置 备注&#xff1a;不配置出接口时&#xff0c;路由器会进行路由递归查询 #添加去往10.1.1.0网段的静态路由&#xff0c;下一跳…

大模型开发之:LangChain4j【附资料】

什么是 LangChain4j&#xff1f; LangChain4j 是一个专为 Java 生态系统设计的开源&#xff08;Apache 2.0 许可&#xff09;框架&#xff0c;用于简化基于大语言模型&#xff08;LLM&#xff09;的应用程序开发。它的名字和灵感来源于其"前辈"——为 Python 设计的 …

SyntaxError: Failed to execute ‘open‘ on ‘XMLHttpRequest‘: Invalid URL

这就是在ajax请求的时候URL不正确&#xff0c; 例如&#xff1a; http://192.168.124.168:8082api/v1/task/get 正确的是这样的&#xff1a; http://192.168.124.168:8082/api/v1/task/get 这个错误的来源是 baseUrl apiUrl 导致的&#xff0c; 比如baseUrl http://19…

程序代码篇---类

为什么有类&#xff1a;要理解编程语言中为什么会有 “类”&#xff0c;我们可以从日常生活的例子入手。想象你要描述 “汽车” 这个事物&#xff1a;它有属性&#xff08;颜色、品牌、排量&#xff09;它有行为&#xff08;行驶、刹车、鸣笛&#xff09;如果没有类&#xff0c…

jenkins备份迁移

1、确认Jenkins版本 在web界面操作步骤&#xff1a;登录Jenkins管理控制台点击左上角"Jenkins"图标选择"Manage Jenkins" > "About Jenkins"在页面中查看"Version"字段显示的具体版本号&#xff08;如2.479.2&#xff09; 建议截图…

Video Ocean 接入 GPT-5

Video Ocean&#xff1a;全球首个接入 GPT-5 的视频智能体引领 AI 视频创作革命 一、技术全景&#xff1a;Video Ocean 的架构与创新 1.1 全球首个 GPT-5 视频智能体的技术突破 Video Ocean 作为全球首个接入 GPT-5 的视频智能体&#xff0c;代表了 AI 视频生成领域的重大突…

如何在API高并发中玩转资源隔离与限流策略?

url: /posts/4ad4ec1dbd80bcf5670fb397ca7cc68c/ title: 如何在API高并发中玩转资源隔离与限流策略? date: 2025-08-27T23:26:45+08:00 lastmod: 2025-08-27T23:26:45+08:00 author: cmdragon summary: 资源隔离是保障API稳定性的核心,通过路由隔离和依赖隔离实现关键业务与…

Swift 解法详解 LeetCode 365:水壶问题

文章目录摘要描述题解答案题解代码分析代码拆解示例测试及结果时间复杂度空间复杂度总结摘要 这道题其实就是经典的 “两个水壶问题”&#xff0c;你可能在电影《虎胆龙威3》里见过&#xff0c;布鲁斯威利斯用两个水壶精确量出 4 升水来解除炸弹。这题就是把那个场景搬到了编程…

Redis集群介绍——主从、哨兵、集群

Redis集群介绍 集群里有三大模式&#xff1a; Redis主从模式&#xff1a;一主一从或一主多从&#xff0c;自带读写分离&#xff0c;负载均衡&#xff1b; Redis哨兵模式&#xff1a;高可用&#xff0c;主服务器宕机&#xff0c;从服务器变为主服务器&#xff1b; Redis集群…

【大前端】封装一个React Native与Android/IOS 端通用的埋点接口

RN 层只暴露一个统一的埋点方法&#xff0c;例如 trackEvent(eventName, params)&#xff0c;内部通过 NativeModule 调用 Android/iOS 的原生埋点 SDK。这样 RN 层不用关心具体实现。写一个通用的示例&#xff1a;1. RN 层封装统一接口&#xff08;JS 代码&#xff09;// anal…

详解 外部负载均衡器 → Ingress Controller Pod 这个过程

在常见的生产架构中&#xff1a; 外部负载均衡器&#xff08;Ng/ELB/ALB/NLB等&#xff09;终止来自客户端的 HTTPS 连接。 它将解密后的明文 HTTP 请求转发给后端的 Kubernetes Ingress Controller Pods。 Ingress Controller 处理 HTTP 请求&#xff0c;并将 HTTP 响应返回给…

Markdown 编辑器 语法

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注…

【PyTorch项目实战】SAM(Segment Anything Model) —— 致力于建立第一个图像分割基础模型

文章目录一、SAM&#xff08;Segment Anything Model&#xff09; —— 致力于建立第一个图像分割基础模型&#xff08;Foundation Model&#xff09;1、项目背景2、核心任务设计3、模型架构&#xff1a;图像编码器 提示编码器 掩码解码器4、核心创新&#xff1a;可提示分割任…

第一次实习总结

开发模式的转变现在虽然不怎么使用很传统的软件开发模型了&#xff0c;但是好歹也要敏捷开发吧。事实上&#xff0c;我这个小厂甚至做的更绝。全程无UML。。。需要一天&#xff1a;1.项目组长与客户进行需求对接。2.项目组长然后就找我来讲述需求&#xff0c;我就直接制作出原型…

创建uniApp小程序项目vue3+ts+uniapp

vscode创建pnpm i -D types/wechat-miniprogram uni-helper/uni-app-types{"compilerOptions": {"types": ["dcloudio/types","types/wechat-miniprogram","uni-helper/uni-app-types"] },"vueCompilerOptions": …