ROS通信机制之服务(Service)的应用

news2025/1/21 0:54:29

1、服务的概述

在上节我们讲过一个重要通信机制话题:ROS通信机制之话题(Topics)的发布与订阅以及自定义消息的实现,这里介绍另外一种节点之间传递数据的方法:服务(Service)
服务的本质是同步的跨进程函数调用,也就是说节点可以调用另一节点中的函数,其定义跟前面的消息很类似。
那么服务一般适合的场景是什么呢?对于那些只需要偶尔去做并且在有限时间里面完成的事情,比如说,分发到其他计算机上面去做通用计算,再比如打开传感器或者从摄像机获取一张高分辨率的图像,这些都可以考虑用到服务。分为两部分,服务端和客户端,或者说是请求和响应。
服务端:提供服务的节点,定义了一个回调函数,用来处理请求
客户端:服务请求的节点,通过本地代理去调用这个服务


2、服务的定义

跟自定义消息一样,先定义一个服务,区别在于定义的服务里面是有输入和输出,两者之间使用三个小短线"---"来隔开
我们这里看一个示例:统计字符串中单词的个数。这里我还是使用前面创建的test包,关于如何创建包,可以查阅:ROS新建工作区(workspace)与包(package)编译的实践(C++示例)

2.1、服务定义

cd ~/catkin_ws/src/test
mkdir srv
cd srv

gedit WordCount.srv

string words
---
uint32 count

为其添加可执行权限:chmod u+x WordCount.srv

可以看到,输入就是一串字符串,所以类型是string,短线隔开之后就是输出的定义,这里是统计单词个数,所以是一个无符号的整数类型uint32
定义好了服务之后,就需要运行catkin_make来创建我们与服务交互的时候真正会用到的代码和类定义,这些都将是自动生成。

2.2、修改package.xml

cd ~/catkin_ws/src/test
gedit package.xml

<buildtool_depend>catkin</buildtool_depend>
<build_depend>message_generation</build_depend>
<build_export_depend>rospy</build_export_depend>
<exec_depend>message_runtime</exec_depend>

2.3、修改CMakeLists.txt 

接着修改CMakeLists.txt文件,里面的find_package()调用包含message_generation,跟消息定义一样,进入编辑:gedit CMakeLists.txt

find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation)

在消息里面是告诉catkin添加的消息文件:add_message_files(),同样的,这里需要告知哪些服务定义的文件需要被编译:

add_service_files(FILES WordCount.srv)

最后同样的需要确保服务定义文件的依赖项已被声明,注释去掉:

generate_messages(DEPENDENCIES std_msgs)

2.4、编译

定义好了之后,跟自定义消息一样,回到工作区根目录进行编译 

cd ~/catkin_ws
catkin_make 

编译之后将自动生成三个类:WordCountRequestWordCountResponseWordCount,这些类将被用来跟服务进行交互,当然了,这些编译后的类一般是不需要去查看的,为了更清晰的了解这个过程,我们依然来看下:

cd ~/catkin_ws/devel/lib/python2.7/dist-packages/test/srv
cat _WordCount.py
# This Python file uses the following encoding: utf-8
"""autogenerated by genpy from test/WordCountRequest.msg. Do not edit."""
import codecs
import sys
python3 = True if sys.hexversion > 0x03000000 else False
import genpy
import struct


