LEO卫星网络仿真框架:从动态拓扑到路由验证的工程指南
发布时间:2026/9/28 8:39:43来源:尧图网络
简介面向卫星通信研究人员与工程师的低地球轨道LEO卫星网络仿真框架源自开源的Hypatia项目可用于星座布局设计、轨道动力学模拟、通信链路与网络协议验证等场景帮助在实际部署前评估覆盖范围、容量、时延等关键性能。压缩包共312个文件以py仿真脚本、cc/hC核心实现、plt绘图脚本、txt说明与配置、sh运行脚本等类型为主整体大小33.63MB另有少量文档与数据集文件目录结构清晰。已有449人学习下载。包内包含完整仿真源码、构建脚本、示例配置与结果绘图可据此复现Hypatia典型实验也能修改卫星数量、轨道高度等参数开展星座优化、协议评估或干扰分析适合具备一定网络仿真与编程基础的研发人员使用。1. 低地球轨道LEO卫星网络仿真框架到底是什么不是又一个网络模拟器低地球轨道LEO卫星网络仿真框架听名字像是一个普通的网络仿真工具换了个马甲真正上手跑一遍才会发现它和地面网络仿真器是两个物种。它能解决的问题一句话说清让路由协议和业务流量跑在一个每隔几分钟就会重新组织一次的卫星网络上并给出可信的延迟、拓扑变化和链路可用率数据。适合三类人做卫星互联网路由验证的网络工程师、评估星间链路预算的通信工程师、以及想用公开星座数据做预研的研究者。这里的“仿真框架”不只是一段代码通常以 zip 包形式分发解压后就包含轨道传播、拓扑生成、链路判断和数据导出的一整套工程链。下面我从“为什么地面工具不行”讲起一路讲到把框架输出对接进上层应用时最容易踩的坑。2. 为什么地面网络仿真工具在 LEO 场景下会集体失灵2.1 动态拓扑定时刷新模型覆盖不了分钟级链路重组地面网络仿真里拓扑变化是低概率事件故障注入后链路断开然后由路由协议收敛恢复。整个过程以秒或分钟为单位拓扑在大多数时间保持近似静止。LEO 星座完全不是这个节奏以典型的 550 km 轨道高度为例卫星绕地球一圈大约 95 分钟星下点在地面移动的速度接近 7 km/s同轨道面内两颗相邻卫星之间的相对距离每秒钟都可能变化数公里。这意味着你昨天跑通的静态路由协议今天放到 LEO 网络上每隔几分钟就要面对一次链路断开重连重连的候选链路还不止一条。很多从地面网络转过来的人第一反应是把 NS-3 的移动模型改成“绕地球转”再跑 OSPF 或 AODV。结果跑出来的收敛时间忽高忽低和任何一篇论文都对不上。原因在于地面网络工具的时间模型是“事件驱动”链路故障是小概率事件而 LEO 仿真需要“时间片驱动”每个时间片内整个网络图都可能变化。仿真框架的核心任务之一就是把轨道传播的秒级位置更新和网络拓扑的十秒级重组协调起来。这也是为什么直接在通用网络仿真器里塞移动模型永远做不出可信的 LEO 结果——它缺的不是移动性而是一整套按周期重算网络拓扑的机制。2.2 传播延迟不是常量这是随位置实时变化的函数地面互联网里两节点间的传播延迟是一个可以写进配置文件的固定值几十毫秒上下波动也不会有人在意。LEO 不是这样。以 780 km 轨道高度的典型系统为例同轨道面相邻卫星之间的距离约 2000 到 3000 km光传播延迟 7 到 10 ms跨轨道面的激光链路距离更长延迟可以冲到 20 ms 以上。更麻烦的是这些数值随时间持续变化同一对卫星之间的延迟在一个轨道周期内可能相差 30% 以上。如果你在框架里把延迟配成一个常量那么后面所有基于延迟做的路由决策、拥塞控制验证都建立在错误的时间观上。工程上的正确做法是链路接口的输入是两个节点的实时坐标延迟由框架根据坐标和距离现场计算而不是查缓存表。这个设计看起来简单但很多二次开发的框架都栽在这里——它们把延迟缓存到下一个拓扑更新点导致输出的延迟曲线呈阶梯状跳跃。你拿这种数据去做路由协议的抖动分析会发现仿真里出现根本不存在的“延迟跳变”白白浪费几天排查时间。2.3 链路预算与物理层约束大多数框架根本不建模不少 LEO 仿真框架把星间链路当成一根理想网线只判断两个卫星节点是否在通信范围内。这对拓扑层的趋势验证没问题但当你需要评估星间链路在低仰角下的实际可用性、或者激光链路在云层遮挡后的性能时这类框架直接失效。网络层协议验证和物理层可行性验证在仿真框架里是两个不同的深度层级。我一般会先搞清楚手里的框架是三层模型中的哪一种只建模拓扑可见性还是建模了链路预算余量还是连误码率和重传机制都有。最轻量的框架连天线指向都不建模它输出的是“几何可见但物理上可能根本通不了”的乐观链路。如果你要做的研究跨越网络层和物理层的边界那要么选一个带物理层接口的框架要么自己在外部把链路预算做一次过滤再把结果喂回网络层。这里的关键是你得知道框架的边界在哪里而不是拿到数据就当真。2.4 仿真时钟与轨道时间两套时间体系的接口问题还有一个容易被忽略的接口问题轨道传播用的 UTC 时间和网络仿真用的仿真时钟是两套完全不同的时间体系。轨道传播需要绝对时间输入典型如儒略日而网络仿真通常从 0 开始计时事件按相对时间排列。如果框架里这两套时间没有做偏移换算你会看到卫星位置没错但网络事件和位置对不上——链路切换发生在错误的时间点统计结果一团糟。成熟的框架会定义统一的时间原点并在对外接口里同时暴露仿真时刻和对应 UTC 时刻这个字段在做结果回放和复现时特别有用。3. 把下载的 zip 跑成最小可用的 LEO 仿真从解压到出数据3.1 先认目录结构zip 里装的到底是哪种框架下载完一个 zip 包别急着双击运行脚本。LEO 仿真框架的路子有很多种功能差异巨大先花十分钟把目录结构看清楚比后面排错省事得多。现在能看到的开源或半开源框架无论代码怎么组织基本逃不出下面这四类内容我用一个典型的目录清单表示目录或文件作用判断标准tle/ 或 constellation/卫星轨道数据TLE 或行星星座参数 CSV有没有真实星座数据还是纯参数生成propagation/轨道传播器封装SGP4 或数值积分是否支持读 TLE还是只支持简化圆轨道network/网络图构建、ISL 可见性判断、拓扑导出核心所在没有这个目录基本只算覆盖分析工具scenarios/预设仿真场景和配置文件能不能改拓扑更新步长和链路参数metrics/ 或 output/结果统计与导出有没有延迟、链路可用率的现成统计接口文档暴露的 API 和数据结构决定你上层对接的改造成本一个框架是骡子是马看 network/ 目录的厚度就知道。只有轨道覆盖分析没有网络拓扑生成的包不是仿真框架是星座设计工具。如果连接口文档都没有那就意味着你每次对接都要自己读源码找字段后续成本会很高。现在能下载到的大多数包至少会配一份 README 或者 example 脚本先把那个跑通再改自己的参数。3.2 最小星座位置计算用轨道根数生成网络节点这里用一个最小可跑的 Python 示例把星座的轨道描述转成网络节点坐标。细节上我做了简化故意不用复杂传播器先把框架的输入输出链跑通再说。这个代码只依赖标准库任何一台机器都能直接跑import math import csv # 以 550 km 轨道高度、倾角 53 度的 Walker 星座为例 # 两个轨道面每面 6 颗星相位因子 1 MU 3.986004418e14 # 地球引力常数单位 m^3/s^2 RE 6378.137e3 # 地球平均半径单位 m def orbital_position(semi_major_axis, inclination, raan, phase_angle): # 简化圆轨道假设偏心率 e0 # 返回地心惯性系ECI坐标单位米 x semi_major_axis * (math.cos(raan) * math.cos(phase_angle) - math.sin(raan) * math.sin(phase_angle) * math.cos(inclination)) y semi_major_axis * (math.sin(raan) * math.cos(phase_angle) math.cos(raan) * math.sin(phase_angle) * math.cos(inclination)) z semi_major_axis * math.sin(inclination) * math.sin(phase_angle) return x, y, z alt 550e3 a RE alt inc math.radians(53.0) # 53 度倾角适合中低纬度覆盖 satellites [] for plane in range(2): raan math.radians(plane * 180.0 / 2) # 轨道面在赤道平面均匀分布 for idx in range(6): # Walker 星座相位因子 1 的含义 # 相邻轨道面同序号卫星的相位差为 360/6*1 60 度 phase math.radians((360.0 / 6) * idx plane * (360.0 / 6) * 0.5) pos orbital_position(a, inc, raan, phase) satellites.append({ id: fP{plane}-S{idx}, x: pos[0], y: pos[1], z: pos[2], }) # 写出节点坐标文件作为后续网络图构建的输入 with open(constellation_nodes.csv, w, newline) as f: writer csv.DictWriter(f, fieldnames[id, x, y, z]) writer.writeheader() writer.writerows(satellites) print(f生成节点数: {len(satellites)})这段代码的价值在接口逻辑不在轨道精度。三个核心参数决定网络形态轨道半径 a 直接决定星间距离和延迟量级倾角 inc 影响跨轨道面链路的可见范围相位因子决定同轨道面内相邻星之间的相对位置关系。真实框架里会换成 SGP4 传播器读取 TLE 文件但对外接口是一样的输入轨道根数或 TLE输出 ECI 坐标。你只需要确认一件事——后面所有可见性判断和延迟计算用的是同一个坐标系的坐标这一点等到了第 5 章会变成一个大坑。3.3 建立星间链路可见性判断是第一个需要调参的接口有了节点坐标下一步是判断哪些卫星之间可以建立星间链路ISL。判断条件至少有三层第一层是几何可见性视线不能被地球挡住第二层是天线仰角限制仰角太低信号穿过大气层路径太长链路预算不够第三层才是通信距离上限。下面这段代码实现了前两层是框架里“卫星网络中的接口”最典型的一处import math def is_visible_and_isl(coord_a, coord_b, min_angle_deg10.0): # 输入两颗卫星的 ECI 坐标单位米最小地心夹角度 # 返回布尔值表示是否满足建链条件 # 第 1 步检查视线是否被地球遮挡 vx coord_b[x] - coord_a[x] vy coord_b[y] - coord_a[y] vz coord_b[z] - coord_a[z] # 从地心指向 A 卫星的向量 ox, oy, oz coord_a[x], coord_a[y], coord_a[z] # 计算视线方向上离地心最近的点用投影参数 t 判断 t -(ox*vx oy*vy oz*vz) / (vx*vx vy*vy vz*vz) if 0 t 1: cx ox t*vx cy oy t*vy cz oz t*vz # 最近点到地心距离小于地球半径说明视线穿过地球 if (cx*cx cy*cy cz*cz) 6378.137e3**2: return False # 第 2 步计算两星之间的地心夹角用最小仰角做简化约束 r_a math.sqrt(coord_a[x]**2 coord_a[y]**2 coord_a[z]**2) r_b math.sqrt(coord_b[x]**2 coord_b[y]**2 coord_b[z]**2) dot coord_a[x]*coord_b[x] coord_a[y]*coord_b[y] coord_a[z]*coord_b[z] cos_theta dot / (r_a * r_b) theta_deg math.degrees(math.acos(max(-1.0, min(1.0, cos_theta)))) return theta_deg min_angle_deg # 组装链路集合通常只考虑同轨道面相邻和相邻轨道面最近邻 edge_list [] for i in range(len(satellites)): for j in range(i1, len(satellites)): if is_visible_and_isl(satellites[i], satellites[j]): edge_list.append((satellites[i][id], satellites[j][id])) print(f初始可见链路数: {len(edge_list)})这个接口函数用两个坐标就返回一个布尔值但里面藏着两个工程决策。第一是地球遮挡的判定精度用投影法求最近距离是解析解速度快但没考虑地球扁率和大气层折射对绝大多数网络层仿真已经足够。第二是 min_angle_deg 的取值设 10 度时链路可见窗口长但信号低仰角穿过大气层链路预算不允许设 25 度时链路更可靠但拓扑断裂更频繁。我一般先用 15 度作为初始值观察延迟曲线是否平滑再根据自己的研究场景调整。3.4 跑一个 30 分钟的仿真输出时序拓扑文件节点有了链路判断有了仿真就是按时间步推进重复“更新位置 → 判断可见性 → 计算延迟 → 记录结果”这个循环。下面这段代码把整个流程串起来并导出标准的 JSON 时序文件import json import math def run_simulation(duration_min30, time_step_s10): # 按固定时间步推进仿真每个时间步重建网络拓扑 results [] current_time 0 # 仿真时钟单位秒 step time_step_s while current_time duration_min * 60: edges_snapshot [] for i in range(len(satellites)): for j in range(i1, len(satellites)): if is_visible_and_isl(satellites[i], satellites[j]): # 计算实时距离注意坐标单位是米 d math.dist( (satellites[i][x], satellites[i][y], satellites[i][z]), (satellites[j][x], satellites[j][y], satellites[j][z]) ) edges_snapshot.append({ src: satellites[i][id], dst: satellites[j][id], distance_km: round(d / 1e3, 2), delay_ms: round(d / 3e8 * 1000, 3) # 光速 3e8 m/s }) results.append({ time_s: current_time, edge_count: len(edges_snapshot), edges: edges_snapshot }) current_time step # 导出 JSON便于用任何语言做后处理 with open(sim_result.json, w) as f: json.dump(results, f, indent2) return results res run_simulation() print(f时间步数: {len(res)}) print(f平均链路数: {sum(r[edge_count] for r in res) / len(res):.1f})这个循环的时间复杂度是 O(N²)当卫星数量到几百颗、时间步长到 1 秒时性能是致命的。工程上最常见的优化是缩小候选链路集合每个轨道面内只检查前后两颗邻星跨轨道面只检查相位最近的候选星把每颗星的候选从 N 降到常数。逻辑没变结果也基本一致但仿真时间可以从几小时降到几分钟。导出格式我用 JSON 是因为后续不管是 Python 还是 Rust 脚本读起来都友好如果数据量太大换成 CSV 或者按时间片分文件更适合。3.5 第一份结果怎么看延迟曲线和链路数量变化趋势跑完上面这段打开 sim_result.json第一件事不是算平均延迟而是看两类趋势链路数量随时间的变化曲线是否平稳单条链路的延迟曲线是否平滑。一个正常的 LEO 星座仿真总链路数应该在一个区间内波动不会有剧烈的尖峰单条链路的延迟应该是缓慢变化的曲线而不是锯齿状跳跃。如果你看到总链路数忽高忽低多半是时间步长太大或者星座构型参数不合理如果你看到延迟曲线呈阶梯状说明框架在缓存延迟而不是实时计算。这两种异常在第 5 章都会详细展开排查方法。4. 接口设计与参数标定让仿真结果能对接真实工程4.1 外部接口路由协议和上层应用到底需要什么字段仿真框架跑出来的数据最终要喂给路由协议仿真或者业务流量模型。而路由协议需要的最少信息是四样东西源节点、目的节点、下一跳候选集合、链路开销。链路开销通常是延迟、丢包率或两者的加权。如果你的框架导出的是每个时间步的延迟值但没有把链路两端的节点 ID 同时给出那你的上层脚本还得自己匹配一旦节点命名规则稍有不同调试就是一场灾难。我在自己项目里对框架输出接口的要求很具体每条链路记录必须包含 time_s、src_id、dst_id、propagation_delay_ms、link_available 五个字段。time_s 用于对齐时间窗src_id 和 dst_id 用于追踪同一条链路随时间的变化link_available 标记当前时间步这条链路是否在可见窗口内。很多论文复现跑不通不是路由算法写错了而是读到的链路数据和拓扑事件根本不是同一个时刻的。这个字段设计看起来基础但实际能避免绝大多数莫名其妙的“对不上”。4.2 三个必调参数轨道高度、最小仰角、链路容量仿真框架里能调的参数很多但真正对结果影响最大、每个新场景都必须重新考虑的是下面这张表里的三个参数快速验证初始值工程参考范围直接影响轨道高度550 km500~1200 km延迟基线、覆盖周期、链路距离最小仰角15°10°~25°链路可用时间、拓扑断裂频率链路容量100 Mbps1~10 Gbps激光链路流量瓶颈、队列延迟、拥塞行为第一个是轨道高度。它决定延迟量级和可见窗口550 km 轨道星地单跳延迟大约 1.8 到 3.5 ms星间链路相邻星延迟约 3 到 7 ms如果把轨道高度改成 1200 km单跳星地延迟涨到 4 到 7 ms。第二个是最小仰角直接决定单条星地链路的可用时间和卫星覆盖半径。低仰角覆盖范围大但信号质量差高仰角正好相反这个参数的设定要结合链路预算来定不能拍脑袋。第三个是链路容量它不改变拓扑结构但决定业务流量会不会在网络里形成瓶颈。很多人偷懒把链路容量设成无穷大结果跑出来的吞吐量好看得一塌糊涂但完全不能用于工程判断。标准做法是先设一个合理的容量值然后观察业务的端到端延迟是否出现排队增长如果出现说明网络确实存在瓶颈。4.3 结果校验用公开 TLE 数据验证轨道传播器在把任何仿真结果写进报告之前必须做一次轨道传播的交叉验证。做法是拿一颗公开的卫星 TLE 数据用框架里的传播器计算它在某个时刻的 ECI 坐标再和外部已知可信的结果对比。下面这段代码演示了用 sgp4 库做 TLE 传播的标准接口# 交叉验证示例验证轨道传播器的位置输出 # 使用 sgp4 库读取两行 TLE 并计算指定时刻的位置 from sgp4.api import Satrec from sgp4.api import jday # TLE 是公开数据格式可以参考空间目标目录中的真实数据 # 这里展示的是接口调用方式TLE 内容需替换为你验证的目标卫星 tle_line1 1 00001U 00001A 24001.50000000 .00000000 00000-0 00000-0 0 0001 tle_line2 2 00001 51.6000 10.0000 0001000 100.0000 260.0000 15.00000000 0000 sat Satrec.twoline2rv(tle_line1, tle_line2) # 指定要计算的时刻2024-01-01 12:00:00 UTC jd, fr jday(2024, 1, 1, 12, 0, 0) e, r, v sat.sgp4(jd, fr) if e 0: print(fECI 位置(km): x{r[0]:.3f}, y{r[1]:.3f}, z{r[2]:.3f}) else: print(f轨道传播失败错误码: {e})这里第一个容易踩的坑是时间输入。jday 函数接收的是 UTC 时间如果你在脚本里用了本地时间而没有做时区换算位置偏差会大到几十公里。第二个坑是 TLE 数据的时效性TLE 是考虑了大气阻力等摄动的平均轨道根数它会随日期变化拿一星期前的 TLE 算今天的卫星位置误差可能会到几十公里甚至更糟。所以交叉验证的准则是用同一份 TLE、同一个时刻把框架输出和外部工具结果做对比偏差在 1 公里以内算正常超过 10 公里就要回头查时间系统和坐标转换。5. LEO 卫星网络仿真避坑手册5 个让我返工过的真实问题5.1 现象拓扑更新频率太高仿真跑一晚都跑不完原因把可见性判断设成每个时间步全对重算时间步取 1 秒卫星数取 100 颗每秒就要做近 5000 次可见性判断再叠加上坐标传播的三角函数运算整个仿真慢到没法用。解决先量瓶颈再谈优化。时间步长放大到 10 秒或 30 秒通常对统计结果影响很小同时把候选链路集合从全对判断改为按轨道面剪枝每颗星只检查同面邻星和相邻面的最近邻。还有一个实用技巧是几何缓存——两颗卫星的相对位置在一个时间步内变化不大可以每 5 个时间步更新一次可见性缓存精度损失微乎其微但速度能提升一个数量级。5.2 现象星间链路几乎不中断仿真结果一片乐观原因建链条件只判断了“距离小于最大通信距离”没判断地球遮挡和天线指向范围。结果就是仿真报告里链路可用率接近 100%和真实 LEO 系统的几十次切换差距巨大。解决把建链判定条件补全至少加上几何视线是否穿过地球和最小仰角约束。如果你的框架接口里没有暴露天线指向角这个字段至少先补上前两个约束得到的结果立刻会向工程靠拢。这个改动通常只需要几十行代码但能让链路切换次数从几跳变成几十跳数据可信度完全不同。5.3 现象延迟曲线呈阶梯状路由协议跟着误判原因框架把链路延迟按拓扑更新时间步缓存了两个拓扑更新点之间延迟不变更新后直接跳变。时间步长设得越大阶梯越明显。路由协议拿这种延迟做选路会把并不存在的延迟跳变当成网络拥塞产生不必要的切换。解决把链路延迟的计算从拓扑更新逻辑中拆开让延迟在每个统计周期内实时计算。如果框架不允许你独立控制这两个时间尺度那就把拓扑更新步长调到 5 秒以内让阶梯小到肉眼看不出。注意这会增加计算量所以要先做第 5.1 条的剪枝优化。5.4 现象同一条链路两个模块导出的距离对不上原因十有八九是坐标系混用。轨道传播模块输出 ECI 坐标惯性系而地面站或某些链路判断模块用的是 ECEF 坐标地固系两者之间缺了旋转矩阵转换。仿真框架里两套坐标系并存很常见但接口层必须明确约定。解决在框架的对外接口层统一坐标约定全部转到 ECEF 或全部转到 ECI禁止内部混用。如果你拿到的是半成品框架直接搜代码里的 coord_system 或 frame 字段把混用点逐个修掉。修完用 4.3 节的 TLE 交叉验证方法再做一次坐标校验直到两个模块导出的同一条链路距离偏差小于 1 米。5.5 现象仿真结果和论文里的实验数据对不上原因不是你们用的星座参数不同而是评价指标的定义不同。“覆盖率”可能是瞬时覆盖面积、累计接触时间、或地面网格点覆盖百分比“切换次数”可能按链路从建立到断开计也可能按路由下一跳变化计。定义不对齐数字永远对不上。解决动手复现之前先把目标指标的定义写在配置文件的注释里。每跑一个场景都在输出目录里附带一份指标定义说明。这样后续调试不用反复猜“这个 0.87 到底是什么含义”也方便和其他团队对齐口径。6. 进阶把仿真框架接到业务流上一个值得多花一周的接口大多数 zip 包默认只输出网络指标不承载业务流量。你想验证一个路由协议在动态拓扑上的真实表现需要自己加一层业务模型——生成业务流、按时间片分配流量、在每个时间步重算路由。常见做法是把框架输出的动态拓扑转成一张时序链路表然后喂给流量仿真或者直接在框架内嵌一个简化版的路由算法。我个人的工作习惯很简单只追踪两个指标——端到端延迟的累积分布和链路切换次数。具体操作是让 20 到 30 个业务流在仿真网络里跑完一个完整的轨道周期把每个流的端到端延迟记录成直方图然后对比链路切换前后延迟的瞬时变化看是否有超过 50% 的抖动尖峰。如果切换总伴随着延迟翻倍说明链路切换逻辑和路由协议需要协同设计这是只看平均延迟永远发现不了的问题。最后用小步长比如 5 秒重跑一遍关键场景验证结果稳定后再放大步长跑长周期精度和速度兼顾。每一次交付框架前我会确保 4.3 节的 TLE 位置校验通过再跑一遍 5.4 条的坐标系排查总共花不了半天时间却能省掉后面几个月的返工。仿真框架的价值不在于代码规模而在于轨道、链路、网络三层拼起来的方式是否可信接口是否够用。希望这篇笔记帮你在跑通 LEO 仿真框架的路上少踩几个坑把时间花在真正要验证的问题上。本文还有配套的精品资源点击获取
网站建设高端定制企业官网