ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ONE仿真器ExternalMovement移动模型:外部轨迹数据导入与配置实战

ONE仿真器ExternalMovement移动模型:外部轨迹数据导入与配置实战 做机会网络DTN仿真的人大概都经历过这么一个阶段用RandomWaypoint跑完几百上千次实验论文也发了回头仔细一想总觉得哪里不对——节点的移动太随机了跟真实场景里的移动规律完全不是一回事。真实的车载网络里车辆沿路网走有拥堵、有红绿灯真实的野生动物追踪数据里动物有作息周期、有领地意识真实的行人移动里大家会绕开障碍物、沿着道路和走廊走。这些特征靠内置的移动模型根本模拟不出来。这时候机会网络仿真器ONE里的ExternalMovement移动模型就成了绕不开的工具它允许你把外部文件里的轨迹数据直接导入仿真让节点严格沿着文件里给定的坐标和时间移动也就是常说的导入外部移动数据。这篇东西适合正在用ONE做投递率、接触时间、路由协议对比研究但觉得内置移动模型不够说服力的人。读完你能搞明白ExternalMovement的文件格式、配置方法、数据生成路径以及我实际跑过程中踩过的各种坑。1. 为什么非要用ExternalMovement机会网络仿真的最后一公里1.1 移动模型直接决定实验结论的可信度机会网络和普通网络仿真的最大区别在于节点之间不保证存在完整路径数据传输靠的是节点移动过程中偶遇产生的接触机会。也就是说节点的移动模式直接决定了接触的次数、接触的持续时间和接触的规律性进而影响所有上层协议的表现。ONE默认提供了不少移动模型RandomWaypoint、RandomWalk、MapBasedMovement、ShortestPathMapBasedMovement、BusMovement、CarMovement等。问题在于这些模型要么是纯随机游走要么是沿着地图道路做有限选择。RandomWaypoint有个著名的毛病节点会在仿真区域中间聚集瞬时速度分布和真实人类移动差很远MapBasedMovement虽然把节点限制在道路上但节点的起点、终点、路线选择依然带随机性无法精确复现真实场景。如果你的论文结论建立在移动模型不太真实但趋势仍然成立这句话上审稿人大概率会问一句换个真实轨迹数据集结论还成立吗ExternalMovement就是用来回答这个问题的。1.2 ExternalMovement给你的是什么ExternalMovement是ONE中一个特殊的内置移动模型实现它的定位非常直接不再由仿真器自己算节点往哪走而是由外部数据文件告诉仿真器每个节点在每一时刻应该在哪个坐标。外部文件的行格式采用的是NS2仿真器常用的轨迹格式所以很多现成的移动轨迹生成工具、论文公开数据集都可以直接或者转换后使用。这个模型解决的三个典型问题复现真实实验把野外车辆GPS轨迹、校园内行人定位数据导入ONE让仿真流量的路由结果与实际物理运动匹配。对照实验可重复同一份轨迹文件可以反复用于不同路由协议、不同缓存大小、不同消息生命周期的实验保证变量控制的严格性。破除随机性质疑用固定轨迹跑实验结果可以完全重现不会因为随机种子不同导致结果抖动。适用人群很明确做车载自组织网络仿真的人、用公开接触痕迹数据集做DTN评估的人、需要把真实移动数据融入到仿真链路里的人。如果你只是想做参数扫描、模型验证的粗粒度实验内置移动模型仍然够用可以不折腾ExternalMovement。2. 先把ns2轨迹文件的结构和规则看懂2.1 每个字段的含义外部移动数据文件的核心单位是一行一个位置事件格式如下$node_(0) set X_ 100.0 Y_ 200.0 T_ 0.0 $node_(1) set X_ 50.0 Y_ 80.0 T_ 0.0拆开看$node_(ID)节点标识。ID可以是纯数字也可以是带前缀的字符串取决于你要和哪个Group匹配。X_ 100.0该时刻节点的横坐标单位与仿真地图一致ONE中默认是米。Y_ 200.0该时刻节点的纵坐标。T_ 0.0该事件对应的时间戳单位是秒对应ONE仿真时钟里的时间。还有一种setdest形式的事件行也会出现在一些NS2轨迹中$node_(0) setdest 100.0 200.0 5.0表示节点从上一个位置以5.0的速度移动到(100.0, 200.0)。ExternalMovement的解析器对这类行有处理逻辑但我个人建议在使用前统一把轨迹转成set X_ Y_ T_这种纯位置事件格式少踩解析分支的坑。2.2 坐标、时间、节点ID三个最容易出错的地方第一坐标必须落在仿真世界范围内。ONE的默认地图是赫尔辛基市中心的4500m x 3400m区域。如果你的外部文件里坐标超出这个范围节点会直接消失在可视区域之外仿真照常跑但节点实际不在交互范围内。这会导致接触事件为0消息全部超时最后你得到的投递率数据完全没法用。第二时间戳必须单调递增。ExternalMovement每次读取文件时是根据时间戳顺序推进的而不是随机跳读。如果文件里出现时间戳回退解析行为会变得不可预测可能出现节点瞬移回之前的位置或者仿真时间推进到文件末尾后节点全部定格。第三节点ID的命名必须和ONE里的Group配置对得上。一个文件中可能出现$node_(0)到$node_(49)共50个节点ID对应你设置文件中Group.nrofHosts 50的Group。如果文件名里的ID前缀和Group的ID策略不一致节点位置更新就会错位表现为该动的节点不动、不该动的节点乱动。2.3 一个最小合法文件的样子下面是一份满足ExternalMovement基本要求的最小文件示例假设2个节点、3个时间点$node_(0) set X_ 100.0 Y_ 100.0 T_ 0.0 $node_(1) set X_ 400.0 Y_ 300.0 T_ 0.0 $node_(0) set X_ 110.0 Y_ 105.0 T_ 1.0 $node_(1) set X_ 395.0 Y_ 295.0 T_ 1.0 $node_(0) set X_ 130.0 Y_ 115.0 T_ 2.0 $node_(1) set X_ 380.0 Y_ 285.0 T_ 2.00秒时两个节点相隔约360米1秒时依然在移动2秒时距离缩短到250米。如果路由协议配置的通信范围是300米那么在2秒这个时刻这两个节点就能产生接触。这份文件已经足够跑起一个最小化验证。有一个细节我建议你写文件时注意末尾留一个空行最后一行也要换行。某些版本的Java文件读取在文件末尾没有换行符时会出现最后一行读取不完整的问题。3. 打开ExternalMovement配置项与源码逻辑快速对齐3.1 settings文件里真正需要改的键假设你已经把ONE工程解压好找到default_settings.txt或者自己定义的场景txt文件启用ExternalMovement需要设置下面这些键## 移动模型设置为ExternalMovement MovementModel.movementModel ExternalMovement ## 外部轨迹文件路径相对路径是相对one.jar的启动目录 ExternalMovement.file scenarios/my_trajectory.ns2 ## 是否需要打印读取的轨迹行debug用 ExternalMovement.debug true注意ExternalMovement.file是较新版本统一使用的配置键。如果你用的是比较旧的版本或某个魔改分支可能看到教程里写的是externalMovementFile。遇到这种差异不要慌打开源码搜一下字符串就可以确认方法见3.3节。除了移动模型自身的配置还要在Group层面确认节点数量。比如文件中出现了50个节点IDGroup里就应该设置Group.nrofHosts 50如果Group数量设置大于文件中的节点数ExternalMovement读取到后续节点时会拿不到对应位置数据如果小于文件节点数则会出现文件里一些ID的数据永远不会被消费白白增加读取开销。3.2 节点组数量、分组ID和文件内容的对应关系外部文件和Group的对应关系是很多初学者第一个翻车的地方。这里说清楚ExternalMovement不是所有Group共享一份全局轨迹这么简单它按节点组来匹配文件中的节点ID。一般情况下你的场景文件里有一个Group节点ID从0到N-1外部文件里也恰好是这些ID两者一一对应就能跑通。如果场景复杂一点有多个Group比如Group1是车辆、Group2是行人你想让它们各自使用不同轨迹就需要为每个Group分别指定移动模型并保证文件中的节点ID和分组策略匹配。以下配置示意了如何让两个Group分别使用不同文件## Group 1: 车辆组 Group1.groupID cars Group1.nrofHosts 20 Group1.movementModel ExternalMovement Group1.externalMovementFile scenarios/cars.ns2 ## Group 2: 行人组 Group2.groupID ped Group2.nrofHosts 30 Group2.movementModel ExternalMovement Group2.externalMovementFile scenarios/ped.ns2当节点的groupID配置为cars时对应文件中节点的ID需要能区分出属于这一组有些版本用纯数字ID有些版本要求前缀匹配。我的建议是除非有明确需求否则一个外部文件对应一个GroupID从0连续编号这是最容易排错的组织方式。3.3 源码这么写数据就是怎么被消费掉的想知道配置键到底叫什么、文件如何被消费最好的办法是直接看源码。ExternalMovement.java这个类在ONE的src/core/目录下主要逻辑集中在构造方法和getNextLocation()方法里。构造方法里通常会读取外部文件名打开文件流getNextLocation则是每次被上层调用时从文件中读取一行解析出节点ID、X_、Y_、T_然后根据当前仿真时间决定是否更新位置。大致流程是文件被逐行读取不是预先全部载入内存。这种设计的好处是大文件不会撑爆内存坏处是文件读取顺序错了会直接影响仿真结果。内部维护每个节点上一次的位置和时间。当同一节点的新一行时间戳出现时它会根据时间差计算速度向量返回给移动模型层。当文件读取完或者当前节点找不到更多数据时节点会停在上一个位置不再产生新的移动事件。看完源码你就明白为什么时间戳必须单调递增因为解析器按顺序消费文件它假设下一行的时间不早于上一行。如果时间戳乱序计算出的速度会是负数节点就会出现倒退这种诡异现象。4. 做出合法外部轨迹的三种常用路子4.1 用BonnMotion生成标准场景文件BonnMotion是一个专门生成移动模型轨迹的工具集合学术界用得非常普遍很多论文里移动模型参数就是用它生成的。它也支持输出NS2格式所以和ONE的ExternalMovement是天然搭配。基本用法类似bm -f rwp RandomWaypoint -n 50 -d 3600 -x 4500 -y 3400其中-n是节点数-d是仿真时长秒-x和-y对应仿真区域大小。默认情况下它生成的并不是直接可用的ns2格式需要转换成ns2格式。BonnMotion各版本转换参数不完全一样建议先跑一个最简单的场景再查看生成的文件内容确认前几行是否是$node_(0) set X_ ... Y_ ... T_ ...这种形式如果不是就查一下当前版本帮助信息里的转换命令。用BonnMotion的好处是轨迹生成和参数调整非常方便支持RandomWaypoint、Gauss-Markov、RandomDirection等经典模型适合做不同移动模型下的对照实验。缺点是它终究是生成的随机轨迹如果想复现真实GPS轨迹还是要走后两条路子。4.2 把GPS经纬度轨迹批量转成ns2格式真实GPS数据通常是时间戳加经纬度比如从车载设备导出的是这种形式172000, 24.9384, 60.1699, 0 172001, 24.9385, 60.1698, 1这里的经纬度不能直接填进ns2文件的X_和Y_因为ONE的地图坐标系是平面米制坐标经纬度是球面角度坐标。在市区几公里范围内可以做一个简单的等距圆柱投影转换成平面坐标核心思路是选一个中心点作为原点然后计算各点相对中心的偏移量。一段可用的Python转换脚本大概长这样import math center_lng, center_lat 24.9384, 60.1699 def gps_to_xy(lng, lat): lat0 math.radians(center_lat) x (lng - center_lng) * 111320.0 * math.cos(lat0) y (lat - center_lat) * 110540.0 return x, y # 原始数据: [(node_id, timestamp, lng, lat), ...] raw_data [ (0, 0.0, 24.9384, 60.1699), (0, 1.0, 24.9385, 60.1698), ] with open(gps_trajectory.ns2, w) as f: for node_id, t, lng, lat in raw_data: x, y gps_to_xy(lng, lat) f.write(f$node_({node_id}) set X_ {x:.2f} Y_ {y:.2f} T_ {t:.2f}\n)注意转换成平面坐标后可能会产生负坐标或者整体偏移到仿真区域外。这时候给所有点的x和y统一加一个偏移量让整条轨迹落在(0,0)到(4500,3400)范围内即可。真实轨迹数据的采样间隔可能是1秒、5秒或10秒直接用1秒精度会导致文件特别大。我的建议是先做降采样比如5秒一个点既能保持移动趋势又能显著减小文件体积仿真速度也会快很多。4.3 自定义轨迹脚本的正确姿势除了真实数据和工具生成有时候你需要构造一些极端场景来说明问题比如两个节点按固定路径周期性相遇、一组节点做编队运动。这种场景用内置移动模型反而难调用脚本生成反而是最简单的。核心思路就是把坐标表示成时间的函数。举个例子如果你想生成一个节点绕圆形轨迹运动import math radius 200.0 center_x, center_y 2250.0, 1700.0 omega 2 * math.pi / 360.0 # 一圈360秒 with open(circle.ns2, w) as f: for t in range(0, 361): x center_x radius * math.cos(omega * t) y center_y radius * math.sin(omega * t) f.write(f$node_(0) set X_ {x:.2f} Y_ {y:.2f} T_ {t:.2f}\n)生成的文件可以直接给ExternalMovement用节点会沿着圆周匀速运动。这类自定义轨迹最适合做理论验证你知道理论接触时间是多久跑仿真后看实际情况是否一致用来验证路由协议没问题也用来验证自己的ExternalMovement配置是否正确。5. 跑起来然后验证它真的在按轨迹跑5.1 GUI可视化观察的三个动作配置文件改好、轨迹文件就位后最简单的启动方式是图形界面java -jar one.jar -b 0 my_settings.txt-b 0表示从仿真的0秒开始。如果文件里时间戳起始不是0比如GPS轨迹从3600秒开始记录那启动参数里的-b就要对应调整java -jar one.jar -b 3600 my_settings.txtGUI打开后重点看三件事节点位置是否和文件里首个时间点的坐标一致。可以双击节点查看它的详细位置信息。节点移动方向是否和轨迹一致。文件中X_增大的节点在GUI里应该向右移动而不是向反方向。到达文件末尾之后节点是否停住不动。这是正常现象说明文件数据已经消费完。如果GUI里节点重叠在原点、或者全部在边界之外立刻停掉仿真回去检查坐标偏移和区域配置。5.2 打开debug输出和报告模块只靠肉眼观察在复杂场景下不够用。打开ExternalMovement.debugtrue后控制台会打印读取到的部分轨迹信息方便确认文件确实被正确解析。这个开关在正常跑批量实验时建议关掉否则IO输出会拖慢仿真速度、刷爆日志文件。更正规的验证方式是配合Report模块输出路由事件和接触事件。以接触时间报告为例在settings里加上Report.reportDir reports/ Report.reportClass ContactTimesReport跑完后打开reports/ContactTimesReport.txt里面会记录每一对节点从什么时候开始接触、持续多久。你可以从原始轨迹文件里手算一次接触时间和报告对比。比如前面2.3节那个最小文件里两个节点在2秒时刻距离250米假设通信半径是300米那么报告中应该出现这两个节点在2秒左右开始接触的记录。如果报告时间和手算对不上说明移动数据的加载或坐标单位可能有问题。这种文件轨迹手算值与报告输出值的核对是我每次配置完ExternalMovement必做的一步能挡掉大部分隐性错误。5.3 我踩过的几个坑和对应解法现象可能原因解决办法启动报找不到文件路径写错或者相对路径基准不对确认one.jar所在目录改成绝对路径测试节点全部停在一个地方文件中该节点对应的时间戳小于仿真当前时间数据已被跳过调整启动参数里的-b值让仿真从文件有效起始时间开始节点移动到区域外后消失坐标没有做平移超出了一张地图的范围将所有点的坐标加上偏移量使其落在仿真世界范围内节点移动方向反了GPS投影时经度纬度顺序反了或Y轴方向理解错误检查原始数据的经纬度列顺序文件读取看起来没生效编码或换行符问题BOM头或CRLF导致解析失败用UTF-8无BOM编码保存换行符统一为LF仿真结束节点还在动文件时间范围大于设置的仿真结束时间要么截断外部文件要么延长仿真仿真总时长文件编码这个坑特别隐蔽。Windows记事本保存UTF-8时会在文件头加入BOMByte Order MarkJava读取时第一行可能解析不正常。建议所有轨迹文件用VS Code或Notepad保存为UTF-8 without BOM或者干脆用脚本生成文件这样可以完全避开这个问题。另一个容易被忽略的是文件体积。每秒一行、50个节点跑1小时会产生18万行左右这个量级ExternalMovement处理起来很轻松。但如果文件达到几百万行比如10个节点的密集采样跑一整天仿真速度会明显下降。此时建议先降采样到2秒或5秒一个点绝大多数应用场景下这个精度完全够用。最后说一个我实际用的流程。每拿到一份新的外部轨迹数据我不会直接跑完整实验而是先构造一个最小验证场景只取前两个节点、前几十行数据配上极短的仿真时间开着GUI看节点是否按预期移动。确认无误后再逐步放大到完整数据集。这套流程看起来多花了几分钟但能在数据量放大之后省下好几天的排错时间。做ExternalMovement相关仿真最怕的不是配置复杂而是数据读进去了但读得不对结果跑完一遍才发现所有实验数据都要作废。
返回列表