class WordCountRequest(genpy.Message):
  _md5sum = "6f897d3845272d18053a750c1cfb862a"
  _type = "test/WordCountRequest"
  _has_header = False  # flag to mark the presence of a Header object
  _full_text = """string words
"""
  __slots__ = ['words']
  _slot_types = ['string']

  def __init__(self, *args, **kwds):
    """
    Constructor. Any message fields that are implicitly/explicitly
    set to None will be assigned a default value. The recommend
    use is keyword arguments as this is more robust to future message
    changes.  You cannot mix in-order arguments and keyword arguments.

    The available fields are:
       words

    :param args: complete set of field values, in .msg order
    :param kwds: use keyword arguments corresponding to message field names
    to set specific fields.
    """
    if args or kwds:
      super(WordCountRequest, self).__init__(*args, **kwds)
      # message fields cannot be None, assign default values for those that are
      if self.words is None:
        self.words = ''
    else:
      self.words = ''

  def _get_types(self):
    """
    internal API method
    """
    return self._slot_types

  def serialize(self, buff):
    """
    serialize message into buffer
    :param buff: buffer, ``StringIO``
    """
    try:
      _x = self.words
      length = len(_x)
      if python3 or type(_x) == unicode:
        _x = _x.encode('utf-8')
        length = len(_x)
      buff.write(struct.Struct('<I%ss'%length).pack(length, _x))
    except struct.error as se: self._check_types(struct.error("%s: '%s' when writing '%s'" % (type(se), str(se), str(locals().get('_x', self)))))
    except TypeError as te: self._check_types(ValueError("%s: '%s' when writing '%s'" % (type(te), str(te), str(locals().get('_x', self)))))

  def deserialize(self, str):
    """
    unpack serialized message in str into this message instance
    :param str: byte array of serialized message, ``str``
    """
    if python3:
      codecs.lookup_error("rosmsg").msg_type = self._type
    try:
      end = 0
      start = end
      end += 4
      (length,) = _struct_I.unpack(str[start:end])
      start = end
      end += length
      if python3:
        self.words = str[start:end].decode('utf-8', 'rosmsg')
      else:
        self.words = str[start:end]
      return self
    except struct.error as e:
      raise genpy.DeserializationError(e)  # most likely buffer underfill


  def serialize_numpy(self, buff, numpy):
    """
    serialize message with numpy array types into buffer
    :param buff: buffer, ``StringIO``
    :param numpy: numpy python module
    """
    try:
      _x = self.words
      length = len(_x)
      if python3 or type(_x) == unicode:
        _x = _x.encode('utf-8')
        length = len(_x)
      buff.write(struct.Struct('<I%ss'%length).pack(length, _x))
    except struct.error as se: self._check_types(struct.error("%s: '%s' when writing '%s'" % (type(se), str(se), str(locals().get('_x', self)))))
    except TypeError as te: self._check_types(ValueError("%s: '%s' when writing '%s'" % (type(te), str(te), str(locals().get('_x', self)))))

  def deserialize_numpy(self, str, numpy):
    """
    unpack serialized message in str into this message instance using numpy for array types
    :param str: byte array of serialized message, ``str``
    :param numpy: numpy python module
    """
    if python3:
      codecs.lookup_error("rosmsg").msg_type = self._type
    try:
      end = 0
      start = end
      end += 4
      (length,) = _struct_I.unpack(str[start:end])
      start = end
      end += length
      if python3:
        self.words = str[start:end].decode('utf-8', 'rosmsg')
      else:
        self.words = str[start:end]
      return self
    except struct.error as e:
      raise genpy.DeserializationError(e)  # most likely buffer underfill

_struct_I = genpy.struct_I
def _get_struct_I():
    global _struct_I
    return _struct_I
# This Python file uses the following encoding: utf-8
"""autogenerated by genpy from test/WordCountResponse.msg. Do not edit."""
import codecs
import sys
python3 = True if sys.hexversion > 0x03000000 else False
import genpy
import struct


