DNP3 系列(三):伪传输层——分段、重组与可靠传输

核心目标:掌握 DNP3 独有的伪传输层(Transport Function)设计------它如何在"无连接"链路上实现大块数据的可靠分段传输,以及它与链路层、应用层的衔接关系。

前置知识:Part 2(链路层 FT3 帧,用户数据上限 250 字节);了解 IEC 104 的 I 帧 N(S)/N® 机制便于对比。


3.1 为什么需要伪传输层

3.1.1 问题:链路层装不下应用层数据

链路层单帧用户数据最多 250 字节,而应用层的一个数据块可能远大于此:

  • 一次完整性轮询响应:数百个遥测点,可达数千字节
  • 一次事件轮询响应:几十个带时标的事件
  • 文件传输的数据块:按块大小协商,常为 2048 字节

如果没有分段机制,应用层的大报文根本无法通过链路层。

3.1.2 DNP3 的答案:协议栈中间插入"伪传输层"

复制代码
┌─────────────────────────────────┐
│ 应用层(Part 4-5)               │  ← 大报文(几百~几千字节)
├─────────────────────────────────┤
│ 伪传输层(本篇)                 │  ← 切成 ≤246 字节的段,加 4B 传输头
├─────────────────────────────────┤
│ 数据链路层(Part 2)             │  ← 每段装入一个 ≤250B 的 FT3 帧
├─────────────────────────────────┤
│ 物理层(串口 / TCP / UDP)       │
└─────────────────────────────────┘

3.1.3 为什么"伪"

称它"伪传输层",是因为它不是完整的传输协议(没有 TCP 那样的连接状态、拥塞控制),只做三件事:

  1. 分段:把应用报文切成链路层装得下的块
  2. 编号:给每个块编顺序号(SEQ)
  3. 重组:接收方按序号拼回完整报文

与 IEC 104 的本质差异 :IEC 104 假设底层是可靠的 TCP 字节流,用 APCI 头部的 N(S)/N® 序号做滑动窗口确认;DNP3 的伪传输层不依赖底层协议,串口、电台、TCP、UDP 都能跑------这是 80 年代设计留下的"传输无关"基因。


3.2 传输头(Transport Header)

3.2.1 传输头结构

每个链路帧的用户数据以 4 字节传输头 开头:

复制代码
┌───────┬───────┬───────┬─────────┬──────────────────┐
│ bit7  │ bit6  │ bit5  │ bit4    │ bit3 ─ bit0      │
│ FIN   │ FIR   │ CON   │ 保留=0  │ SEQ(模 16 序号) │
└───────┴───────┴───────┴─────────┴──────────────────┘
名称 含义
bit7 FIN Final,=1 表示这是报文的最后一段
bit6 FIR First,=1 表示这是报文的第一段
bit5 CON Confirm,=1 表示请求接收方确认
bit4 --- 保留,必须为 0
bit3-0 SEQ 段序号 0-15,模 16 递增回绕

3.2.2 标志位的组合语义

复制代码
单帧报文(应用数据 ≤246 字节):
  FIR=1, FIN=1, SEQ=0        → 传输头 0xC0

多帧报文(应用数据 >246 字节):
  第 1 段:FIR=1, FIN=0, SEQ=n        → 0xC0 | n
  中间段:FIR=0, FIN=0, SEQ=n+1, n+2...  → 0x00 | n
  最后 1 段:FIR=0, FIN=1, SEQ=m       → 0x80 | m

注意:单帧报文 SEQ 规范上应置 0;多帧报文 SEQ 从 0 开始逐段递增(FIR 段 SEQ 通常为 0),模 16 回绕。接收方以 SEQ 连续性作为重组依据。

3.2.3 每帧应用数据上限推导

复制代码
链路层用户数据上限   = 250 字节
减:传输头            = 4 字节
────────────────────────────
每帧应用层数据上限   = 246 字节

N 字节应用报文 → ceil(N / 246) 个链路帧
例:500 字节应用数据 → 246 + 246 + 8 = 3 帧

3.3 分段与重组状态机

3.3.1 发送端分段流程

复制代码
应用层报文(500 字节示例)
        │
        ▼ 按 246 字节切块
┌──────────┬──────────┬──────────┐
│ 段1 246B  │ 段2 246B  │ 段3 8B    │
└──────────┴──────────┴──────────┘
        │           │           │
        ▼           ▼           ▼
TH=0xC0      TH=0x10      TH=0x82
FIR=1        FIR=0        FIR=0
FIN=0        FIN=0        FIN=1
SEQ=0        SEQ=1        SEQ=2

