跨机器调用 ROS 服务时,服务端和客户端可以放在不同 package,甚至不同机器上运行;但 .srv 类型定义应来自同一个共享接口包。不要在服务端包和客户端包里各自复制一份同名 .srv,否则后续字段变更、生成代码和类型一致性都很难维护。

下面示例拆成三个包:

  • robot_interfaces:只定义 AddTwoInts.srv
  • robot_server_package:服务端节点
  • robot_client_package:客户端节点

一、创建共享 srv 包

1
2
3
cd ~/catkin_ws/src
catkin_create_pkg robot_interfaces message_generation
mkdir -p robot_interfaces/srv

创建 robot_interfaces/srv/AddTwoInts.srv

1
2
3
4
int64 a
int64 b
---
int64 sum

robot_interfaces/CMakeLists.txt 关键配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
find_package(catkin REQUIRED COMPONENTS
message_generation
)

add_service_files(
FILES
AddTwoInts.srv
)

generate_messages()

catkin_package(
CATKIN_DEPENDS message_runtime
)

robot_interfaces/package.xml 至少包含:

1
2
3
<buildtool_depend>catkin</buildtool_depend>
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>

编译后,服务端机器和客户端机器都要能找到这个接口包。如果两台机器各自有工作空间,需要保证两边的 robot_interfaces 内容一致,并分别执行 catkin_makesource devel/setup.bash

二、服务端包

1
2
3
cd ~/catkin_ws/src
catkin_create_pkg robot_server_package rospy robot_interfaces
mkdir -p robot_server_package/scripts

robot_server_package/CMakeLists.txt 关键配置:

1
2
3
4
5
6
7
8
find_package(catkin REQUIRED COMPONENTS
rospy
robot_interfaces
)

catkin_package(
CATKIN_DEPENDS rospy robot_interfaces
)

创建 robot_server_package/scripts/add_two_ints_server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python3

import rospy
from robot_interfaces.srv import AddTwoInts, AddTwoIntsResponse

def handle_add_two_ints(req):
rospy.loginfo("Returning [%s + %s = %s]", req.a, req.b, req.a + req.b)
return AddTwoIntsResponse(req.a + req.b)

def main():
rospy.init_node("add_two_ints_server")
rospy.Service("add_two_ints", AddTwoInts, handle_add_two_ints)
rospy.loginfo("Ready to add two ints.")
rospy.spin()

if __name__ == "__main__":
main()

三、客户端包

1
2
3
cd ~/catkin_ws/src
catkin_create_pkg robot_client_package rospy robot_interfaces
mkdir -p robot_client_package/scripts

robot_client_package/CMakeLists.txt 关键配置:

1
2
3
4
5
6
7
8
find_package(catkin REQUIRED COMPONENTS
rospy
robot_interfaces
)

catkin_package(
CATKIN_DEPENDS rospy robot_interfaces
)

创建 robot_client_package/scripts/add_two_ints_client.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/env python3

import sys
import rospy
from robot_interfaces.srv import AddTwoInts

def add_two_ints_client(x, y):
rospy.wait_for_service("add_two_ints")
try:
add_two_ints = rospy.ServiceProxy("add_two_ints", AddTwoInts)
resp = add_two_ints(x, y)
return resp.sum
except rospy.ServiceException as exc:
rospy.logerr("Service call failed: %s", exc)
raise

if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: %s [x y]" % sys.argv[0])
sys.exit(1)

x = int(sys.argv[1])
y = int(sys.argv[2])
print("Requesting %s + %s" % (x, y))
print("%s + %s = %s" % (x, y, add_two_ints_client(x, y)))

四、跨机器环境变量

假设服务端机器 IP 是 192.168.1.2,客户端机器 IP 是 192.168.1.3,并且 ROS master 运行在服务端。

服务端:

1
2
export ROS_MASTER_URI=http://192.168.1.2:11311
export ROS_IP=192.168.1.2

客户端:

1
2
export ROS_MASTER_URI=http://192.168.1.2:11311
export ROS_IP=192.168.1.3

ROS_IP 应写本机可被对方访问的 IP 地址。如果要使用主机名,则配置 /etc/hosts 或 DNS 后使用 ROS_HOSTNAME,不要把主机名写进 ROS_IP

五、运行服务和客户端

两台机器都先编译并加载各自工作空间:

1
2
3
cd ~/catkin_ws
catkin_make
source devel/setup.bash

服务端:

1
2
3
roscore
chmod +x ~/catkin_ws/src/robot_server_package/scripts/add_two_ints_server.py
rosrun robot_server_package add_two_ints_server.py

客户端:

1
2
chmod +x ~/catkin_ws/src/robot_client_package/scripts/add_two_ints_client.py
rosrun robot_client_package add_two_ints_client.py 2 3

如果配置正确,服务端会打印请求处理日志,客户端会输出 2 + 3 = 5。跨机器调试时,优先检查两边是否互相能 ping 通、端口是否被防火墙拦截、ROS_MASTER_URI 是否一致,以及 robot_interfaces 是否在两边都已经编译并 source。