class WordCountResponse(genpy.Message):
  _md5sum = "ac8b22eb02c1f433e0a55ee9aac59a18"
  _type = "test/WordCountResponse"
  _has_header = False  # flag to mark the presence of a Header object
  _full_text = """uint32 count

"""
  __slots__ = ['count']
  _slot_types = ['uint32']

  def __init__(self, *args, **kwds):
    """
    Constructor. Any message fields that are implicitly/explicitly
    set to None will be assigned a default value. The recommend
    use is keyword arguments as this is more robust to future message
    changes.  You cannot mix in-order arguments and keyword arguments.

    The available fields are:
       count

    :param args: complete set of field values, in .msg order
    :param kwds: use keyword arguments corresponding to message field names
    to set specific fields.
    """
    if args or kwds:
      super(WordCountResponse, self).__init__(*args, **kwds)
      # message fields cannot be None, assign default values for those that are
      if self.count is None:
        self.count = 0
    else:
      self.count = 0

  def _get_types(self):
    """
    internal API method
    """
    return self._slot_types

  def serialize(self, buff):
    """
    serialize message into buffer
    :param buff: buffer, ``StringIO``
    """
    try:
      _x = self.count
      buff.write(_get_struct_I().pack(_x))
    except struct.error as se: self._check_types(struct.error("%s: '%s' when writing '%s'" % (type(se), str(se), str(locals().get('_x', self)))))
    except TypeError as te: self._check_types(ValueError("%s: '%s' when writing '%s'" % (type(te), str(te), str(locals().get('_x', self)))))

  def deserialize(self, str):
    """
    unpack serialized message in str into this message instance
    :param str: byte array of serialized message, ``str``
    """
    if python3:
      codecs.lookup_error("rosmsg").msg_type = self._type
    try:
      end = 0
      start = end
      end += 4
      (self.count,) = _get_struct_I().unpack(str[start:end])
      return self
    except struct.error as e:
      raise genpy.DeserializationError(e)  # most likely buffer underfill


  def serialize_numpy(self, buff, numpy):
    """
    serialize message with numpy array types into buffer
    :param buff: buffer, ``StringIO``
    :param numpy: numpy python module
    """
    try:
      _x = self.count
      buff.write(_get_struct_I().pack(_x))
    except struct.error as se: self._check_types(struct.error("%s: '%s' when writing '%s'" % (type(se), str(se), str(locals().get('_x', self)))))
    except TypeError as te: self._check_types(ValueError("%s: '%s' when writing '%s'" % (type(te), str(te), str(locals().get('_x', self)))))

  def deserialize_numpy(self, str, numpy):
    """
    unpack serialized message in str into this message instance using numpy for array types
    :param str: byte array of serialized message, ``str``
    :param numpy: numpy python module
    """
    if python3:
      codecs.lookup_error("rosmsg").msg_type = self._type
    try:
      end = 0
      start = end
      end += 4
      (self.count,) = _get_struct_I().unpack(str[start:end])
      return self
    except struct.error as e:
      raise genpy.DeserializationError(e)  # most likely buffer underfill

_struct_I = genpy.struct_I
def _get_struct_I():
    global _struct_I
    return _struct_I
class WordCount(object):
  _type          = 'test/WordCount'
  _md5sum = '58903d21a3264f3408d79ba79e9f7c7e'
  _request_class  = WordCountRequest
  _response_class = WordCountResponse

2.5、查看服务

服务文件定义好并编译之后,我们可以使用rossrv命令来查看服务定义的内容

rossrv show WordCount
'''
[test/WordCount]:
string words
---
uint32 count
'''

恩,没有问题,跟定义的是一样的。其他一些服务相关的命令如下:
查看所有可用的服务:rossrv list

查看所有提供了服务的包:rossrv packages

 

查看某个包提供的服务:rossrv package control_msgs

control_msgs/QueryCalibrationState
control_msgs/QueryTrajectoryState  

3、实现服务

我们已经定义并编译了服务,现在就可以开始实现这个服务了。跟前面介绍的话题一样,服务也是基于回调函数的机制。

3.1、服务端节点

对字符串计算单词的个数:

cd ~/catkin_ws/src/test/src
gedit service_server.py
#!/usr/bin/env python

import rospy
from test.srv import WordCount,WordCountResponse

def count_words(request):
    return WordCountResponse(len(request.words.split()))

rospy.init_node('service_server')
service = rospy.Service('word_count',WordCount,count_words)
rospy.spin()

然后加个执行权限:chmod u+x service_server.py

这里导入类的时候,都是在与包名同名的带有.srv后缀的模块中,这里的包是test,所以就是test.srv里面
其中rospy.Service函数的参数分别是声明的名字word_count,类型WordCount,回调函数count_words,最后就是调用rospy.spin()将程序的执行交给ROS,只有当节点即将要退出的时候才返回,当然这里调用rospy.spin()之后,并没有真正地交出程序的控制(跟C++的API有点区别),因为回调函数是在它们自己的线程中运行的。如果你有其他的事情需要做,可以创建自己的循环,但是要记得检查何时需要结束,使用rospy.spin()是一种方便的方式来保证节点直到需要退出的时候才退出。

3.2、服务是否正常

先运行上面的服务节点:rosrun test service_server.py
当然在此之前需要运行节点管理器,这个也已在前面的文章有讲述:roscore
接下来就可以使用roservice来查看下运行了哪些服务节点:rosservice list

