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

Jetson Nano上ROS服务通信实战:从概念到调试全解析

1. 项目缘起与核心目标

最近在Jetson Nano上折腾ROS,跟着赵虚左老师的《ROS理论与实践》课程一路学下来,到了第三章服务通信这块。说实话,之前话题通信和订阅发布模型玩得挺溜,但服务通信(Service)这个“一问一答”的模式,刚开始接触时总觉得有点别扭,不像话题那样“滔滔不绝”。尤其是在资源受限的Jetson Nano(Ubuntu 18.04 + ROS Melodic)上,从零开始搭建服务通信的客户端(Client)和服务端(Server),每一步都可能遇到在x86电脑上不太会出现的“坑”。比如,Python节点的依赖管理、ROS Master的通信稳定性,还有在ARM架构下一些库的细微差别。

这篇文章,就是我在Nano上“啃”下服务通信这一章的实战记录。我不会照本宣科地复述教程,而是聚焦于如何在真实的、资源有限的嵌入式开发板上,把一个ROS服务从概念变成可稳定运行的代码。你会看到我如何创建功能包、编写Python服务端和客户端、处理参数传递,以及最关键的部分——在Nano上调试服务通信时遇到的典型问题及其解决方案。无论你是刚拿到Jetson Nano的ROS新手,还是在其他嵌入式平台学习ROS,希望这些踩坑经验能帮你少走弯路。

2. 服务通信模型深度解析:为什么是“一问一答”?

在开始敲代码之前,我们必须先彻底搞懂服务通信(Service)在ROS架构里到底扮演什么角色,它和之前学的发布订阅(Topic)本质区别在哪里。很多人一开始会混淆,觉得不就是发送接收消息吗?但它们的应用场景和通信模式截然不同。

2.1 服务 vs. 话题:同步与异步的本质区别

你可以把话题通信想象成广播电台。一个节点(发布者)不断地向某个频道(话题)发送消息,它并不关心有没有听众(订阅者),也不关心听众收到后会不会回复。这是一种单向、异步、多对多的通信。比如,你的传感器节点持续发布摄像头图像数据到/camera/image话题,导航节点订阅它来进行路径规划。发布者只管发,订阅者只管收,两者在时间上是解耦的。

服务通信则更像是一次电话呼叫。客户端(Client)发起一个具体的请求(Request),然后主动等待,直到服务端(Server)处理完这个请求并返回一个响应(Response)。这是一个双向、同步、一对一的请求-响应模型。客户端在收到响应之前,其调用线程通常是阻塞的(当然也可以实现异步调用)。典型的应用场景包括:让机器人移动到某个指定位置(客户端发送目标坐标,服务端执行移动并返回是否成功)、查询传感器状态、或者执行一次计算任务(如求解逆运动学)。

在赵虚左老师的课程中,这个模型被用于实现两个整数求和的服务:客户端发送两个整数a和b,服务端计算a+b并返回结果。这个例子虽小,但完美诠释了服务的核心:客户端需要得到一个明确的、即时的答案

2.2 服务通信在资源受限设备上的特殊考量

在Jetson Nano这类设备上,理解这个模型尤为重要。因为同步调用意味着客户端在等待响应时,会占用一个线程资源。如果服务端处理缓慢(比如涉及大量计算),或者网络有延迟,客户端就可能被长时间阻塞,影响整个系统的实时性。因此,在设计Nano上的ROS服务时,我们必须问自己几个问题:

  1. 服务处理是否耗时?如果耗时,是否可以考虑将任务拆解,或用话题+状态机的方式实现异步通知?
  2. 服务的响应时间要求多高?这决定了我们能否接受同步阻塞。
  3. 服务端是否可能成为瓶颈?Nano的CPU性能有限,一个设计不良的重计算服务可能拖垮整个节点。

对于我们的加法服务例子,计算是瞬时的,所以同步模型非常合适。但如果你要在Nano上做一个图像识别服务,客户端发送一张图片,服务端用深度学习模型推理后返回结果,这个处理过程可能就需要几百毫秒甚至更久。这时,你就需要仔细设计,比如在服务端内部使用多线程或队列来处理并发请求,避免一个慢请求阻塞后续所有请求。

3. Jetson Nano开发环境搭建与功能包创建

工欲善其事,必先利其器。在Nano上做ROS开发,第一步就是确保你的基础环境是干净、可用的。我使用的是Ubuntu 18.04 with ROS Melodic,这是官方为Nano长期支持的版本组合,稳定性最好。

