简介:面向X-Plane 11飞行模拟器开发者、座舱与运动平台集成者及仿真联调人员的中文UDP通信说明文档,用于解决外部程序与模拟器之间收发飞行数据、控制指令时协议不清晰的问题。资源包仅含1个docx文件,约44KB,体量轻便,适合随查随用;文档围绕端口49000的通信机制展开,梳理消息类型、数据格式与交互示例,并给出ACFN、PREL、ACPR等新结构飞机消息、RPOS位置输出及空值终止、网络数据转储到error.out等排错要点。内容还提示从X-Plane 10迁移到11的注意事项,区分插件与UDP消息两种实现路径,便于读者按需选择快速读写大量变量,或自行编写外部程序收发数据。已有2063人学习,可作为飞行仿真联调、自定义仪表与移动地图开发的协议参考。
1. 为什么一个端口就能驾驶飞行模拟器:UDP 接口的反直觉之处
X-Plane 11 的 UDP 通信说明文档里有一个很容易被忽略的设定:模拟器永远在 49000 端口收包,但回包地址不是你预先配置的服务器地址,而是你发包时的源 IP 和源端口。你用一个临时 socket 发一条 RPOS,位置数据会回到那个临时端口;换一个 socket,旧订阅就收不到。这个设计把“订阅”和“连接”拆开了:没有握手,没有会话表,谁发订阅谁负责收。它解决的是外部视觉系统、运动平台、自定义座舱和飞行模型联调时最怕的进程耦合问题——不用写插件,不用和模拟器同机,甚至不用知道对方用什么语言,只要会发 UDP,就能把飞行状态拉出来,或者把姿态灌进去。搞懂这个回包规则,后面所有消息类型才有落脚点。
2. 消息序言与结构体对齐:4 字节标签背后的二进制契约
2.1 五字节序言:为什么标签后面必须跟一个 0
所有发往 X-Plane 的 UDP 消息都遵循同一个开头:4 个字符的消息标签,加 1 个值为 0 的字节,后面才是数据。比如 RPOS 的完整开头是RPOS\x00,不是RPOS四个字节。这个 0 字节不是分隔符装饰,它让标签在 C 语言里成为一个合法的空终止字符串,接收端可以先读 5 字节、按字符串比较,再决定后面按哪种结构体解析。代价是每条消息都多 1 个字节,好处是解析器不用维护“标签长度表”,也不怕标签后面紧跟二进制数据时被误读。
字符串参数同样要空终止。RPOS 后面跟的是一个表示“每秒发送多少次位置”的字符串,发送 60 次就是RPOS\x00加60\x00。如果只发60不带结尾的 0,接收端会继续往后读,直到碰到内存里偶然出现的 0,频率可能变成 60 加上后面几个乱码字符,也可能直接解析失败。这个坑在调试时很常见:包发出去了,模拟器也收了,但频率不对,或者结构体整体偏移,原因就是字符串少了结尾。
用 Python 发一条 RPOS 并收回流数据,代码可以写成下面这样。注意bind这一步:不绑定源端口,系统也会分配一个,但你必须用同一个 socket 去recvfrom,因为回包只认发包时的源地址和源端口。
import socket sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind(("0.0.0.0", 0)) # 让系统分配源端口,回包会回到这个端口 payload = b"RPOS\x00" + b"60\x00" # 5 字节序言 + 空终止的频率字符串 sock.sendto(payload, ("127.0.0.1", 49000)) data, addr = sock.recvfrom(2048) print(len(data), data[:5]) # 正常应看到 69 b'RPOS\x00'这段代码里b"RPOS\x00"是 5 字节序言,b"60\x00"是频率参数,recvfrom拿到的包长度是 5 字节标签加 64 字节位置数据。如果你把bind去掉,很多系统仍然能发,但回包可能落到一个已经关闭的临时端口,表现就是“发得出去、收不回来”。这不是模拟器的问题,是 UDP 的无连接语义在提醒你:订阅关系只存在于源地址和源端口上。
2.2 结构体对齐:int 和 double 之间那 4 个字节去哪里了
说明文档里反复强调“结构对齐”,因为 X-Plane 收发的不是文本协议,而是把 C 结构体按内存布局直接塞进 UDP 包。主流 64 位编译器默认按成员自然边界对齐:4 字节的 int 和 float 按 4 字节对齐,8 字节的 double 按 8 字节对齐,结构体整体再按最大成员对齐,末尾可能补填充。这个规则本身不复杂,复杂的是很多人手动拼包时只按字段顺序拼,忘了编译器会在 int 和 double 之间插空洞。
以 VEHX 为例,字段顺序是 int、double、double、double、float、float、float。int 占 4 字节,下一个 double 要求偏移是 8 的倍数,所以 int 后面通常要补 4 字节,double 才能从偏移 8 开始。三个 double 结束后偏移是 32,三个 float 占 12 字节,结构体最大对齐是 8,末尾还可能补 4 字节到 48。不同编译器、不同对齐选项下,这个结构可能是 44 字节,也可能是 48 字节。协议要求双方对“实际字节数”达成一致,而不是对“字段顺序”达成一致。
struct vehx_packet { int p; // 偏移 0,4 字节 // 这里通常有 4 字节填充,让下一个 double 落在 8 字节边界 double lat; // 偏移 8 double lon; // 偏移 16 double ele; // 偏移 24 float psi; // 偏移 32 float the; // 偏移 36 float phi; // 偏移 40 // 结构体按 8 字节对齐,末尾可能还有 4 字节填充 };我一般会先用sizeof和offsetof把每个字段的偏移打印出来,再和模拟器 dump 出来的“期望结构大小”对一遍。说明文档里提到,可以在设置菜单里打开“转储网络数据到 log.txt”,让 X-Plane 把收到的数据和它期望的结构大小写进日志。这个日志很朴素,但它是排查对齐问题最诚实的证据:你发 44 字节,它期望 48 字节,日志会直接告诉你差多少,不用靠猜。
2.3 端序、定长字符串与接收端的强转陷阱
XCHR、XINT、XFLT、XDOB 这些类型名在文档里反复出现,它们分别表示字符、4 字节整数、4 字节浮点和 8 字节双精度浮点,并且按本机字节序排列。大多数桌面平台是小端,跨平台联调时如果一方是大端,整数字段会变成天文数字。更稳妥的做法是发送前显式按小端打包,接收时也按小端解包,不要依赖“大家应该都一样”。
定长字符串是另一个容易翻车的地方。ACFN 结构里有一个char acfn_path_rel[150],后面紧跟一个 intlivery_index。150 不是 4 的倍数,所以livery_index前面通常会有 2 字节填充,整个结构大小是 160。如果你手动拼包时只写 150 字节路径、紧接着写 4 字节涂装索引,接收端按 160 字节结构去读,涂装索引就会错位,飞机可能加载了但涂装不对,或者加载直接失败。
struct acfn_struct { int acfn_p; // 偏移 0:0 表示主飞机,1..19 表示其他飞机 char acfn_path_rel[150]; // 偏移 4:相对路径,必须 '\0' 结尾 // 偏移 154 到 155 通常是 2 字节填充 int livery_index; // 偏移 156:涂装索引 };接收端还有一个陷阱:不要因为标签是 5 字节,就把recvfrom的缓冲区直接强转成结构体指针。RPOS 返回包以RPOS\x00开头,后面紧跟 double,double 的起始偏移是 5,不是 8。x86 上这种未对齐访问可能能跑,换到对齐要求严格的平台就会崩,或者编译器优化后读出错值。正确做法是把数据逐字段拷贝到对齐的局部变量,或者用memcpy搬进结构体,再访问成员。协议是字节流,不是内存映射,这一点想清楚,后面解析 RPOS、RREF 会少很多玄学问题。
3. 输出通道:RPOS、RADR、FLIR 与 RREF 的数据回流
3.1 RPOS:位置回流的 64 字节定长包
RPOS 是外部视觉系统和移动地图最常用的输出消息。你发RPOS\x00加频率字符串,X-Plane 就按这个频率把飞机的位置、姿态、速度、角速度打包发回你的源地址和源端口。返回包以RPOS\x00开头,后面是 64 字节紧排数据:3 个 double 加 10 个 float。字段顺序和偏移如下表,偏移从包开头算,标签占 0 到 4。
| 字段 | 类型 | 偏移 | 含义 |
|---|---|---|---|
| dat_lon | double | 5 | 经度,度 |
| dat_lat | double | 13 | 纬度,度 |
| dat_ele | double | 21 | 海拔高度,米 |
| y_agl_mtr | float | 29 | 离地高度,米 |
| veh_the_loc | float | 33 | 俯仰角,度 |
| veh_psi_loc | float | 37 | 真航向,度 |
| veh_phi_loc | float | 41 | 滚转角,度 |
| vx_wrl | float | 45 | 东向速度,米每秒 |
| vy_wrl | float | 49 | 垂向速度,米每秒 |
| vz_wrl | float | 53 | 南向速度,米每秒 |
| Prad | float | 57 | 滚转角速度,弧度每秒 |
| Qrad | float | 61 | 俯仰角速度,弧度每秒 |
| Rrad | float | 65 | 偏航角速度,弧度每秒 |
解析时用struct.unpack_from从偏移 5 开始读,不要先按 8 字节对齐去补三个字节。下面这段代码把 13 个值一次解出来,变量名和协议字段一一对应,方便后面直接喂给渲染循环。
import struct def parse_rpos(data): if data[:5] != b"RPOS\x00": return None vals = struct.unpack_from("<ddd" + "f" * 10, data, 5) lon, lat, ele = vals[0], vals[1], vals[2] y_agl, the, psi, phi = vals[3], vals[4], vals[5], vals[6] vx, vy, vz = vals[7], vals[8], vals[9] prad, qrad, rrad = vals[10], vals[11], vals[12] return { "lon": lon, "lat": lat, "ele": ele, "agl": y_agl, "pitch": the, "heading": psi, "roll": phi, "vx": vx, "vy": vy, "vz": vz, "roll_rate": prad, "pitch_rate": qrad, "yaw_rate": rrad, }dat_lon、dat_lat是 double,精度足够描述飞机在地球上的位置;速度和角速度用 float,省带宽也够用。频率字符串发 60 表示每秒 60 次,但实际能不能稳定收到 60 次,取决于你的接收循环有没有被渲染或其他逻辑阻塞。UDP 不排队,收慢了就丢,视觉系统会表现为位置跳变。调试时先降到 20 或 30,确认字段对得上,再往上加。
注意:RPOS 的回包地址是你发包时的源 IP 和源端口,不是 49000。换 socket 就要重新发订阅,旧 socket 关掉后回包不会自动迁移。
3.2 RADR 与 FLIR:按帧率节流的批量数据
RADR 和 RPOS 的请求格式很像:RADR\x00加一个字符串,表示每帧要发多少个雷达点。区别在语义:RPOS 的频率是“每秒多少次”,RADR 的频率是“每帧多少个点”。返回包以RADR\x00开头,后面是 N 组 4 个 float:经度、纬度、降水等级 0 到 100、风暴顶高度米。N 由你请求的字符串决定,接收端要先读标签,再按5 + N * 16算包长,不能假设固定大小。
FLIR 则是图像流,请求格式也是FLIR\x00加每秒帧数字符串。图像数据通常远大于一个 MTU,接收端不能假设“一个 UDP 包就是一帧”,而要按文档给出的帧结构重组。文档里提到有单独的提取代码说明,说明图像流不是简单地把像素塞进一个包,而是有分帧和组装规则。频率参数在这里是带宽闸门:10 帧每秒和 30 帧每秒对网络和接收缓冲区的压力完全不同。先用低帧率把帧头、帧长、像素格式对齐,再谈实时性,否则很容易看到花屏或半帧。
RADR 和 FLIR 的共同设计动机是“批量数据按帧节流”。仿真外设往往不需要每个点都实时,但需要稳定批量;UDP 不保证顺序,接收端要用帧号或包内偏移自己重组。这类消息的调试重点不是字段含义,而是包长和分片:先确认单个包的字节数,再确认一帧由几个包组成,最后才看内容。
3.3 RREF:用整数句柄订阅任意 dataref
RPOS 和 RADR 是预定义的高频包,字段固定;RREF 是通用订阅通道,你想读哪个 dataref 就发哪个路径。请求结构是 4 字节频率、4 字节句柄、400 字节路径字符串。频率是每秒发送次数,句柄是你自己编的整数,X-Plane 回包时原样带回,用来区分多个订阅。路径要空终止,不足 400 字节补零。数组类型的 dataref 在末尾加[索引],索引从 0 开始,[1]表示第二个元素。
import socket import struct sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind(("0.0.0.0", 0)) dref = b"sim/flightmodel/engine/POINT_thrust[1]" # 每秒 20 次,句柄 1001,路径区固定 400 字节 pkt = b"RREF\x00" + struct.pack("<ii400s", 20, 1001, dref) sock.sendto(pkt, ("127.0.0.1", 49000)) while True: data, _ = sock.recvfrom(2048) if data[:5] == b"RREF\x00": en, val = struct.unpack_from("<if", data, 5) print(en, val) # en 是 1001,val 是推力值返回包以RREF\x00开头,后面是 4 字节句柄和 4 字节 float 值。即使 dataref 本身是整数,返回也用 float 表达,读取端要按 float 解,不要按 int 解。句柄不是 X-Plane 分配的,是你自己在请求里填的,所以你可以用 1001、1002 这种编号管理几十个订阅。把频率改成 0 再发一次,就是停止这个句柄的推送;不需要单独的发取消消息。
RREF 的价值在于“按需订阅”。RPOS 一次性给你位置和姿态,但你可能只想要发动机参数;RREF 让你只拉需要的 dataref,降低带宽和解析成本。代价是路径字符串必须准确,数组索引必须从 0 开始,路径不存在时不会有明显报错,只是收不到回包。调试时先用一个已知存在的路径,比如油门或襟翼,确认句柄和回包格式,再换成业务字段。
4. 输入通道:VEHX、PREL、ACFN 与 DREF 的控制写入
4.1 VEHX:把外部飞行模型的位置和姿态灌进去
VEHX 是“用我的飞行模型驱动 X-Plane 视觉系统”的核心消息。你发VEHX\x00,后面跟一个结构体:飞机索引、纬度、经度、海拔、航向、俯仰、滚转。索引 0 表示你正在飞的主飞机,1 到 19 表示其他飞机。这个接口不传速度,也不传角速度,它只负责把飞机摆到某个位置和姿态;速度由模拟器根据位置变化或内部模型推算。所以外部模型每帧发一次 VEHX,模拟器看到的是位置和姿态的连续变化。
import struct # 以 48 字节布局为例:int + 4 字节填充 + 3 个 double + 3 个 float + 4 字节尾部填充 payload = b"VEHX\x00" + struct.pack( "<i4xdddfff4x", 0, # 主飞机 12.34, # 纬度,度 56.78, # 经度,度 100.0, # 海拔,米 90.0, # 航向,度 2.0, # 俯仰,度 0.0, # 滚转,度 )<i4xdddfff4x里的4x就是显式补洞:第一个4x补在 int 和 double 之间,第二个4x补在三个 float 之后。如果你的模拟器 dump 出来的结构大小是 44 字节,说明它不要求尾部填充,那就把最后一个4x去掉。这里没有“谁对谁错”,只有双方是否一致。发送频率建议跟模拟器帧率接近,不要超过 60;发太快不会让画面更顺,只会让接收队列堆积,表现成姿态抖动。
VEHX 的另一个细节是坐标系。纬度、经度是度,海拔是米,航向是真航向,俯仰和滚转是度。外部模型如果用的是弧度,或者航向用磁航向,直接灌进去就会偏。先在地面静止状态发一组已知值,看模拟器里的飞机是不是朝向正确、高度正确,再接入动态模型。这个“静态对齐”步骤很土,但能省掉后面大量坐标变换的猜测。
4.2 PREL 与 ACFN/ACPR:加载飞机和摆放到指定位置
ACFN 负责加载飞机。结构是飞机索引、150 字节相对路径、涂装索引。索引 0 是主飞机,1 到 19 是其他飞机;路径是相对路径,不是绝对路径;涂装索引选涂装。发送ACFN\x00加结构体,模拟器就按路径加载。路径字符串必须空终止,150 字节后面有 2 字节填充,再是涂装索引。手动拼包时如果漏了这 2 字节,涂装索引会读到路径的尾部字符,表现是飞机能加载但涂装随机或者加载失败。
PREL 负责把飞机放到某个位置。它的结构比 ACFN 复杂:开始类型枚举、飞机索引、机场 ID、跑道索引、跑道方向、纬度、经度、海拔、航向、速度。开始类型决定后面哪些字段有效。比如loc_specify_lle表示直接用经纬高定位,这时机场 ID 和跑道字段可以置零;loc_ram表示停机坪启动,loc_tak表示跑道起飞,loc_vfr表示 VFR 进近。枚举值不是随便编的,文档里给了 5 到 36 的编号,选错类型会出现“飞机被放到莫名其妙的位置”或者“加载后立刻重置”。
struct PREL_struct { int type_start; // 开始类型,如 loc_specify_lle = 6 int p_idx; // 0 主飞机,1..19 其他飞机 char apt_id[/*idDIM*/]; // 机场 ID,按机场定位时使用 int apt_rwy_idx; // 跑道索引 int apt_rwy_dir; // 跑道方向 double dob_lat_deg; // 纬度 double dob_lon_deg; // 经度 double dob_ele_mtr; // 海拔,米 double dob_psi_tru; // 真航向,度 double dob_spd_msc; // 速度,米每秒 };apt_id的长度由协议头文件里的idDIM决定,文档正文没有给具体数字。这是一个很典型的“不能手算”的结构:如果idDIM导致后面的 int 或 double 偏移不对,编译器会插填充,你按自己猜的长度拼包就会整体错位。稳妥做法是用sizeof和offsetof打印实际偏移,或者先用loc_specify_lle这种不依赖机场 ID 的类型,把经纬高定位跑通,再处理机场和跑道。
ACPR 是 ACFN 加 PREL 的组合:先加载飞机,再把它放到位置。发送ACPR\x00后连续跟两个结构体,注意两个结构体之间也有对齐问题。ACPR 适合“一键摆飞机”的场景,比如外部模型启动时把主飞机放到指定经纬高,或者多机编队时批量摆放。调试时先单独发 ACFN 确认飞机能加载,再单独发 PREL 确认位置能摆对,最后合并成 ACPR;直接上组合包,出错时很难判断是加载失败还是定位失败。
4.3 DREF、CMND 与 DATA:写 dataref、触发命令、改数据输出
DREF 用来给任意 dataref 写一个浮点值。结构是 4 字节 float 值加 500 字节路径,整包固定 509 字节:5 字节序言、4 字节值、500 字节路径区。路径要空终止,剩余补零。文档里举的例子是打开防冰开关:值 1.0,路径sim/cockpit/switches/anti_ice_surf_heat_left。这个接口很强,但也有边界:不是所有 dataref 都可写,有些值由物理模型决定,你写进去会被下一帧覆盖。先用 RREF 读同一个路径,确认它存在且可读,再尝试写,能少走很多弯路。
import struct path = b"sim/cockpit/switches/anti_ice_surf_heat_left" # 5 字节序言 + 4 字节 float + 500 字节路径区 = 509 字节 payload = b"DREF\x00" + struct.pack("<f", 1.0) + path.ljust(500, b"\x00")CMND 用来触发模拟器命令。数据部分就是命令字符串,发送CMND\x00加字符串加空终止。命令字符串可以从设置菜单的按钮高级选项卡里看到,比如sim/flight_controls/flaps_up。CMND 不传参数,只触发一次;需要连续控制时,要么重复发,要么改用 DREF 写对应的开关 dataref。CMND 的好处是命令是模拟器自己定义的,语义明确;坏处是字符串必须完全匹配,大小写、下划线、路径层级错一个字符就不会执行。
DATA 用来设置数据输出屏幕里的变量。结构是 int 索引加 8 个 float 值。索引对应数据输出列表中的位置,值最多 8 个,很多输出用不满。发送DATA\x00加结构体,就能通过 UDP 控制一些原本要在界面上调的变量。索引填 -999 表示释放这个变量,让模拟器回到默认控制。DATA 的边界是“不是所有变量都能设”,比如马赫数由飞机速度决定,你设了也会被覆盖。先确认你要控制的变量在数据输出列表里存在,再用 -999 释放,避免外部程序退出后模拟器还锁在最后写入的值上。
DSEL 和 USEL 这一族消息用整数序列选择或取消选择数据输出。DSEL 后面跟一串整数,表示要输出列表中的第几项,第一项是 1,第二项是 2。发送DSEL\x00加整数序列,模拟器就按互联网设置屏幕里列出的地址推送这些数据。这个接口适合“外部座舱启动时一次性打开一批输出”,不用逐条进界面勾选。取消选择是反向操作,调试时记得把不用的输出关掉,否则多个外部程序同时订阅,带宽和解析负担都会上去。
5. 避坑与排查:UDP 包被静默丢弃的五个现场
5.1 回包地址错位:为什么换了 socket 就收不到
现象是发送 RPOS 后recvfrom一直超时,但模拟器日志显示它已经收到了请求。原因通常不是防火墙,而是回包地址规则:X-Plane 把数据发回你发送请求时的源 IP 和源端口,不是发回 49000。如果你用sendto发完就关闭 socket,或者中间设备改了源端口,回包会落到一个没人监听的端口。另一个常见情况是你在两个线程里用了两个 socket,一个发订阅,另一个收数据,收的那个 socket 从来没发过包,自然收不到。
解决方法是让同一个 socket 完成发送和接收,并且显式bind到本地地址,让系统分配一个稳定源端口。不要在发完订阅后立刻关闭 socket;订阅的生命周期和 socket 绑定。如果中间有 NAT 或防火墙,确认它没有改写源端口,或者把接收超时调长,先看模拟器日志里有没有“sent to”记录。调试阶段可以把频率降到 1 次每秒,减少干扰,确认回包路径通了再升频。
5.2 结构体大小对不上:dump 日志里最诚实的证据
现象是消息发出去了,模拟器不报错,但飞机不动、位置不对,或者加载失败。原因多半是结构体大小和模拟器期望的不一致:int 和 double 之间少补了 4 字节,char 数组后面少补了 2 字节,或者末尾多补了 4 字节。不同编译器、不同语言、不同打包方式都会改变实际字节数,而 UDP 包本身不带“字段偏移表”,接收端只能按它自己的结构体去读。
解决方法是打开“转储网络数据到 log.txt”,让 X-Plane 把收到的数据和期望的结构大小写进日志。然后在你自己的代码里打印sizeof和每个字段的offsetof,逐项对比。手动拼包时用显式填充,比如 Python 的4x、2x,C 里用固定宽度类型和static_assert检查大小。接收端不要直接把缓冲区强转成结构体指针,先memcpy到对齐的结构体,再访问字段。这个习惯能同时解决对齐和未对齐访问两个问题。
5.3 字符串没有空终止:路径被读成乱码
现象是 ACFN 加载飞机失败,或者 CMND 命令不执行,但包的标签和长度看起来都对。原因通常是字符串字段没有以 0 结尾。C 字符串靠结尾的 0 判断长度,如果 150 字节的路径区全部填满可见字符,后面没有 0,接收端会继续往后读,把涂装索引或下一个字段当成路径的一部分。DREF 的 500 字节路径区也一样,路径后不补零,模拟器可能把整包剩余字节都当成路径。
解决方法是所有字符串字段发送前都做空终止和定长补零。Python 里用path.ljust(500, b"\x00"),C 里用memset清零后再strncpy,并确保最后一个字节是 0。不要用可见空格填充定长字段,空格是路径的一部分,不是终止符。发完可以用抓包工具看一眼十六进制,确认字符串末尾确实有00,而不是20。
5.4 频率和带宽:为什么画面越调越抖
现象是 RPOS 频率调到 60 后,视觉系统反而出现位置跳变,或者 FLIR 图像花屏。原因是 UDP 没有拥塞控制,发送端按固定频率发,接收端处理不过来就丢包;图像帧如果大于 MTU,还会被 IP 分片,丢一片就整帧报废。RPOS 的 64 字节数据很小,60 次每秒带宽不高,但接收循环如果被渲染阻塞,内核缓冲区满了就会丢。FLIR 的带宽高得多,10 帧每秒和 30 帧每秒对接收缓冲区的要求完全不是一个量级。
解决方法是按需订阅、降频、加大接收缓冲区。位置和姿态 20 到 30 次每秒通常够视觉系统用,没必要追 60。RREF 只订阅当前需要的 dataref,不要一次性拉几百个。图像流先确认单帧的包数和重组逻辑,再逐步升帧率;接收缓冲区用SO_RCVBUF调大,接收线程只做收包和入队,解析放到另一个线程。调试时在接收端打印每秒收包数,和发送频率对比,丢包率一眼就能看出来。
5.5 只读 dataref 与数组索引:写进去又被改回来
现象是用 DREF 设置某个 dataref 后,值短暂变化又恢复,或者设置完全没反应。原因有两种:一是这个 dataref 由物理模型决定,模拟器每帧都会覆盖它,比如马赫数、某些气动系数;二是数组索引写错了,文档里数组从 0 开始,[1]是第二个元素,写成[0]或[2]可能读到别的发动机。还有一种情况是路径拼写错误,X-Plane 不会对未知路径报错,只是静默忽略。
解决方法是先用
本文还有配套的精品资源,点击获取