/rosout/get_loggers
/rosout/set_logger_level
/service_server/get_loggers
/service_server/set_logger_level
/word_count 

除了ROS提供的日志服务,我们自己定义的服务service_server也在里面,还可以使用rosservice info来获取更多的信息:rosservice info word_count

Node: /service_server
URI: rosrpc://YAB:45277
Type: test/WordCount
Args: words 

这个word_count就是前面服务声明的名字,显示出来的服务信息,可以看到有节点名称,URI,类型,还有一个参数words
其他一些命令也可以直接获取,获取类型:rosservice type word_count

test/WordCount

获取参数:rosservice args word_count

words

3.3、多返回值

前面介绍的是只有一个返回值的情况,也可以是多个返回值,就使用元组或者列表,比如:

def count_words(request):
    return len(request.words.split())

len(request.words.split()) 可以修改成 [len(request.words.split())] 进行返回,一个与多个都可以。

还可以使用字典类型,其中键名是参数的名字:

def count_words(request):
    return {'count':len(request.words.split())}

这两种情况下,ROS服务调用的底层代码都会将这些返回值直接翻译成WordCountResponse对象,也就是说元组或列表或字典的写法,得到的结果跟回调函数是一样的:

def count_words(request):
    return WordCountResponse(len(request.words.split()))

另外需要注意的是,字典的键名如果不是WordCountResponse的属性,就会报错,比如我将count修改成count1

ERROR: service [/word_count] responded with an error: service cannot process request: handler returned invalid value: count1 is not an attribute of WordCountResponse

4、调用服务 

 调用服务最简单的方式,命令行使用rosservice直接call即可,如下:

rosservice call word_count "hello tony are you ok"
count: 5

从返回的结果也可以看到,返回的K:V值跟前面我们多返回值的两种形式相符。这种命令方式一般用来确认它是否正常工作,所以大多情况我们还是将其写在另一节点中来调用。

客户端节点service_client.py

cd ~/catkin_ws/src/test/src
gedit service_client.py
#!/usr/bin/env python

import rospy
from test.srv import WordCount
import sys

rospy.init_node('service_client')
rospy.wait_for_service('word_count')
word_counter = rospy.ServiceProxy('word_count',WordCount)
words = ' '.join(sys.argv[1:])
word_count = word_counter(words)
print(words, '-->' ,word_count.count)

加个可执行权限:chmod u+x service_client.py

可以看到使用了本文概述中介绍的本地代理rospy.ServiceProxy,里面的参数分别是服务名称和类型。
其中的sys.argv[1:]表示的是输入的内容,因为sys.argv返回的是列表,其中第一个内容是文件名,第二个开始就是输入的内容,比如1.py:

import sys
print(' '.join(sys.argv))
print(' '.join(sys.argv[1:]))

然后我们在命令行执行它

python 1.py hello           world a          b   c
'''
1.py hello world a b c
hello world a b c
'''

客户端节点定义好了之后,我们来测试下

rosrun test service_client.py hello tony are you ok haha
'''
('hello tony are you ok haha', '-->', 6)
'''

结果没有问题,单词和统计数都正确显示!

5、小结

在本节的例子,只有一个参数words,所以代理函数也是一个参数,同样,服务也只有一个返回参数,所以代理函数只返回一个值。如果在服务中定义:

string words
int min_word_len
---
uint32 count
uint32 ignored

输入和输出都有两个参数的情况,那么代理函数也需要两个参数:word_counter(words,3),参数按照定义中的顺序传递,也可以显式构造一个服务请求对象来进行服务调用:

request = WordCountRequest('hello tony are you ok haha',3)
count,ignored = word_counter(request)

如果使用这种方法,那么在客户端需要导入:from test.srv import WordCountRequest

另外一个好的习惯就是,任何的参数,我们都应该去显式赋值,如果省略一些服务调用所必须的参数,这些参数都将是未定义的值,可能在节点交互之间出现一些未知的bug。 

服务作为在ROS中的第二种主要通信机制,一般是对那些偶尔会做的事情,或者是当你需要同步响应的时候,就考虑使用服务。服务的回调函数中的计算时间应该较短,需要在有限时间内完成,如果它们耗时太长,或者说对时间的要求高,那就需要考虑另外一种通信机制:动作

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