3.1 基础ROS环境验证

在创建我们自己的功能包之前,先打开终端,运行几个命令确保ROS核心功能正常:

# 1. 检查ROS环境是否已正确加载 echo $ROS_PACKAGE_PATH # 应该输出包含`/opt/ros/melodic/share`的路径 # 2. 启动ROS Master和基础日志节点(在一个终端运行) roscore

保持roscore运行。这是所有ROS节点进行通信的“总机”,服务通信同样依赖它来查找和连接服务端。

3.2 创建专属的功能包(Package)

ROS的代码是以功能包为单元组织的。我们将创建一个专门用于练习服务通信的包。

# 切换到你的ROS工作空间src目录下,例如 ~/catkin_ws/src cd ~/catkin_ws/src # 使用catkin_create_pkg命令创建包。这里的关键是: # learning_service:包名。 # std_msgs rospy roscpp:依赖项。 # std_msgs: 包含了ROS标准消息类型,我们定义服务时会用到。 # rospy: 我们要用Python编写节点,所以需要Python客户端库。 # roscpp: 虽然本篇用Python,但通常也加上C++依赖以备后用。 catkin_create_pkg learning_service std_msgs rospy roscpp

创建成功后,你会看到learning_service目录,里面自动生成了CMakeLists.txtpackage.xml在Nano上,我强烈建议在创建包后立即执行一次工作空间的编译,即使还没写代码。这可以提前暴露一些环境配置问题。

cd ~/catkin_ws catkin_make source devel/setup.bash

3.3 创建服务定义文件(.srv)

服务通信的核心是数据格式的约定。客户端发送什么(Request),服务端返回什么(Response),都需要在一个.srv文件中明确定义。这个文件相当于服务的“接口合同”。

在我们的加法服务例子中,合同很简单:请求是两个整数,响应是一个整数之和。

  1. learning_service包目录下创建srv文件夹:
    cd ~/catkin_ws/src/learning_service mkdir srv
  2. srv目录下创建AddTwoInts.srv文件,内容如下:
    # 请求部分(客户端发送) int64 a int64 b --- # 响应部分(服务端返回) int64 sum
    注意:三个短横线---是分隔符,上方是请求,下方是响应。int64是ROS内置的基本数据类型,对应Python的int

3.4 配置编译系统以识别服务定义

