1. 为什么非得用XML写路网?——从“点选拖拽”到“精准控制”的思维切换
你打开SUMO的netedit,拖几条路、拉几个交叉口、点几下鼠标,5分钟就能画出一个像模像样的十字路口。这很爽,对吧?但当你需要建一个包含237个信号灯相位、每条车道精确到0.1米偏移、所有连接器带特定转向权重、且要和下游交通流仿真模型做毫秒级时间同步的城郊主干道网络时,netedit的图形界面会突然变得像一张布满毛刺的砂纸——越擦越糊,越改越乱。我去年帮一个物流园区做仿真优化,最初用netedit做了三天,导出的.net.xml文件里有47处手动调整后遗漏的laneID冲突,导致整个仿真在t=12.8秒时崩溃,报错信息只有一行:Error: Invalid lane ID 'E1_0' used in connection.。没人知道这个E1_0是从哪冒出来的,因为netedit根本不会告诉你它内部怎么编号。
这时候,XML就不是“一种语言”,而是你和SUMO底层引擎之间唯一能听懂彼此的“工程图纸”。netedit生成的.net.xml本质就是一份结构化说明书:它告诉SUMO,“这里有一条叫E1的边,长度326.7米,含3条车道,每条宽3.5米,起始坐标是(120.45, 30.22),终点是(120.89, 30.22),它的第0号车道连接到另一条边J2的第1号车道,转向权重设为0.83”。而XML的语法强制你把每一个坐标、每一条车道、每一个连接关系都明确定义出来。这不是繁琐,是可追溯、可版本管理、可自动化生成、可与GIS数据无缝对接的必然选择。
你可能会问:“那我直接手写XML,岂不是比用netedit还慢?”不。真正高效的路网构建,从来不是“纯手写”,而是“模板化+参数化+自动化”。比如,你要建一条标准城市快速路,它永远有:中央隔离带(1条虚拟lane)、双向各3条机动车道(每条3.75米)、最外侧1条公交专用道(3.5米)、两侧各1条非机动车道(2.5米)、人行道(3米)。把这些固定结构写成一个XML模板,只需替换其中的起点坐标、总长度、曲率半径三个参数,就能批量生成10条不同走向的快速路。我实测过,用Python脚本读取Excel里的道路列表(含名称、起点经纬度、终点经纬度、设计车速),自动填充模板并生成127个路段的.net.xml,耗时2.3秒。而用netedit逐条绘制,保守估计要17小时以上,且无法保证车道宽度一致性。
更关键的是,XML让你摆脱了图形界面的“所见即所得”幻觉。netedit里看起来整齐的交叉口,在底层可能藏着几十个未显式定义的连接器(connection),它们默认按某种启发式规则生成,一旦仿真中出现车辆在路口“悬停”或“鬼穿墙”,你根本无从排查。而手写XML时,你必须显式声明每一个connection,比如:
<connection from="E1" to="J2" fromLane="0" toLane="0" pass="0" priority="1"/> <connection from="E1" to="J2" fromLane="1" toLane="1" pass="1" priority="1"/> <connection from="E1" to="J2" fromLane="2" toLane="2" pass="1" priority="1"/>这三行代码意味着:E1边的0号车道只能直行去J2的0号车道(pass="0"表示禁止变道),而1、2号车道允许变道(pass="1")。这种粒度的控制,图形界面永远做不到。所以,学XML不是为了取代netedit,而是为了在netedit画完初稿后,用XML做“外科手术式”的精准修正——这才是工业级仿真的真实工作流。
提示:别把XML当成编程语言来学。它没有循环、没有变量、没有函数。它就是一个高度结构化的“填空题”。你的任务不是写逻辑,而是准确填写SUMO要求的每一个字段。就像建筑工人看施工图,他不需要懂混凝土配比公式,但他必须清楚“此处钢筋直径12mm,间距150mm,锚固长度35d”。
2. netconvert:从“草图”到“可执行路网”的编译器原理
很多人以为netconvert只是一个“格式转换工具”,把OpenStreetMap的.osm文件转成.sumo.net.xml。这是巨大的误解。netconvert的本质,是一个路网语义编译器。它接收你提供的原始地理数据(osm、csv、shapefile)或手工XML描述,经过一系列严格的拓扑校验、几何计算、逻辑推导,最终输出一个符合SUMO内核运行规范的二进制就绪路网。这个过程,远比“转换”二字复杂得多。
举个最典型的例子:你在OpenStreetMap上画了一条路,标注为highway=primary。netconvert拿到这个信息后,并不会简单地把它变成一条边。它会做以下事情:
车道数推断:根据
highway=primary这个标签,结合你配置的--default.lanenumber参数(默认是2),但它还会查表——SUMO内置了一个“道路等级-典型车道数”映射表。primary通常对应2-4车道,netconvert会进一步检查该路段的lanes=3这个附加标签,如果存在,就采用3;否则,它会分析该路段在OSM中的实际宽度(如果有width=12.5标签),除以标准车道宽3.5米,向下取整得到3条车道。连接器自动生成:当两条
highway=primary道路相交时,netconvert不会只生成一个简单的十字路口。它会:- 计算每条道路进入交叉口的“入口段”(approach)和“出口段”(exit);
- 为每一对可能的转向组合(左转、直行、右转)生成独立的
<connection>元素; - 根据道路等级,自动设置
priority属性(主干道优先级高,支路优先级低); - 如果检测到某条转向路径存在几何冲突(比如左转轨迹会与对向直行车道重叠),它会自动插入一个“转弯缓存区”(turning loop),并在XML中生成额外的虚拟边(virtual edge)来规避。
几何精度校验:netconvert会对所有节点坐标进行浮点数精度归一化。它会把所有坐标统一缩放到小数点后6位(例如
120.456789123→120.456789),并检查是否存在重复节点(距离小于1e-6米)。一旦发现重复,它会合并节点,并更新所有引用该节点的边(edge)的端点ID。这一步看似微小,却能避免后续仿真中因浮点误差导致的“车辆卡在节点上”的诡异问题。
所以,当你运行netconvert --osm-files map.osm -o network.net.xml时,你不是在“转换”,而是在“编译”。这个编译过程的输出,就是一份完全由机器验证过的、无歧义的路网定义。这也是为什么,我们强烈建议:任何通过netedit手工绘制的路网,最终都必须用netconvert重新“编译”一次。netedit保存的.net.xml文件,只是编辑状态的快照,它可能包含未清理的临时节点、冗余的连接器、甚至坐标精度错误。而netconvert的--xml-validation选项(默认开启)会强制执行全套校验,确保输出的.network.net.xml是“生产就绪”的。
我踩过的一个深坑是:在netedit里画了一条弯曲的环形匝道,觉得形状完美,直接保存为.net.xml。结果仿真跑起来,车辆在匝道末端集体“消失”。用sumo-gui --net-file network.net.xml加载后,发现匝道末端的最后一个节点,其坐标被netedit四舍五入成了(120.123456, 30.654321),而下游连接的道路起点坐标是(120.1234567, 30.6543219)。这两个点在图形界面上看起来完全重合,但在SUMO内核里,它们是两个不同的节点,导致连接器失效。用netconvert重新编译后,它自动将两个坐标归一化为(120.123457, 30.654322),问题瞬间解决。
注意:netconvert的
--geometry.min-radius参数至关重要。它定义了道路曲线的最小允许曲率半径。如果你的路网中有大量小半径弯道(比如停车场内的U型掉头),必须把这个值调小(例如--geometry.min-radius 5),否则netconvert会强行把弯道“掰直”,生成一堆锯齿状的短线段,严重影响仿真精度和性能。
3. XML路网的核心骨架:从根节点到车道连接的逐层拆解
SUMO的路网XML文件,其结构严格遵循一个自顶向下的层级模型。理解这个骨架,是你能写出正确XML的前提。它不是随意堆砌的标签,而是一个精密的“树状工程蓝图”。我们从最顶层开始,一层层剥开:
3.1<net>:路网的容器与元数据
所有路网XML的根节点必须是<net>。它本身不定义任何几何实体,但它承载着整个路网的全局属性和命名空间声明:
<?xml version="1.0" encoding="UTF-8"?> <net version="1.8" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://sumo.dlr.de/xsd/net_file.xsd">这里的version="1.8"指明了该文件遵循SUMO 1.8版的schema规范。这个版本号必须与你安装的SUMO版本严格匹配。如果你用SUMO 1.7生成的.net.xml,强行在1.8里加载,很可能因为新增的字段(如<edge>的type属性)缺失而报错。xsi:noNamespaceSchemaLocation指向官方XSD Schema文件,这是XML验证的依据。你可以用任何支持XSD验证的编辑器(如VS Code + XML Tools插件)来实时检查你的XML是否符合规范。
3.2<location>:地理坐标的锚定点
紧随<net>之后,几乎必有的是<location>块。它定义了整个路网的地理参考系:
<location netOffset="120.456789,30.234567" convBoundary="0.00,0.00,1000.00,1000.00" origBoundary="120.45,30.23,120.46,30.24" projParameter="+proj=utm +zone=50 +ellps=WGS84"/>netOffset:这是最关键的一行。它表示路网原点(0,0)在WGS84地理坐标系(经纬度)中的位置。所有你在XML里写的<node>坐标,都是相对于这个原点的偏移量(单位:米)。例如,一个<node id="n1" x="100.0" y="200.0"/>,其真实地理坐标就是(120.456789 + 100.0/111319.9, 30.234567 + 200.0/111319.9)(这里111319.9是赤道附近1度经度约等于的米数,实际计算由projParameter完成)。convBoundary:定义了路网在“投影平面坐标系”下的边界矩形(xmin,ymin,xmax,ymax),单位是米。SUMO用它来优化渲染和碰撞检测。origBoundary:原始地理坐标的边界(经纬度),用于GIS软件识别。projParameter:PROJ字符串,定义了如何将经纬度转换为平面坐标。+proj=utm +zone=50表示使用UTM第50带投影。如果你的路网横跨多个UTM带(比如从东经119°到东经121°),必须使用+proj=longlat(经纬度直角坐标)或+proj=merc(墨卡托),否则会出现严重的几何畸变。
3.3<node>:路网的“钉子”与拓扑基石
<node>是路网中最基础的几何单元,它代表一个空间点。所有道路(<edge>)都必须始于和终于某个<node>:
<node id="n1" x="0.0" y="0.0" type="traffic_light"/> <node id="n2" x="500.0" y="0.0" type="priority"/> <node id="n3" x="500.0" y="500.0" type="priority"/> <node id="n4" x="0.0" y="500.0" type="priority"/>id:节点的唯一标识符,必须全局唯一,且不能包含空格或特殊字符(推荐纯字母数字)。x,y:相对于netOffset的平面坐标(米)。type:节点类型,决定了它在交叉口的行为。traffic_light表示此处有信号灯控制;priority表示主次路优先通行;unregulated表示无管制;dead_end表示死胡同。这个type属性直接驱动SUMO的车辆让行逻辑,绝不能随意设置。比如,一个type="priority"的节点,如果连接的两条边等级相同(都是highway=primary),SUMO会按“先到先得”原则处理,而不是你期望的“主路优先”。
3.4<edge>:道路的“骨架”与车道载体
<edge>定义了一条物理道路,它是连接两个<node>的线段。一条<edge>可以包含多条平行的<lane>:
<edge id="E1" from="n1" to="n2" priority="1" type="highway.primary"> <lane id="E1_0" index="0" speed="13.89" width="3.5" length="500.0"/> <lane id="E1_1" index="1" speed="13.89" width="3.5" length="500.0"/> <lane id="E1_2" index="2" speed="13.89" width="3.5" length="500.0"/> </edge>id:边的ID,必须唯一。from,to:引用<node>的ID,定义了边的起点和终点。priority:边的优先级数值,数值越大,优先级越高。在交叉口,高优先级边上的车辆有路权。type:边的类型,通常与OSM标签对应(highway.primary,highway.residential等),它关联到SUMO预设的车道数、速度限制等默认值。<lane>:嵌套在<edge>内的子元素。每条<lane>代表一条物理车道:id:车道ID,格式通常是<edge_id>_<index>,这是SUMO内部寻址的关键。index:车道索引,从0开始。索引0通常是靠近路中心线的车道(左行国家则相反)。speed:该车道的限速(m/s)。13.89m/s = 50 km/h。width:车道宽度(米)。length:车道长度(米),必须等于<edge>的几何长度。
3.5<connection>:交通流的“神经突触”
如果说<edge>是血管,<lane>是血细胞,那么<connection>就是决定血液流向的“瓣膜”。它定义了车辆如何从一条车道,合法地转移到另一条车道:
<connection from="E1" to="E2" fromLane="0" toLane="0" pass="0" priority="1" via=":n2_0_0"/> <connection from="E1" to="E2" fromLane="1" toLane="1" pass="1" priority="1" via=":n2_0_1"/> <connection from="E1" to="E2" fromLane="2" toLane="2" pass="1" priority="1" via=":n2_0_2"/>from,to:引用源边和目标边的ID。fromLane,toLane:引用具体的源车道和目标车道ID(注意,这里用的是index,不是id)。pass:是否允许变道。0表示禁止,1表示允许。这是控制车辆行为的核心开关。priority:该连接的优先级,影响车辆在冲突点的让行决策。via:这是一个关键字段!它指向一个“内部节点”(internal node),格式为:n2_0_0。这个节点不是你定义的<node>,而是netconvert在编译时,为每个连接自动生成的虚拟节点,用于精确描述车辆的转向轨迹。你永远不应该手动修改或删除via属性,否则会导致转向路径计算失败。
理解这五层结构,你就掌握了XML路网的全部骨骼。剩下的,就是在这个骨架上,填充血肉——添加交通灯程序、定义公交线路、设置可变限速区域。但骨架错了,一切皆空。
4. 实战:从零开始构建一个带信号灯的T型交叉口
现在,让我们把前面所有的理论,落地到一个具体、可运行的案例:一个标准的T型交叉口,南北向为主干道(双向4车道),东西向为支路(双向2车道),交叉口处设有四相位信号灯。我们将全程手写XML,不依赖netedit,来展示真正的“精准控制”。
4.1 步骤一:规划坐标与节点
首先,确定交叉口的中心点为原点(0,0)。主干道(南北向)沿Y轴延伸,支路(东西向)沿X轴延伸。
- 主干道南端节点
n1:(0, -300)(距中心300米) - 交叉口中心节点
n2:(0, 0) - 主干道北端节点
n3:(0, 300) - 支路西端节点
n4:(-200, 0) - 支路东端节点
n5:(200, 0)
这些节点的type需要精心设置:
n2是信号灯控制的交叉口,type="traffic_light"n1,n3,n4,n5都是普通端点,type="priority"(因为它们连接的是主干道,有路权)
4.2 步骤二:定义主干道与支路的边(Edge)
主干道分为两段:E1(南→中心),E2(中心→北)。支路也分为两段:E3(西→中心),E4(中心→东)。
<!-- 主干道南段 --> <edge id="E1" from="n1" to="n2" priority="3" type="highway.primary"> <lane id="E1_0" index="0" speed="16.67" width="3.5" length="300.0"/> <lane id="E1_1" index="1" speed="16.67" width="3.5" length="300.0"/> <lane id="E1_2" index="2" speed="16.67" width="3.5" length="300.0"/> <lane id="E1_3" index="3" speed="16.67" width="3.5" length="300.0"/> </edge> <!-- 主干道北段 --> <edge id="E2" from="n2" to="n3" priority="3" type="highway.primary"> <lane id="E2_0" index="0" speed="16.67" width="3.5" length="300.0"/> <lane id="E2_1" index="1" speed="16.67" width="3.5" length="300.0"/> <lane id="E2_2" index="2" speed="16.67" width="3.5" length="300.0"/> <lane id="E2_3" index="3" speed="16.67" width="3.5" length="300.0"/> </edge> <!-- 支路西段 --> <edge id="E3" from="n4" to="n2" priority="1" type="highway.residential"> <lane id="E3_0" index="0" speed="8.33" width="3.25" length="200.0"/> <lane id="E3_1" index="1" speed="8.33" width="3.25" length="200.0"/> </edge> <!-- 支路东段 --> <edge id="E4" from="n2" to="n5" priority="1" type="highway.residential"> <lane id="E4_0" index="0" speed="8.33" width="3.25" length="200.0"/> <lane id="E4_1" index="1" speed="8.33" width="3.25" length="200.0"/> </edge>注意priority的设置:主干道priority="3",支路priority="1",确保主干道车辆有绝对路权。
4.3 步骤三:定义所有必需的连接器(Connection)
T型交叉口有6种合法转向:主干道直行(2种)、主干道左转(2种)、支路直行(2种)。我们需要为每一种组合,显式定义<connection>。
<!-- 主干道南→北直行 (E1_0->E2_0, E1_1->E2_1, E1_2->E2_2, E1_3->E2_3) --> <connection from="E1" to="E2" fromLane="0" toLane="0" pass="0" priority="1"/> <connection from="E1" to="E2" fromLane="1" toLane="1" pass="0" priority="1"/> <connection from="E1" to="E2" fromLane="2" toLane="2" pass="0" priority="1"/> <connection from="E1" to="E2" fromLane="3" toLane="3" pass="0" priority="1"/> <!-- 主干道北→南直行 (E2_0->E1_0, E2_1->E1_1, E2_2->E1_2, E2_3->E1_3) --> <connection from="E2" to="E1" fromLane="0" toLane="0" pass="0" priority="1"/> <connection from="E2" to="E1" fromLane="1" toLane="1" pass="0" priority="1"/> <connection from="E2" to="E1" fromLane="2" toLane="2" pass="0" priority="1"/> <connection from="E2" to="E1" fromLane="3" toLane="3" pass="0" priority="1"/> <!-- 主干道南→西左转 (E1_0->E3_0, E1_1->E3_1) --> <connection from="E1" to="E3" fromLane="0" toLane="0" pass="0" priority="1"/> <connection from="E1" to="E3" fromLane="1" toLane="1" pass="0" priority="1"/> <!-- 主干道北→东左转 (E2_0->E4_0, E2_1->E4_1) --> <connection from="E2" to="E4" fromLane="0" toLane="0" pass="0" priority="1"/> <connection from="E2" to="E4" fromLane="1" toLane="1" pass="0" priority="1"/> <!-- 支路西→东直行 (E3_0->E4_0, E3_1->E4_1) --> <connection from="E3" to="E4" fromLane="0" toLane="0" pass="0" priority="1"/> <connection from="E3" to="E4" fromLane="1" toLane="1" pass="0" priority="1"/> <!-- 支路东→西直行 (E4_0->E3_0, E4_1->E3_1) --> <connection from="E4" to="E3" fromLane="0" toLane="0" pass="0" priority="1"/> <connection from="E4" to="E3" fromLane="1" toLane="1" pass="0" priority="1"/>这里的关键是pass="0"。我们禁止所有变道,确保每条车道的转向路径是唯一的、可预测的。这对于信号灯配时至关重要。
4.4 步骤四:添加信号灯程序
最后,为节点n2添加一个四相位信号灯。SUMO的信号灯程序(<tlLogic>)定义了每个相位的绿灯时长和哪些连接器被允许通行。
<tlLogic id="n2" type="static" programID="myProgram" offset="0"> <!-- 相位1:主干道南北直行 --> <phase duration="30" state="GGGgrrrrGGGgrrrr"/> <!-- 相位2:主干道南北左转 --> <phase duration="15" state="yyyyGGGGyyyyGGGG"/> <!-- 相位3:支路东西直行 --> <phase duration="25" state="rrrrGGGgrrrrGGGg"/> <!-- 相位4:支路东西左转 --> <phase duration="10" state="GGGGyyyyGGGGyyyy"/> </tlLogic>state字符串是核心。每一位对应一个<connection>,顺序与<connection>在XML中出现的顺序一致。G表示绿灯(允许通行),g表示黄灯(准备停止),r表示红灯(禁止通行),y表示黄灯。这个字符串必须与你定义的<connection>数量和顺序完全匹配,否则信号灯会失控。
将以上所有内容,连同<net>、<location>、<node>等,整合成一个完整的.net.xml文件,用netconvert编译,再用sumo-gui加载,你就能看到一个完全由你定义、毫秒级可控的T型交叉口仿真了。这个过程,没有任何“黑箱”,每一步都清晰可见。
经验技巧:在编写大型路网时,永远先写一个最小可行单元(如一个交叉口),用
sumo-gui反复测试,确认所有连接器和信号灯都工作正常后,再复制、粘贴、修改,扩展成整个网络。切忌一上来就写几百个节点,最后报错时无从排查。
5. 常见XML陷阱与排错链路:从“文件加载失败”到“车辆行为异常”
即使你严格按照XSD Schema写了XML,SUMO在加载时仍可能报错。这些错误往往不是语法错误,而是语义错误。下面是我整理的最常遇到的5类问题,以及一套标准化的排错链路。
5.1 错误类型一:Error: Invalid node ID 'n1' used in edge 'E1'
表象:netconvert或sumo-gui启动时,第一行报错,指出某个节点ID不存在。
根因分析:这是最基础的引用错误。<edge from="n1" to="n2">中引用的n1,在<node>列表里根本没定义,或者ID拼写错误(比如写成了n01)。
排错链路:
- 用文本编辑器的“查找”功能,搜索
n1,确认它只在<node id="n1">中定义过一次。 - 检查
<node>标签是否闭合,有没有漏掉/>或</node>。 - 检查
<node>是否被意外地写在了<edge>标签内部(XML不允许嵌套)。
避坑心得:在VS Code中安装“Auto Rename Tag”插件。当你修改一个<node id="n1">时,它会自动帮你修改所有引用n1的地方,彻底杜绝此类错误。
5.2 错误类型二:Warning: Connection 'E1_0->E2_0' is not usable (no common node)
表象:netconvert能成功生成.net.xml,但sumo-gui加载后,车辆在交叉口“凭空消失”或“撞墙”。
根因分析:<connection>的from和to边,没有共享同一个<node>。例如,E1的to="n2",E2的from="n3",那么E1和E2就没有物理连接,<connection>自然无效。
排错链路:
- 找到报错的
<connection>,记下from和to的边ID。 - 分别找到这两个
<edge>的定义,查看它们的from和to属性。 - 确认它们是否真的共享一个节点。例如,
E1 to="n2",E2 from="n2",这才叫共享。
避坑心得:在定义<edge>时,养成习惯:先写好所有<node>,再写<edge>,并用编辑器的“折叠”功能,把<node>块暂时收起来,避免视觉干扰。写完所有<edge>后,再展开<node>,逐一核对。
5.3 错误类型三:Error: Lane 'E1_0' has invalid length
表象:netconvert报错,指出某条车道长度与边的几何长度不匹配。
根因分析:<lane length="500.0"/>的值,必须等于<edge>两个端点之间的欧氏距离。如果你的<node n1 x="0" y="0"/>和<node n2 x="300" y="400"/>,那么边长是500米,<lane>的length就必须是500.0,不能是500(缺少小数点)或500.0000001(浮点误差)。
排错链路:
- 用计算器算出两个端点的距离:
sqrt((x2-x1)^2 + (y2-y1)^2)。 - 将结果精确到小数点后1位,填入
<lane length="..."/>。 - 如果边是弯曲的(有
shape属性),length必须是shape定义的曲线长度,这时需要用GIS软件或Python的shapely库计算。
避坑心得:对于直线边,永远用<edge>的length属性(如果netconvert生成的)或自己计算的精确值,不要凭感觉填写。SUMO对长度精度极其敏感。
5.4 错误类型四:Warning: No connections defined for node 'n2'
表象:路网能加载,但车辆到了交叉口就“卡住”,不转向。
根因分析:你定义了<node id="n2" type="traffic_light">,但没有为它定义任何<connection>。type="traffic_light"只是告诉SUMO“这里有个灯”,但灯管什么?必须由<connection>来定义。
排错链路:
- 找到
<node id="n2">。 - 搜索所有
<connection from="..." to="...">,确认是否有from或to指向n2所连接的边。 - 如果有,再检查这些
<connection>的fromLane和toLane索引,是否超出了对应边的车道数。
避坑心得:为每个type="traffic_light"的节点,建立一个检查清单:1)该节点连接的所有边;2)每条边的所有车道;3)为每一对可能的转向,写一个<connection>。用Excel表格管理,比在XML里硬找可靠得多。
5.5 错误类型五:Error: Invalid tlLogic ID 'n2' for node 'n2'
表象:信号灯不亮,或者所有相位同时为绿灯。
根因分析:<tlLogic id="n2">的id,必须与<node id="n2">的id完全一致。此外,<node>的type必须是traffic_light,否则SUMO会忽略这个<tlLogic>。
排错链路:
- 确认
<node id="n2" type="traffic_light">已正确定义。 - 确认
<tlLogic id="n2">的id与之完全相同。 - 确认
<tlLogic>的state字符串长度,等于该节点所有<connection>的数量。用`grep "<connection" your