骨传导耳机哪款比较好,市面上最好的骨传导耳机分享

随着科技的日新月异&#xff0c;骨传导耳机也在不断更新换代。市场上涌现出许多品牌&#xff0c;这使得消费者在购买时感到困惑。别担心&#xff01;我们为你整理了一些市场上最好的骨传导耳机品牌&#xff0c;希望能帮到你。现在&#xff0c;就让我们一起探索这些骨传导耳机的…

人工智能会成为人类的威胁吗?马斯克、扎克伯格、比尔·盖茨出席

根据消息人士透露&#xff0c;此次人工智能洞察论坛将是一次历史性的聚会&#xff0c;吸引了来自科技界的许多重量级人物。与会者们将共同探讨人工智能在科技行业和社会发展中的巨大潜力以及可能带来的挑战。 埃隆马斯克&#xff0c;特斯拉和SpaceX的首席执行官&#xff0c;一直…

ssm+vue理发店会员管理系统源码和论文

ssmvue理发店会员管理系统源码和论文089 开发工具&#xff1a;idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 技术&#xff1a;ssm 摘 要 网络技术和计算机技术发展至今&#xff0c;已经拥有了深厚的理论基础&#xff0c;并在现实中进行了充分运用&a…

《高性能Linux网络编程核心技术揭秘》已出版

#好书推荐##好书奇遇季#《高性能Linux网络编程核心技术揭秘》&#xff0c;京东当当天猫都有发售。定价109元&#xff0c;网店打折销售更便宜。本书配套示例项目源码、作者QQ答疑。 本书详解高性能Linux网络编程的核心技术及DPDK框架&#xff0c;剖析Nginx高性能服务器架构&…

《PyTorch 2.0深度学习从零开始学》已出版

#好书推荐##好书奇遇季#《PyTorch 2.0深度学习从零开始学》&#xff0c;京东当当天猫都有发售。定价69元&#xff0c;网店打折销售更便宜。本书配套示例项目源码、PPT课件。 本书以通俗易懂的方式介绍PyTorch深度学习基础理论&#xff0c;并以项目实战的形式详细介绍PyTorch框…

无涯教程-Android - 应用组件

应用程序组件是Android应用程序的基本组成部分&#xff0c;这些组件需要在应用程序清单文件 AndroidManifest.xml 注册&#xff0c;该文件描述了应用程序的每个组件以及它们如何交互。 Android应用程序可以使用以下四个主要组件- Sr.NoComponents & 描述1 Activities 它们…

基于JAVA SpringBoot和HTML婴幼儿商品商城设计

摘要 随着网络技术的发展与普遍,人们的生活发生了日新月异的变化,特别是计算机的应用已经普及到经济和社会的各个领域.为了让消费者网上购物过程变得简单,方便,安全,快捷,网上商城购物成了一种新型而热门的购物方式。网上商城在商品销售的发展中占据了重要的地位,已成为商家展示…

Python直接变快五倍?最新的优化解释器和内存管理

来自公众号&#xff1a;OSC开源社区 2020 年秋&#xff0c;CPython 核心开发者 Mark Shannon 提出了关于 Python 的几个性能改进&#xff0c;这个提议被称为 “香农计划” (Shannon Plan)。 Shannon 随后创建了 Faster Cpython 项目&#xff0c;他希望在 4 年的时间里&#xff…

Boost开发指南-4.11config

config config库主要是提供给Boost库开发者&#xff08;而不是库用户&#xff09;使用&#xff0c;它将程序的编译配置分解为三个正交的部分&#xff1a;平台、编译器和标准库&#xff0c;帮助他们解决特定平台特定编译器的兼容问题。 一般来说&#xff0c;config库不应该被库…

《Flink学习笔记》——第二章 Flink的安装和启动、以及应用开发和提交

​ 介绍Flink的安装、启动以及如何进行Flink程序的开发&#xff0c;如何运行部署Flink程序等 2.1 Flink的安装和启动 本地安装指的是单机模式 0、前期准备 java8或者java11&#xff08;官方推荐11&#xff09;下载Flink安装包 https://flink.apache.org/zh/downloads/hadoop&a…

