ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

05-BACnet网络层与路由详解

05-BACnet网络层与路由详解 目录BACnet 网络层与路由详解跨网络寻址机制一、为什么需要网络层二、BACnet 网络地址模型三、NPDU 结构3.1 Version 字段3.2 Control 字段3.3 目的地址字段可选3.4 源地址字段可选3.5 Hop Count四、网络层报文类型五、路由器的工作机制5.1 路由表5.2 报文转发流程5.3 路由自学习六、NPDU 编解码的 C 语言实现七、路由器转发示例八、典型网络拓扑示例九、小结BACnet 网络层与路由详解跨网络寻址机制一、为什么需要网络层BACnet 支持多种链路层技术MS/TPRS-485、Ethernet、BACnet/IP、BACnet/SC 等ARCNET 已较少使用LonTalk 自 BACnet-2012 起已从标准移除。一个完整的楼宇系统中往往同时存在多种链路层网络例如楼层级控制器通过 MS/TP 总线连接主干网络使用 BACnet/IP老旧设备使用 ARCNET。要让这些异构网络上的设备互相通信就需要一个统一的寻址与路由机制这正是 BACnet 网络层的职责。网络层将网络号 MAC 地址组合成全局地址使报文可以跨越多个网络传输类似 IP 网络中的路由器作用。二、BACnet 网络地址模型BACnet 网络地址由两部分组成BACnet 地址 (网络号, MAC 地址)网络号Network Number16 位无符号整数可分配范围 1 ~ 655340 表示未配置标识一个 BACnet 网络。0xFFFF65535保留表示全局广播。MAC 地址链路层地址长度与格式取决于具体链路层MS/TP1 字节主站 0~127从站 128~254255 为广播。Ethernet6 字节 MAC 地址。ARCNET1 字节。BACnet/IP4 字节 IP 2 字节 UDP 端口。每个 BACnet 设备都属于一个网络路由器同时属于多个网络是连接它们的桥梁。三、NPDU 结构网络层协议数据单元NPDUNetwork Protocol Data Unit是 BACnet 网络层的报文格式。NPDU 由网络层头部 数据部分组成数据部分通常是 APDU 或网络层控制信息。┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐ │ Version │ Control │ DNET │ DLEN │ DADR │ SNET │ SLEN │ SADR │ Hop │ Data │ │ (1 byte)│ (1 byte)│ (2 byte)│ (1 byte)│ (变长) │ (2 byte)│ (1 byte)│ (变长) │ (1) │ ... │ └─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────┴──────┘3.1 Version 字段固定为0x01表示 BACnet 网络层协议版本。3.2 Control 字段8 位控制字段定义报文类型与可选字段位含义Bit 71 网络层控制报文0 应用层报文APDUBit 6保留Bit 51 包含 DNET/DLEN/DADRDLEN0 表示在目的网络上广播Bit 4保留Bit 31 包含 SNET/SLEN/SADRBit 21 该报文期望应答Bit 1-0网络优先级00普通01紧急10关键设备11生命安全3.3 目的地址字段可选DNET目的网络号2 字节。DLEN目的 MAC 地址长度1 字节。DADR目的 MAC 地址DLEN 字节。DLEN0 表示该网络上所有设备。3.4 源地址字段可选SNET源网络号2 字节。SLEN源 MAC 地址长度1 字节。SADR源 MAC 地址SLEN 字节。3.5 Hop Count跳数计数器每经过一个路由器减 1到 0 时丢弃防止环路。最大值 0xFF255。四、网络层报文类型当 Control 字段 Bit 7 1 时NPDU 是网络层控制报文Data 部分第一个字节是报文类型类型编号名称说明0x00Who-Is-Router-To-Network查询前往指定网络的路由器0x01I-Am-Router-To-Network声明能到达指定网络0x02I-Could-Be-Router-To-Network可作为某网络的备用路由器0x03Reject-Message-To-Network拒绝转发无法到达0x04Router-Busy-To-Network路由器拥塞0x05Router-Available-To-Network路由器恢复可用0x06Initialize-Routing-Table初始化路由表0x07Initialize-Routing-Table-Ack路由表初始化确认0x08Disconnect-Connection-To-Network断开到指定网络的连接0x09Establish-Connection-To-Network建立到指定网络的连接这些控制报文用于路由器之间的信息交换是 BACnet 路由自学习的基础。五、路由器的工作机制5.1 路由表每个 BACnet 路由器维护一张路由表记录网络号 → 端口的映射typedef struct { uint16_t network; /* 目的网络号 */ uint8_t port; /* 转发端口编号 */ uint8_t status; /* 端口状态 */ uint16_t next_hop; /* 下一跳路由器如果是间接路由 */ } bacnet_routing_entry_t; ​ typedef struct { bacnet_routing_entry_t entries[64]; uint8_t count; } bacnet_routing_table_t;5.2 报文转发流程当路由器收到一个 NPDU 时按以下流程处理解析 NPDU 头部获取 DNET目的网络号。若 DNET 本路由器所在网络号之一 → 直接在本网络发送。若 DNET 在路由表中 → 通过对应端口转发Hop Count 减 1。若 DNET 不在路由表中 → 发起 Who-Is-Router-To-Network 查询。若 Hop Count 0 → 丢弃报文。5.3 路由自学习BACnet 路由器通过以下机制自动学习网络拓扑被动学习路由器从收到的报文 SNET 字段中学习哪个网络在哪个端口。主动查询路由器启动时发送 Who-Is-Router-To-Network其他路由器回复 I-Am-Router-To-Network从而建立完整路由表。六、NPDU 编解码的 C 语言实现#include stdint.h #include stdbool.h #include string.h ​ #define BACNET_VERSION 0x01 #define NPDU_FLAG_CONTROL 0x80 /* 网络层控制报文 */ #define NPDU_FLAG_HAS_DST 0x20 /* 包含目的地址 */ #define NPDU_FLAG_HAS_SRC 0x08 /* 包含源地址 */ #define NPDU_FLAG_REPLY_EXPECTED 0x04 /* 期望应答 */ #define NPDU_PRIORITY_MASK 0x03 /* 网络优先级0普通 1紧急 2关键 3生命安全 */ ​ typedef struct { uint16_t dnet; uint8_t dlen; uint8_t dadr[6]; uint16_t snet; uint8_t slen; uint8_t sadr[6]; uint8_t hop_count; bool is_control; /* 是否为网络层控制报文 */ bool has_dst; bool has_src; bool reply_expected; } npdu_header_t; ​ /* 解析 NPDU 头部 */ int npdu_decode_header(const uint8_t *npdu, uint16_t npdu_len, npdu_header_t *hdr) { uint16_t idx 0; if (npdu_len 2) return -1; ​ /* Version */ if (npdu[idx] ! BACNET_VERSION) return -1; idx; ​ /* Control */ uint8_t ctrl npdu[idx]; hdr-is_control (ctrl NPDU_FLAG_CONTROL) ! 0; hdr-has_dst (ctrl NPDU_FLAG_HAS_DST) ! 0; hdr-has_src (ctrl NPDU_FLAG_HAS_SRC) ! 0; hdr-reply_expected (ctrl NPDU_FLAG_REPLY_EXPECTED) ! 0; ​ /* 目的地址 */ if (hdr-has_dst) { hdr-dnet (npdu[idx] 8) | npdu[idx 1]; idx 2; hdr-dlen npdu[idx]; if (hdr-dlen sizeof(hdr-dadr)) return -1; memcpy(hdr-dadr, npdu[idx], hdr-dlen); idx hdr-dlen; } ​ /* 源地址 */ if (hdr-has_src) { hdr-snet (npdu[idx] 8) | npdu[idx 1]; idx 2; hdr-slen npdu[idx]; if (hdr-slen sizeof(hdr-sadr)) return -1; memcpy(hdr-sadr, npdu[idx], hdr-slen); idx hdr-slen; } ​ /* Hop Count仅当包含目的地址时存在 */ if (hdr-has_dst) { hdr-hop_count npdu[idx]; } ​ return (int)idx; /* 返回头部长度 */ } ​ /* 构造 NPDU 头部 */ uint16_t npdu_encode_header(uint8_t *npdu, const npdu_header_t *hdr) { uint16_t idx 0; ​ npdu[idx] BACNET_VERSION; ​ uint8_t ctrl 0; if (hdr-is_control) ctrl | NPDU_FLAG_CONTROL; if (hdr-has_dst) ctrl | NPDU_FLAG_HAS_DST; if (hdr-has_src) ctrl | NPDU_FLAG_HAS_SRC; if (hdr-reply_expected) ctrl | NPDU_FLAG_REPLY_EXPECTED; npdu[idx] ctrl; ​ if (hdr-has_dst) { npdu[idx] (uint8_t)(hdr-dnet 8); npdu[idx] (uint8_t)(hdr-dnet 0xFF); npdu[idx] hdr-dlen; memcpy(npdu[idx], hdr-dadr, hdr-dlen); idx hdr-dlen; } ​ if (hdr-has_src) { npdu[idx] (uint8_t)(hdr-snet 8); npdu[idx] (uint8_t)(hdr-snet 0xFF); npdu[idx] hdr-slen; memcpy(npdu[idx], hdr-sadr, hdr-slen); idx hdr-slen; } ​ /* Hop Count 在源地址之后仅当包含目的地址时存在 */ if (hdr-has_dst) { npdu[idx] hdr-hop_count; } ​ return idx; }七、路由器转发示例下面是一个简化的路由器转发函数/* 路由器端口定义 */ typedef struct { uint8_t port_id; uint16_t local_network; /* 该端口所属网络号 */ /* 链路层发送函数 */ int (*send)(uint8_t port, const uint8_t *mac, const uint8_t *data, uint16_t len); } bacnet_router_port_t; ​ /* 路由器转发 NPDU */ int router_forward_npdu(bacnet_routing_table_t *table, bacnet_router_port_t *ports, uint8_t num_ports, const npdu_header_t *hdr, const uint8_t *payload, uint16_t payload_len) { /* 查找目的网络对应的端口 */ int port_idx -1; for (int i 0; i table-count; i) { if (table-entries[i].network hdr-dnet) { port_idx table-entries[i].port; break; } } ​ if (port_idx 0) { /* 未知网络发起 Who-Is-Router-To-Network 查询 */ return -1; } ​ /* 找到对应端口 */ bacnet_router_port_t *port NULL; for (int i 0; i num_ports; i) { if (ports[i].port_id port_idx) { port ports[i]; break; } } if (port NULL) return -1; ​ /* Hop Count 减 1到 0 则丢弃 */ npdu_header_t new_hdr *hdr; if (new_hdr.hop_count 0) return -1; new_hdr.hop_count--; ​ /* * 简化处理重新编码完整 NPDU 头部并转发。 * 实际实现中若目的网络与本路由器直接相连应剥离 DNET/DLEN/DADR/Hop Count * 字段目的设备在本地网络不需要跨网信息 * 若还需经过下一跳路由器应使用下一跳路由器的 MAC 地址而非原始 DADR。 * 此处为简化示例未区分这两种情况。 */ uint8_t npdu_out[512]; uint16_t npdu_len npdu_encode_header(npdu_out, new_hdr); memcpy(npdu_out[npdu_len], payload, payload_len); npdu_len payload_len; ​ return port-send(port-port_id, hdr-dadr, npdu_out, npdu_len); }八、典型网络拓扑示例网络 1 (MS/TP) 网络 2 (BACnet/IP) 网络 3 (Ethernet) ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ 控制器 MAC1 │ │ 服务器 IP10.1 │ │ 工作站 MAC0x01 │ │ 控制器 MAC2 │ │ 控制器 IP10.2 │ │ 工作站 MAC0x02 │ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │ │ │ └──── 路由器 A ───────┴──── 路由器 B ─────────┘ (端口1MS/TP, 端口2IP) (端口1IP, 端口2Ethernet)当网络 1 上的控制器要访问网络 3 上的工作站时控制器构造 NPDUDNET3DADR工作站 MAC。报文到达路由器 A路由器 A 查表发现 DNET3 在端口 2IP 网络方向。路由器 A 转发到 IP 网络Hop Count 减 1。报文到达路由器 B路由器 B 查表发现 DNET3 在端口 2Ethernet方向。路由器 B 转发到 Ethernet工作站收到报文。九、小结BACnet 网络层通过 NPDU 实现了跨异构网络的统一寻址与路由。理解网络号、MAC 地址、Hop Count 的作用以及路由器如何通过路由表转发报文是设计大型 BACnet 系统的基础。下一篇将深入 BACnet/IP 与 MS/TP 两种最常用的链路层技术揭示它们在物理层与数据链路层上的实现细节。
返回列表