3.3.2 接收端重组状态机

复制代码
IDLE(空闲)
  │  收到 FIR=1, FIN=1(单帧)
  ▼
直接交付应用层

IDLE(空闲)
  │  收到 FIR=1, FIN=0(多帧开始)
  ▼
RECEIVING(重组中,记录 SEQ=0)
  │  收到 SEQ 连续递增的下一段
  ▼
RECEIVING
  │  收到 FIN=1 段(SEQ 连续)
  ▼
交付完整报文 → IDLE

异常路径:
  RECEIVING 中收到 FIR=1(新报文开始)  → 丢弃旧缓冲,重新开始
  RECEIVING 中 SEQ 不连续 / 超时未收齐  → 丢弃缓冲 → IDLE

3.3.3 分段重组时序图

主站 Master 从站 Outstation 主站 Master 从站 Outstation #mermaid-svg-XhREKiWitwPGAPOV{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XhREKiWitwPGAPOV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XhREKiWitwPGAPOV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XhREKiWitwPGAPOV .error-icon{fill:#552222;}#mermaid-svg-XhREKiWitwPGAPOV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XhREKiWitwPGAPOV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XhREKiWitwPGAPOV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XhREKiWitwPGAPOV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XhREKiWitwPGAPOV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XhREKiWitwPGAPOV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XhREKiWitwPGAPOV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XhREKiWitwPGAPOV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XhREKiWitwPGAPOV .marker.cross{stroke:#333333;}#mermaid-svg-XhREKiWitwPGAPOV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XhREKiWitwPGAPOV p{margin:0;}#mermaid-svg-XhREKiWitwPGAPOV .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-XhREKiWitwPGAPOV text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-XhREKiWitwPGAPOV .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-XhREKiWitwPGAPOV .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-XhREKiWitwPGAPOV .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-XhREKiWitwPGAPOV .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-XhREKiWitwPGAPOV #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-XhREKiWitwPGAPOV .sequenceNumber{fill:white;}#mermaid-svg-XhREKiWitwPGAPOV #sequencenumber{fill:#333;}#mermaid-svg-XhREKiWitwPGAPOV #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-XhREKiWitwPGAPOV .messageText{fill:#333;stroke:none;}#mermaid-svg-XhREKiWitwPGAPOV .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-XhREKiWitwPGAPOV .labelText,#mermaid-svg-XhREKiWitwPGAPOV .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-XhREKiWitwPGAPOV .loopText,#mermaid-svg-XhREKiWitwPGAPOV .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-XhREKiWitwPGAPOV .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-XhREKiWitwPGAPOV .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-XhREKiWitwPGAPOV .noteText,#mermaid-svg-XhREKiWitwPGAPOV .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-XhREKiWitwPGAPOV .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-XhREKiWitwPGAPOV .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-XhREKiWitwPGAPOV .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-XhREKiWitwPGAPOV .actorPopupMenu{position:absolute;}#mermaid-svg-XhREKiWitwPGAPOV .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-XhREKiWitwPGAPOV .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-XhREKiWitwPGAPOV .actor-man circle,#mermaid-svg-XhREKiWitwPGAPOV line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-XhREKiWitwPGAPOV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 应用层产生 500B 响应 收齐 3 段,重组为 500B 报文 链路帧1: TH=0xC0(SEQ=0) + 246B链路帧2: TH=0x10(SEQ=1) + 246B链路帧3: TH=0x82(SEQ=2, FIN=1) + 8B应用层确认(若 CON 置位)

3.3.4 主站 → 从站方向的分段

主站→从站的大报文(如批量写配置、文件下发)同样分段,但每个链路帧走 CONFIRMED USER DATA(FC=4)并逐帧 ACK------可靠性由链路层保证,主站方向通常不使用传输层 CON 位。

复制代码
主站分段发送(链路层逐帧确认):
  M → O: CONFIRMED USER DATA 帧1 (TH=0xC0)   O → M: ACK
  M → O: CONFIRMED USER DATA 帧2 (TH=0x10)   O → M: ACK
  M → O: CONFIRMED USER DATA 帧3 (TH=0x82)   O → M: ACK

3.4 传输层确认机制

3.4.1 CON 位与确认

传输头 CON 位在从站 → 主站方向有实际意义:从站请求主站对该报文做应用层确认。

复制代码
从站 → 主站 的报文(尤其 Unsolicited 上报):
  TH 的 CON=1  → 主站收齐并重组后,回一个应用层 CONFIRM(功能码 0)
  TH 的 CON=0  → 主站无需确认(如轮询响应)