循环购商业模式:挖掘用户价值,创新引领商业未来-微三云门门

亲爱的企业家们&#xff0c;我是微三云门门&#xff01;今天&#xff0c;我将为大家详细介绍一种颠覆性的商业模式&#xff1a;循环购商业模式。这个模式不仅可以帮助企业提升平台的复购率&#xff0c;还能够拉新用户并提升用户的消费率。让我们一起深入了解这个引人注目的商业…

MySQL8.Xx安装控制台未参数随机密码解决方案

MySQL8.xx一主两从复制安装与配置 MySQL8.XX随未生成随机密码解决方案 一: Mysql 安装时控制台未生成密码 安装过程中解压或者时安装时报错等,这种情况一般是因网络等其他原因导致下载的安装包不完整&#xff0c; 重新下载安装即可; 二: 安装解压都没问题,就是不生成随机密…

软件测试用例经典方法 | 单元测试法案例

单元测试又称模块测试&#xff0c;是对软件设计的最小单元的功能、性能、接口和设计约束等的正确性进行检验&#xff0c;检查程序在语法、格式和逻辑上的错误&#xff0c;并验证程序是否符合规范&#xff0c;以发现单元内部可能存在的各种缺陷。 单元测试的对象是软件设计的最…

深入浅出SSD:固态存储核心技术、原理与实战(文末赠书)

名字&#xff1a;阿玥的小东东 学习&#xff1a;Python、C/C 主页链接&#xff1a;阿玥的小东东的博客_CSDN博客-python&&c高级知识,过年必备,C/C知识讲解领域博主 目录 内容简介 作者简介 使用Python做一个计算器 本期赠书 近年来国家大力支持半导体行业&#xff0…

前端组件库造轮子——Tree组件开发教程

前端组件库造轮子——Tree组件开发教程 前言 本系列旨在记录前端组件库开发经验&#xff0c;我们的组件库项目目前已在Github开源&#xff0c;下面是项目的部分组件。文章会详细介绍一些造组件库轮子的技巧并且最后会给出完整的演示demo。 文章旨在总结经验&#xff0c;开源分…

三雄极光家居秋季新品发布,争滔滔不绝!

​8月28日&#xff0c;三雄极光2023家居秋季新品发布暨订货会于中山古镇盛大启幕&#xff0c;会议以“聚力革新 影势领行”为主题&#xff0c;采用线上、线下相结合的方式举行。三雄极光总裁张宇涛、副总裁林岩、营销总经理陈勤显、家居事业部副总经理赵峰等领导出席了本次会议…

hdfs操作

hadoop fs [generic options] [-appendToFile … ] [-cat [-ignoreCrc] …] [-checksum …] [-chgrp [-R] GROUP PATH…] [-chmod [-R] <MODE[,MODE]… | OCTALMODE> PATH…] [-chown [-R] [OWNER][:[GROUP]] PATH…] [-copyFromLocal [-f] [-p] [-l] [-d] … ] [-copyTo…

大学生开学必备清单|你有一份开学必备物品清单,请查收!

​又到了要开学的时候&#xff0c;面对开学季&#xff0c;很多同学还不知道需要准备哪些东西&#xff0c;为了让同学们能够准备充分大学生活&#xff0c;作为一个过来人&#xff0c;下面就来和你们唠一唠进入大学的时候&#xff0c;需要用到什么东西&#xff0c;以及有什么是开…

jumpserver堡垒机添加资产配置

目录 jumpserver堡垒机添加资产配置 1、创建jumpserver管理用户&#xff0c;登录jumpserver堡垒机 2、创建普通用户&#xff0c;管理资源服务器 3、创建特权用户&#xff0c;登录资源服务器 4、添加资源 5、资产授权 6、登录jumpserver&#xff0c;创建的jumpserver用户 7、…

【LeetCode】28 . 找出字符串中第一个匹配项的下标

28 . 找出字符串中第一个匹配项的下标&#xff08;简单&#xff09; 方法&#xff1a;双指针法 思路 使用 find 函数枚举原串 ss 中的每个字符作为「发起点」&#xff0c;每次从原串的「发起点」和匹配串的「首位」开始尝试匹配&#xff1a; 匹配成功&#xff1a;返回本次匹配…