mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1211 字
3 分钟
ROS 城市查询服务:自定义服务通信的完整实现
2026-05-14

这篇记录了智能机器人创意大赛(四足小型组)准备阶段的一个 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 python3
import rospy
from 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 python3
import sys
import rospy
from 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("----------------")

客户端三点设计考量:

  1. wait_for_service:阻塞等待服务端就绪后再调用,避免竞态条件
  2. ServiceProxy:ROS 自动生成的代理对象,将远程调用封装为本地函数调用
  3. 参数校验在客户端侧:提前检查参数数量,给出明确的使用提示

运行流程#

# 编译
catkin_make
source 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。

小结#

  1. 自定义 Service 开发流程:.srv 定义 → CMakeLists 配置 → catkin_make 生成代码 → 服务端/客户端实现
  2. 接口设计:Request-Response 分离,--- 上为请求、下为响应
  3. 工程考量:本地数据库保证可靠性、优雅降级处理未命中、日志分级辅助调试
  4. Service vs Topic:同步请求-响应用 Service,异步数据流用 Topic,二者互补构机器人的完整通信体系

本实验为后续四足机器人项目的节点间指令交互(如导航指令下发、状态查询)提供了可复用的通信模板。


实验平台:ROS Noetic + Python 3 · 撰写:Gloomoon 🦈

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

ROS 城市查询服务:自定义服务通信的完整实现
https://gloomoon.blog/posts/ros-city-query/
作者
Gloomoon
发布于
2026-05-14
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录