创建了.srv文件后,ROS编译系统并不会自动处理它。我们需要修改两个配置文件:

  1. 修改package.xml:确保它声明了生成消息所需的依赖。 找到<buildtool_depend>catkin</buildtool_depend>,在其后添加:

    <build_depend>message_generation</build_depend> <exec_depend>message_runtime</exec_depend>

    message_generation是编译时生成消息/服务代码的依赖,message_runtime是运行时依赖。

  2. 修改CMakeLists.txt:这是关键步骤,容易出错。

    • 找到find_package:确保它包含了message_generation
      find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation # 添加这一行 )
    • 找到add_service_files:取消注释并添加我们的.srv文件。
      add_service_files( FILES AddTwoInts.srv )
    • 找到generate_messages:确保它被调用,并且依赖了std_msgs
      generate_messages( DEPENDENCIES std_msgs )
    • 找到catkin_package:确保CATKIN_DEPENDS中包含了message_runtime
      catkin_package( # INCLUDE_DIRS include # LIBRARIES learning_service CATKIN_DEPENDS roscpp rospy std_msgs message_runtime # 确保有message_runtime # DEPENDS system_lib )

3.5 编译并验证服务生成

配置完成后,回到工作空间根目录进行编译:

cd ~/catkin_ws catkin_make

编译成功后,最关键的一步来了:验证服务定义是否已正确生成Python代码。这是很多新手会卡住的地方,尤其是在交叉编译或环境配置有误时。

# 首先,重新source环境,使新生成的包生效 source devel/setup.bash # 然后,尝试导入生成的服务模块 python3 -c "from learning_service.srv import AddTwoInts, AddTwoIntsRequest, AddTwoIntsResponse; print('Service import successful!')"

如果这条命令能成功执行,没有报ImportError,恭喜你,服务定义的环境已经准备就绪。如果失败,请依次检查:

  1. CMakeLists.txtpackage.xml的修改是否正确。
  2. 编译过程是否有错误输出。
  3. 是否执行了source devel/setup.bash在Nano上,我习惯把这条命令加到~/.bashrc末尾,避免每次开终端都要手动source。

4. 手把手编写Python服务端与客户端

环境搞定,现在进入核心编码环节。我们将分别创建服务端节点(server.py)和客户端节点(client.py)。

4.1 服务端(Server)实现:等待与响应

服务端的职责是:

  1. 初始化一个ROS节点。
  2. 向ROS Master注册一个服务(例如/add_two_ints)。
  3. 进入循环,等待客户端的请求。
  4. 收到请求后,执行处理逻辑(这里是加法计算)。
  5. 将处理结果封装成响应,返回给客户端。

learning_service包下创建scripts文件夹(如果不存在),然后创建server.py

cd ~/catkin_ws/src/learning_service mkdir -p scripts touch scripts/server.py chmod +x scripts/server.py # 赋予可执行权限

编辑server.py,代码如下:

#!/usr/bin/env python3 # -*- coding: utf-8 -*- import rospy from learning_service.srv import AddTwoInts, AddTwoIntsResponse def handle_add_two_ints(req): """ 服务请求处理回调函数。 当客户端调用服务时,ROS会调用此函数,并传入一个AddTwoIntsRequest对象(这里命名为req)。 """ rospy.loginfo("Received request: a=%d, b=%d", req.a, req.b) # 执行核心逻辑:加法计算 sum_val = req.a + req.b rospy.loginfo("Returning response: [%d + %d = %d]", req.a, req.b, sum_val) # 返回一个AddTwoIntsResponse对象。注意:在Python中,可以直接返回一个元组或一个Response对象。 # 这里我们显式创建Response对象,更清晰。 return AddTwoIntsResponse(sum_val) def add_two_ints_server(): """ 初始化服务端节点。 """ # 初始化节点,节点名必须唯一。匿名节点(anonymous=True)可以避免多次启动时的命名冲突。 rospy.init_node('add_two_ints_server', anonymous=True) # 创建服务(Service)。 # 参数1:服务名称,客户端将通过这个名称来调用服务。 # 参数2:服务类型,即我们定义的AddTwoInts。 # 参数3:处理回调函数,收到请求时自动调用。 s = rospy.Service('add_two_ints', AddTwoInts, handle_add_two_ints) rospy.loginfo("Ready to add two ints.") # rospy.spin()使节点保持运行,直到节点被关闭。它会阻塞在这里,等待请求到来。 rospy.spin() if __name__ == "__main__": try: add_two_ints_server() except rospy.ROSInterruptException: pass

代码要点与Nano实操注意:

  • #!/usr/bin/env python3:指定解释器为python3。在Ubuntu 18.04上,默认的python命令可能指向Python 2.7,而ROS Melodic同时支持Python 2和3。明确使用python3可以避免版本混乱。
  • rospy.init_node:节点名add_two_ints_server。如果多次启动同一个节点名,ROS会报错。anonymous=True参数会在节点名后附加一个随机数,便于调试。
  • rospy.Service:这是服务创建的核心。服务名/add_two_ints是客户端查找的依据。
  • rospy.spin():这是一个非常重要的调用。它让节点进入等待循环,不会让脚本立即退出。如果没有它,你的服务端节点注册完服务后就退出了,客户端根本找不到它。

4.2 客户端(Client)实现:发起请求与接收响应

客户端的职责是:

  1. 初始化一个ROS节点。
  2. 等待指定的服务可用(rospy.wait_for_service)。
  3. 创建一个服务代理(rospy.ServiceProxy)。
  4. 构造请求数据(AddTwoIntsRequest或直接传参)。
  5. 调用服务代理,并获取响应。

scripts目录下创建client.py

touch scripts/client.py chmod +x scripts/client.py

编辑client.py,代码如下:

#!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys import rospy from learning_service.srv import AddTwoInts, AddTwoIntsRequest def add_two_ints_client(x, y): """ 服务客户端函数。 """ # 首先,等待名为'add_two_ints'的服务变得可用。 # 这是一个好习惯,避免服务端还没启动,客户端就尝试调用导致失败。 rospy.loginfo("Waiting for service 'add_two_ints' to become available...") rospy.wait_for_service('add_two_ints') rospy.loginfo("Service available!") try: # 创建服务代理(Service Proxy)。这就像拿到了服务的“电话号码”。 add_two_ints_proxy = rospy.ServiceProxy('add_two_ints', AddTwoInts) # 方法一:直接传递参数,ROS会帮你封装成Request。这是最简洁的方式。 rospy.loginfo("Calling service with args: %d, %d", x, y) resp1 = add_two_ints_proxy(x, y) rospy.loginfo("Response (direct args): %d", resp1.sum) # 方法二:显式创建Request对象,再调用。这种方式更直观,便于理解数据结构。 req = AddTwoIntsRequest() req.a = 10 req.b = 20 rospy.loginfo("Calling service with request object: a=%d, b=%d", req.a, req.b) resp2 = add_two_ints_proxy(req) # 也可以写成 add_two_ints_proxy(req.a, req.b) rospy.loginfo("Response (request object): %d", resp2.sum) return resp1.sum, resp2.sum except rospy.ServiceException as e: # 服务调用可能失败,例如服务端处理异常、通信超时等。 rospy.logerr("Service call failed: %s", e) return None, None if __name__ == "__main__": # 初始化客户端节点。注意节点名不要和服务端重复。 rospy.init_node('add_two_ints_client', anonymous=True) # 从命令行参数获取要相加的两个数,如果没有提供,则使用默认值1和2。 if len(sys.argv) == 3: try: x = int(sys.argv[1]) y = int(sys.argv[2]) except ValueError: rospy.logerr("Invalid arguments. Using defaults (1, 2).") x, y = 1, 2 else: rospy.loginfo("No arguments provided. Using defaults (1, 2).") x, y = 1, 2 rospy.loginfo("Requesting addition of %d and %d", x, y) result = add_two_ints_client(x, y) if result[0] is not None: rospy.loginfo("Final results: %d and %d", result[0], result[1])

代码要点与Nano实操注意:

  • rospy.wait_for_service这是客户端健壮性的关键。在Nano上,由于启动顺序或系统负载,服务端可能比客户端晚一点准备好。不加这行代码,客户端可能立即调用失败。你可以设置超时参数,例如rospy.wait_for_service('add_two_ints', timeout=5),表示最多等5秒。
  • rospy.ServiceProxy:创建代理后,调用它就像调用一个本地函数一样。但要注意,这是一个阻塞调用。在收到响应或超时之前,程序会停在这里。对于耗时服务,需要考虑使用多线程或异步调用(roscpp中有async方法,rospy中需结合多线程实现)。
  • 命令行参数:客户端设计了从sys.argv读取参数的功能,这样你可以灵活测试:rosrun learning_service client.py 5 10

5. 在Jetson Nano上运行与深度调试

代码写完了,最激动人心的环节就是让它跑起来。在Nano上运行ROS节点,你需要打开至少两个终端(或者使用tmuxscreen这类终端复用工具)。

5.1 启动服务与调用服务

  1. 终端1:启动ROS Master和roscore(如果还没启动)

    roscore
  2. 终端2:启动服务端节点

    # 确保已经source了工作空间的环境 source ~/catkin_ws/devel/setup.bash # 启动服务端 rosrun learning_service server.py

    如果一切正常,你将看到输出:[INFO] [时间戳]: Ready to add two ints.

  3. 终端3:启动客户端节点

    source ~/catkin_ws/devel/setup.bash # 使用默认参数(1和2) rosrun learning_service client.py # 或者指定参数 rosrun learning_service client.py 100 200

    在客户端终端,你应该看到类似以下的输出:

    [INFO] [时间戳]: Waiting for service 'add_two_ints' to become available... [INFO] [时间戳]: Service available! [INFO] [时间戳]: Requesting addition of 100 and 200 [INFO] [时间戳]: Calling service with args: 100, 200 [INFO] [时间戳]: Response (direct args): 300 [INFO] [时间戳]: Calling service with request object: a=10, b=20 [INFO] [时间戳]: Response (request object): 30 [INFO] [时间戳]: Final results: 300 and 30

    同时,在服务端终端,你会看到:

    [INFO] [时间戳]: Received request: a=100, b=200 [INFO] [时间戳]: Returning response: [100 + 200 = 300] [INFO] [时间戳]: Received request: a=10, b=20 [INFO] [时间戳]: Returning response: [10 + 20 = 30]

    恭喜!一个完整的ROS服务通信流程已经在你的Jetson Nano上跑通了。

5.2 使用命令行工具进行探测与调试

除了运行节点,ROS提供了强大的命令行工具来观察系统状态,这对于调试至关重要。

  • rosservice list:列出当前系统中所有已注册的服务。运行客户端前后各执行一次,可以看到/add_two_ints服务的出现。
  • rosservice info /add_two_ints:查看某个服务的详细信息,包括服务类型、提供该服务的节点(Node)、URI等。
  • rosservice type /add_two_ints:查看服务的类型,输出应为learning_service/AddTwoInts
  • rosservice call /add_two_ints [args]最强大的工具!你可以不写客户端代码,直接用命令行调用服务。
    rosservice call /add_two_ints "a: 5 b: 3"
    注意YAML格式,a:b:后面有空格。如果成功,会返回:sum: 8。这个功能在快速测试服务端逻辑时非常有用。

5.3 Jetson Nano专属问题排查实录

在Nano上,我遇到了几个典型问题,这里分享排查思路:

问题一:ImportError: No module named 'learning_service.srv'

  • 现象:运行rosrun时,Python报错找不到自定义的服务模块。
  • 排查
    1. 检查编译:首先确认catkin_make是否成功,且没有关于learning_service的报错。
    2. 检查环境:运行echo $ROS_PACKAGE_PATH,查看你的工作空间路径(如/home/nano/catkin_ws/src)是否在其中。每次新开终端,都必须source devel/setup.bash
    3. 检查生成文件:到~/catkin_ws/devel/lib/python2.7/dist-packages/learning_service(Python2)或~/catkin_ws/devel/lib/python3/dist-packages/learning_service(Python3)目录下,查看是否有srv文件夹以及里面的_AddTwoInts.py等文件。在Ubuntu 18.04 + ROS Melodic的默认配置下,catkin_make生成的文件可能在Python2.7的目录下,但你的脚本用python3运行。这是最常见的坑!
  • 解决
    • 方案A(推荐):在CMakeLists.txt中强制指定生成Python3的代码。在find_package(catkin...)之前或之后添加:
      # 设置使用Python3 find_package(PythonLibs 3 REQUIRED) # 或者,如果上述不行,尝试更明确地指定PYTHON_EXECUTABLE # set(PYTHON_EXECUTABLE /usr/bin/python3)
      然后重新catkin_make。但这有时会因ROS Melodic的默认配置而复杂。
    • 方案B(实用):直接修改你的Python脚本,在文件最开头(#!/usr/bin/env python3之后)添加以下代码,将Python3的site-packages路径加入搜索路径:
      import sys sys.path.append('/home/nano/catkin_ws/devel/lib/python2.7/dist-packages')
      /home/nano替换为你的实际家目录。这是一个“硬编码”的解决方案,不够优雅但能快速解决问题。
    • 方案C(一劳永逸):将你的工作空间devel目录下的Python3路径(如果存在)或Python2路径,通过PYTHONPATH环境变量永久导出。在~/.bashrc中添加:
      export PYTHONPATH=$PYTHONPATH:/home/nano/catkin_ws/devel/lib/python2.7/dist-packages
      然后source ~/.bashrc

问题二:服务调用超时或无响应

  • 现象:客户端一直卡在Waiting for service...,或者调用后长时间无响应然后超时。
  • 排查
    1. 检查roscore:确保roscore正在运行。用rosnode list查看,应该能看到/rosout
    2. 检查服务端节点:用rosnode list查看/add_two_ints_server是否存在。用rosnode info /add_two_ints_server查看其状态和发布/订阅的服务。
    3. 检查网络配置(多机通信时):如果在多台机器上运行,需要正确设置ROS_MASTER_URIROS_HOSTNAME。在Nano单机上,通常没问题。
    4. 检查防火墙:虽然单机本地通信一般不受影响,但可以检查一下是否有防火墙规则阻止了本地回环地址的通信(极少见)。
  • 解决:确保启动顺序:先roscore,再服务端,最后客户端。使用rospy.wait_for_service并合理设置超时时间。

问题三:服务端处理函数崩溃导致客户端无响应

  • 现象:服务端在处理请求时抛出未捕获的异常(例如除零错误、类型转换错误),导致整个服务端节点崩溃。客户端会一直等待直到超时(默认超时时间可能很长)。
  • 模拟与解决:在服务端的handle_add_two_ints函数里故意制造一个错误:
    def handle_add_two_ints(req): rospy.loginfo("Received request: a=%d, b=%d", req.a, req.b) # 模拟一个运行时错误 if req.b == 0: result = req.a / req.b # 除零错误! else: result = req.a + req.b return AddTwoIntsResponse(result)
    当客户端传入b=0时,服务端崩溃,客户端卡住。教训:服务端的回调函数内部一定要做好异常捕获(try-except),并返回一个合理的错误响应,或者使用rospy.logerr记录日志后返回。一个健壮的服务端应该像这样:
    def handle_add_two_ints(req): try: rospy.loginfo("Received request: a=%d, b=%d", req.a, req.b) sum_val = req.a + req.b # 或者更复杂的逻辑 return AddTwoIntsResponse(sum_val) except Exception as e: rospy.logerr("Error processing request: %s", e) # 可以返回一个特殊的错误码,或者重新抛出异常让ROS框架处理(不推荐) # 简单起见,这里返回一个标识错误的结果,例如-1 return AddTwoIntsResponse(-1)

6. 从加法服务到真实场景:服务通信的进阶思考

通过加法例子,我们掌握了服务通信的基本流程。但在真实的机器人项目中,服务通信的应用要复杂和有趣得多。在Jetson Nano这样的边缘计算设备上,服务通信常常扮演着“决策中枢”或“任务触发器”的角色。

6.1 典型应用场景拓展

  1. 设备控制与状态查询

    • 场景:一个主控节点通过服务,命令Nano上的相机节点“开始录制”或“停止录制”。相机节点执行操作后,返回“成功”或“失败”。
    • 实现要点:服务定义可能包含命令枚举(如START, STOP)和状态码。服务端需要处理硬件资源(如相机句柄)的并发访问安全。
  2. 算法服务化

    • 场景:在Nano上部署了一个训练好的神经网络模型(如目标检测)。其他节点(如感知融合节点)将图像数据通过服务请求发送过来,Nano上的算法服务节点运行推理,并将检测结果(边框、类别)通过响应返回。
    • 实现要点:这是计算密集型服务。必须考虑服务端的并发处理能力。简单的rospy.Service是单线程的,一个请求处理完才处理下一个。对于耗时的推理,这会成为性能瓶颈。解决方案是使用多线程或ROS的动作库(ActionLib)。动作库更适合长时间运行、可抢占、有反馈的任务。
  3. 配置与参数设置

    • 场景:机器人启动后,通过服务动态设置某个算法模块的参数(如PID控制器的Kp, Ki, Kd)。
    • 实现要点:服务端需要将接收到的参数更新到内存中的配置变量,并可能持久化到文件。同时要考虑参数校验。

6.2 服务通信的局限性及替代方案

认识到服务的“同步阻塞”特性是其优势也是劣势后,我们就知道它不是万能的。

  • 局限性

    • 阻塞客户端:客户端在等待响应时不能做其他事。
    • 无中间状态反馈:对于长时间任务,客户端只知道开始和结束,不知道进度。
    • 服务端需高可用:如果服务端崩溃,所有客户端请求都会失败。
    • 不适合持续数据流:像传感器数据这种持续不断的流,用话题更合适。
  • 替代方案选择

    • 需要简单请求-响应,且处理快:用服务(Service)。本文内容。
    • 需要长时间运行、有进度反馈、可取消的任务:用动作(Action)。例如导航到目标点。
    • 需要单向、持续、多对多的数据流:用话题(Topic)。例如激光雷达数据。
    • 需要动态配置、且希望持久化:用参数服务器(Parameter Server)配合动态重配置(dynamic_reconfigure)更专业。

6.3 在Nano上优化服务性能的实践技巧

  1. 服务端多线程化:使用Python的threading库或rospyMultiThreadedService。这样服务端可以同时处理多个客户端请求。但要注意线程安全和资源竞争。

    # 示例:创建多线程服务(ROS Melodic的rospy似乎没有直接的MultiThreadedService,通常用ThreadPool) from concurrent.futures import ThreadPoolExecutor import rospy from learning_service.srv import AddTwoInts, AddTwoIntsResponse executor = ThreadPoolExecutor(max_workers=4) # 根据Nano的CPU核心数调整 def handle_add_two_ints(req): # ... 处理逻辑 ... return AddTwoIntsResponse(sum_val) def add_two_ints_server(): rospy.init_node('add_two_ints_server_mt') # 注意:标准rospy.Service是单线程的。 # 要实现多线程,一种模式是服务回调函数内部将任务提交给线程池,并立即返回一个“已接收”的响应。 # 真正的结果通过另一个话题或回调机制返回。这其实更接近“动作”的模式。 # 对于简单的计算,保持单线程可能更简单安全。 s = rospy.Service('add_two_ints_mt', AddTwoInts, handle_add_two_ints) rospy.spin() executor.shutdown()

    注意:对于简单的加法服务,多线程是杀鸡用牛刀。但对于图像处理、规划算法等耗时操作,这种模式值得考虑。更规范的做法是直接使用ROS的ActionLib

  2. 客户端设置超时:使用rospy.wait_for_servicerospy.ServiceProxy.call时,都提供超时参数,避免客户端无限期挂起。

    # 等待服务,最多5秒 rospy.wait_for_service('add_two_ints', timeout=5) # 调用服务,最多3秒 try: resp = add_two_ints_proxy.call(req, timeout=rospy.Duration(3)) except rospy.ROSException as e: rospy.logwarn("Service call timed out: %s", e)
  3. 精简服务数据:在Nano和上位机或其他设备通信时,网络带宽可能有限。确保你的.srv消息定义只包含必要字段,避免传输大图像等数据(考虑用话题传输图像,用服务传输一个触发指令或小尺寸的处理结果)。

http://www.cnnetsun.cn/news/4260011.html

相关文章:

  • vue学习(白话功能版)
  • 国赛真题解析:利用数学特性与剪枝优化子数组和积相等问题
  • Python实战Bayes判别分析:从数学原理到LDA/QDA模型应用
  • 实测数据公开:ZED X系列深度精度与传输性能全面验证报告
  • MVMD多元变分模态分解与小波阈值联合去噪:原理、MATLAB实现与调优指南
  • 三相电源Delta与Wye输入兼容设计:以4080W电源为例
  • 训练-免费的开放词汇语义分割:原型引导文本校准方法解析与工程实践
  • 企业私有 RAG 避坑实录:从代码幻觉到受约束生成的全链路改造
  • 知网二代讨论章节AI疑似度偏高怎么改:助研君分段处理实测
  • 敏捷BI实战指南:从概念到落地,避开五大误区构建数据驱动文化
  • RTL-SDR V2 RTL2832U+FC0012/FC0013 SDR软件无线电接收机 收音机 RTL-SDR6 V2无线电接收器 RTL2832U SDR接收机 FM频谱分析 ADS-B
  • 火焰识别VOC数据集解析与YOLO模型训练部署实战
  • 工业级布匹缺陷数据集构建:从采集、标注到模型训练全流程详解
  • AI落地最大的坑不是模型,而是数据、评测与工程化
  • ComfyUI+SD1.5+LoRA:AI一键将房屋平面图转为3D渲染效果图
  • 【单片机毕业设计推荐】基于 STM32 或 51 单片机的燃气火焰安全监测报警系统设计与实现 基于 STM32 或 51 单片机的家居燃气火情智能防护系统设计(017607)
  • 超长二进制数模5计算:状态机算法与性能优化实战
  • 本地开源AI去水印系统:原理、部署与实战调优
  • 腾讯云助手-优化SCF与静态托管CICD流水线
  • 从代码到数据库运行时,深入理解 SAP HANA Cloud HDI 的容器化部署体系
  • Apple Vision Pro辅助内镜手术提速20%:visionOS开发实战拆解
  • 元初混沌体系 第三卷 卫星互联网全域周天拓扑体系:第四十四篇 灾害应急全域中继中轨补网拓扑方案
  • AI训练开关不是隐私终点,还有人工审阅、聚合信号、评测采样三条暗道
  • 2025全新升级|单细胞多组学实战教程大全:涵盖scRNA-seq、scATAC-seq、bulk RNA-seq及高级分析与精美可视化代码
  • 本科毕设解析:Apache+.htaccess+CSS Flex+localStorage实战
  • 融资到账后技术团队第一步:容量规划与稳定性治理实战指南
  • 基于Spring Boot与微信小程序的失物招领系统全栈开发实战
  • 腾讯混元Hy ASR 3.0 Preview:选型评估与工程落地指南
  • 动态规划解本质上升子序列:状态定义与去重计数详解
  • AI时代情绪管理:把焦虑转化为行动力的技术指南