这篇记录了智能机器人创意大赛(四足小型组)准备阶段的一个 ROS 基础实验:自定义 Service 通信。选题是城市邮编查询,麻雀虽小五脏俱全——从 .srv 服务定义到 CMakeLists 编译配置,再到服务端/客户端全流程实现。
实验背景
智能机器人创意大赛的核心任务是四足机器人的自主导航与交互。ROS 作为机器人软件框架,其核心通信机制分为两类:
- Topic(话题):单向异步传输,适合传感器数据流
- Service(服务):请求-响应同步通信,适合指令下发、状态查询等需要即时回复的场景
本实验通过一个具体案例——城市邮编查询服务——验证 Service 通信模型的完整开发流程,为后续比赛中机器人节点间的指令交互打下基础。
项目结构
ros_city_query/├── CMakeLists.txt # 编译配置,含消息生成├── package.xml # 包元信息与依赖声明├── srv/│ └── CityQuery.srv # 自定义服务接口定义└── scripts/ ├── server.py # 服务端节点 └── client.py # 客户端节点作为一个最小完整 ROS 服务包,四个文件各司其职:接口定义、依赖声明、编译规则、运行逻辑。
服务接口定义
ROS 自定义 Service 的第一步是写 .srv 文件,分离请求与响应字段:
string city # 请求:城市名称---string postal_code # 响应:邮政编码string province # 响应:所属省份/国家--- 上方为 Request 字段(客户端发送),下方为 Response 字段(服务端返回)。这种声明式接口定义方式让节点间的契约一目了然,也是 ROS 服务通信与纯 socket 编程相比最大的工程优势——类型安全、自动生成代码、语言无关。
CMakeLists 编译配置
.srv 文件需要编译生成 Python 类代码,CMakeLists.txt 中的关键配置:
cmake_minimum_required(VERSION 3.0.2)project(ros_city_query)
find_package(catkin REQUIRED COMPONENTS rospy std_msgs message_generation # ① 声明需要消息生成)
add_service_files( # ② 注册服务文件 FILES CityQuery.srv)
generate_messages( # ③ 触发代码生成 DEPENDENCIES std_msgs)
catkin_package( CATKIN_DEPENDS message_runtime rospy std_msgs)
catkin_install_python(PROGRAMS # ④ 安装可执行脚本 scripts/server.py scripts/client.py DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})四项职责对应 ROS 服务开发的四个阶段:声明依赖 → 注册接口 → 生成代码 → 安装脚本。
服务端实现
服务端维护一个城市数据库,接收查询请求后查表返回结果。
数据设计
选择了 10 个城市,覆盖不同国家、不同编码体系,验证通用性:
| 城市 | 邮政编码 | 所属 |
|---|---|---|
| 广州 | 510000 | 广东省 |
| 成都 | 610000 | 四川省 |
| 首尔 | 04547 | 韩国 |
| 悉尼 | 2000 | 澳大利亚 |
| 纽约 | 10001 | 美国 |
| 杭州 | 310000 | 浙江省 |
| 卑尔根 | 5003 | 挪威 |
| 普洱 | 665000 | 云南省 |
| 西安 | 710000 | 陕西省 |
| 景德镇 | 333000 | 江西省 |
收录逻辑:国内城市占多数(7 个),覆盖华东、华南、西南、西北区域;国外城市 3 个(首尔、悉尼、纽约),体现不同国家邮政编码格式差异——中国为 6 位纯数字,韩国 5 位,澳大利亚 4 位,美国 ZIP Code 5 位,挪威 4 位。
核心逻辑
#!/usr/bin/env python3import rospyfrom ros_city_query.srv import CityQuery, CityQueryResponse
DB = { "广州": ("510000", "广东省"), "成都": ("610000", "四川省"), "首尔": ("04547", "韩国"), "悉尼": ("2000", "澳大利亚"), "纽约": ("10001", "美国"), "杭州": ("310000", "浙江省"), "卑尔根": ("5003", "挪威"), "普洱": ("665000", "云南省"), "西安": ("710000", "陕西省"), "景德镇": ("333000", "江西省"),}
def handle_query(req): city = req.city rospy.loginfo("查询城市: %s", city) if city in DB: postal_code, province = DB[city] rospy.loginfo("找到: %s -> %s, %s", city, postal_code, province) return CityQueryResponse(postal_code=postal_code, province=province) else: rospy.logwarn("未找到城市: %s", city) return CityQueryResponse(postal_code="未找到", province="未找到")
def main(): rospy.init_node("city_query_server") service = rospy.Service("city_query", CityQuery, handle_query) rospy.loginfo("城市查询服务已启动") rospy.spin()几点设计考量:
- 查表而非 API 调用:实验环境可能无外网,本地数据库保证可靠性
- 查不到不抛异常:返回 “未找到” 字符串,优雅降级而非让客户端崩溃
- 日志分级:正常查询用
loginfo,查不到用logwarn,便于调试
客户端实现
客户端从命令行参数读取城市名,调用服务并打印结果。
#!/usr/bin/env python3import sysimport rospyfrom ros_city_query.srv import CityQuery, CityQueryRequest
def main(): rospy.init_node("city_query_client")
rospy.wait_for_service("city_query") # ① 等待服务上线 query = rospy.ServiceProxy("city_query", CityQuery) # ② 创建代理
if len(sys.argv) < 2: rospy.logerr("用法: rosrun ros_city_query client.py <城市名>") sys.exit(1)
city = sys.argv[1] rospy.loginfo("查询城市: %s", city)
req = CityQueryRequest(city=city) resp = query(req)
print("--- 查询结果 ---") print("城市: {}".format(city)) print("邮政编码: {}".format(resp.postal_code)) print("位置: {}".format(resp.province)) print("----------------")客户端三点设计考量:
wait_for_service:阻塞等待服务端就绪后再调用,避免竞态条件ServiceProxy:ROS 自动生成的代理对象,将远程调用封装为本地函数调用- 参数校验在客户端侧:提前检查参数数量,给出明确的使用提示
运行流程
# 编译catkin_makesource devel/setup.bash
# 启动服务端rosrun ros_city_query server.py
# 另开终端,客户端查询rosrun ros_city_query client.py 杭州# → 城市: 杭州, 邮政编码: 310000, 位置: 浙江省
rosrun ros_city_query client.py 纽约# → 城市: 纽约, 邮政编码: 10001, 位置: 美国
rosrun ros_city_query client.py 上海# → 城市: 上海, 邮政编码: 未找到, 位置: 未找到三次查询覆盖三种场景:国内城市命中、国外城市命中、未收录城市。
通信时序
Client Server | | |-- CityQueryRequest ------->| (city: "杭州") | | handle_query() 查 DB |<-- CityQueryResponse ------| (postal_code, province) | |Service 通信是同步阻塞的——客户端发送请求后挂起,等待服务端返回才继续。这与 Topic 的异步推送形成互补:查询类操作用 Service,数据流类操作用 Topic。
小结
- 自定义 Service 开发流程:.srv 定义 → CMakeLists 配置 → catkin_make 生成代码 → 服务端/客户端实现
- 接口设计:Request-Response 分离,
---上为请求、下为响应 - 工程考量:本地数据库保证可靠性、优雅降级处理未命中、日志分级辅助调试
- Service vs Topic:同步请求-响应用 Service,异步数据流用 Topic,二者互补构机器人的完整通信体系
本实验为后续四足机器人项目的节点间指令交互(如导航指令下发、状态查询)提供了可复用的通信模板。
实验平台:ROS Noetic + Python 3 · 撰写:Gloomoon 🦈
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时