主站确认的作用:
  - 让从站知道"上送的 Unsolicited 事件已安全到达"
  - 从站未收到确认 → 超时后重发 Unsolicited

3.4.2 两个方向的可靠性分工

方向 可靠性机制 说明
主站 → 从站 链路层 CONFIRMED USER DATA + ACK 每个链路帧单独确认,FCB 去重
从站 → 主站 传输层 CON + 应用层 CONFIRM 整个报文确认一次(Unsolicited 场景)

这种"方向不对称"的设计与 DNP3 的主从模型一致:主站可信、可控,从站需要确认来确保事件不丢。

3.4.3 超时与重发

复制代码
从站发送 CON=1 的多帧 Unsolicited 后:
  - 等待主站 CONFIRM,超时(工程上常配 1-5s)
  - 未收到 → 重发整个报文(SEQ 重新从 0 开始)
  - 重发 N 次后放弃,事件留在缓冲中等下次轮询

3.5 伪传输层 vs IEC 104 滑动窗口对比

维度 DNP3 伪传输层 IEC 104 APCI(I 帧)
所处位置 链路层与应用层之间,独立一层 应用层 APDU 头部
序号 SEQ,模 16(0-15) N(S)/N®,模 128(0-127)
序号方向 仅段序号,无"期待接收"反向序号 N(S) 发送 + N® 接收双向序号
确认方式 主站侧靠链路层 ACK;从站侧靠应用层 CONFIRM S 帧统一确认
窗口 无窗口概念,逐段收发 k/w 参数限制未确认帧数
重发 整报文重发(从站侧) 按 N® 缺口重发未确认帧
底层依赖 无(串口/电台/TCP/UDP 通用) 必须 TCP
设计哲学 为不可靠低速链路自建分段 在可靠 TCP 之上做应用确认

工程启示:正因为伪传输层的"传输无关"特性,DNP3 可以无缝跑在串口、无线电台、TCP(端口 20000)、UDP 上,而协议栈上层完全无感。IEC 104 则被 TCP 绑定。两者各有取舍------DNP3 的代价是 SEQ 空间小(模 16)、需要自行处理重组超时。


小结与导航

本期完成了协议栈的第二层:

  1. 伪传输层的定位 ------ 解决链路层 250 字节限制,让大报文跨"无连接"链路可靠传输
  2. 传输头结构 ------ FIN/FIR/CON + SEQ(模 16),单帧 0xC0、多帧按 246 字节/段切分
  3. 分段重组状态机 ------ 按 FIR/FIN/SEQ 连续性组装,异常时丢弃缓冲
  4. 确认机制 ------ 主站→从站靠链路层逐帧 ACK;从站→主站靠应用层 CONFIRM(CON 位)
  5. 与 IEC 104 对比 ------ 模 16 段序号 vs 模 128 滑动窗口,传输无关 vs TCP 绑定

下期预告

Part 4:应用层框架 将进入协议栈最上层:

  • 应用层报文结构(应用控制字 + 功能码 + 对象头 + 数据体)
  • 功能码全集(读/写/操作/冻结/文件/安全...)
  • IIN 内部指示字 16 位全解
  • 请求-响应报文逐字段剖析

参考标准

  • DNP Users Group Specification Volume 3: Transport Function
    推荐工具

  • Wireshark(过滤 dnp3)------ 观察分段报文的 FIN/FIR/SEQ 标注

  • opendnp3 ------ 参考实现中的 TransportRx / TransportTx 状态机代码

相关推荐
筝筝ba1 小时前
linux 查询当前登录的用户数量
linux·服务器·网络
ShiXZ2132 小时前
网络调试四剑客:ping / telnet / nc / netstat 速查指令集
运维·开发语言·网络·php
snow@li2 小时前
Telnet 全景教程:全平台安装、命令使用、故障排查与安全规范
运维·网络
随风而飘1863 小时前
罗德与施瓦茨 FSP13 中高端频谱分析仪
网络·功能测试·测试工具
旗开得胜马到成功3 小时前
AMD TPM 8.3/8.5漏洞技术剖析:服务器加密信任根崩塌后的备份密钥泄露风险与紧急加固
运维·服务器·网络·系统架构
阿pin4 小时前
HTTP 协议的演进
网络·网络协议·http
bksczm4 小时前
Linux之网络层协议(IP协议)
linux·网络·tcp/ip
Mortalbreeze4 小时前
深入理解TCP协议(一):TCP报文格式详解
linux·服务器·网络·tcp/ip
啦啦啦啦啦zzzz5 小时前
高级I/O函数(一)
linux·服务器·网络·c++·网络编程