简介:在电力自动化系统中,101和104规约是实现设备远程通信的关键协议,广泛应用于SCADA系统中。101规约基于串行通信,适用于主站与RTU之间的数据交互;而104规约基于TCP/IP,更适合现代网络环境。本文详细介绍了101和104规约的基本结构、帧格式及解析流程,并深入解析104规约的源码实现,包括TCP连接管理、ASDU与TCPU结构、序列号控制、错误检测与恢复机制等内容。通过学习规约源码,有助于提升电力系统通信的稳定性与开发调试效率。
1. 电力自动化通信协议概述
在现代智能电网系统中,通信协议扮演着数据交互与控制指令传递的核心角色。其中,IEC 60870-5-101(简称101规约)与IEC 60870-5-104(简称104规约)作为电力自动化领域的关键通信标准,广泛应用于变电站自动化、远程终端单元(RTU)与主站系统之间的信息交互。
101规约基于串行通信,适用于点对点或小规模网络环境,具备结构清晰、实现简单等特点;而104规约则基于TCP/IP协议栈,适用于广域网环境,支持更高效的远程数据通信。两者在设计上均遵循统一的应用服务数据单元(ASDU)结构,体现了良好的兼容性与可扩展性。
随着智能电网的发展,101与104规约不断演进,增强了对时间同步、安全认证、数据压缩等功能的支持,成为构建现代电力监控系统(SCADA)的重要技术基础。
2. 101规约结构与解析方法
IEC 60870-5-101(简称101规约)是电力系统中广泛采用的一种串行通信协议,主要用于变电站自动化系统中主站与子站之间的数据交换。它定义了在物理层、数据链路层以及应用层上的通信规范,适用于点对点或一点多点的串行通信方式。本章将从协议的结构组成、通信机制、解析方法到实际调试工具的应用进行深入剖析,帮助读者全面理解101规约的工作原理与工程实现。
2.1 101规约的协议层次与帧格式
101规约基于OSI模型的三层结构:物理层、数据链路层和应用层。每一层都承担着不同的通信职责,确保数据的可靠传输与正确解析。
2.1.1 物理层与数据链路层的关系
在101规约中,物理层主要负责数据的电气传输,通常使用RS-232或RS-485接口进行通信。数据链路层则定义了帧格式、差错控制、流量控制等机制,确保数据在物理介质上可靠传输。
| 层次 | 功能描述 |
|---|---|
| 物理层 | 定义电平、波特率、传输方式(异步/同步) |
| 数据链路层 | 定义帧结构、差错检测(如校验和)、帧同步 |
| 应用层 | 定义数据语义、信息对象类型、传输服务 |
数据链路层通过帧结构将上层应用数据进行封装,并添加控制信息如地址、长度、校验码等,形成完整的帧进行传输。
2.1.2 帧类型与基本结构定义
101规约定义了三种主要帧类型:
- I帧(信息帧) :用于传输应用数据,具有序列号,用于流量控制。
- S帧(监督帧) :用于确认接收,不携带数据。
- U帧(非编号帧) :用于控制连接的建立、释放等操作。
帧的基本结构如下:
| 起始字节 | 地址域 | 控制域 | 长度域 | 用户数据 | 校验和 |
例如,一个典型的I帧结构如下所示:
7E 01 03 05 00 01 02 03 04 05 B0
其中:
-
7E:起始字符,标识帧的开始; -
01:地址域,表示子站地址; -
03:控制域,表示帧类型和序列号; -
05:长度域,表示用户数据长度; -
00 01 02 03 04 05:用户数据; -
B0:校验和,用于差错检测。
代码分析 :
def calc_checksum(data):
return sum(data) % 256
上述代码用于计算帧的校验和,其中 data 是用户数据部分的字节列表。通过求和并取模256的方式,确保校验和与接收端一致。
2.2 101规约的通信模式与交互流程
101规约采用主从结构的通信方式,主站(调度中心)发起请求,子站(RTU/FTU)响应。通信流程通常包括请求、响应、确认和重传机制。
2.2.1 主从结构与请求-响应机制
主站发送请求帧(如遥测召唤)后,子站需在规定时间内返回响应帧。若主站未收到响应,则触发重传机制。
流程图如下:
graph TD
A[主站发送请求帧] --> B[子站接收并处理]
B --> C{子站是否有数据?}
C -->|是| D[子站发送响应帧]
C -->|否| E[发送确认帧]
D --> F[主站接收并校验]
F --> G{校验通过?}
G -->|是| H[通信完成]
G -->|否| I[主站重发请求]
2.2.2 数据召唤与事件上报的实现流程
数据召唤(如总召唤)是主站主动获取子站全部数据的过程。而事件上报则是子站在发生状态变化(如开关变位)时主动上报。
例如,总召唤流程如下:
- 主站发送C_IC_NA_1(总召唤命令);
- 子站接收后开始准备数据;
- 子站逐帧发送遥测、遥信等数据;
- 主站接收并确认;
- 所有数据完成后发送召唤结束帧。
2.3 101规约的解析方法与实现技巧
正确解析101规约的报文是实现通信的关键。解析流程通常包括帧同步、校验、解码和状态判断。
2.3.1 报文解码的通用流程
完整的解析流程如下:
graph LR
A[接收原始字节流] --> B[寻找起始字符7E]
B --> C[读取地址域]
C --> D[读取控制域]
D --> E[读取长度域]
E --> F[读取用户数据]
F --> G[计算校验和]
G --> H{校验是否通过?}
H -->|是| I[解码用户数据]
H -->|否| J[丢弃帧并记录错误]
2.3.2 关键字段提取与状态判断
在解码用户数据时,需根据类型标识(TypeID)提取信息对象。例如,遥测(TypeID=9)包含信息对象地址、值、时间戳等字段。
例如,遥测信息体结构如下:
| 信息对象地址 | 值 | 品质描述 | 时间戳 |
代码示例:
def parse_measurement(data):
obj_addr = data[0] * 256 + data[1]
value = (data[2] << 8) | data[3]
quality = data[4]
timestamp = (data[5] << 16) | (data[6] << 8) | data[7]
return {
'obj_addr': obj_addr,
'value': value,
'quality': quality,
'timestamp': timestamp
}
上述代码解析了一个遥测信息体,其中:
-
obj_addr:信息对象地址; -
value:测量值; -
quality:品质描述(如有效、溢出); -
timestamp:时间戳。
2.4 101规约的典型应用场景与调试工具
101规约广泛应用于变电站监控系统、配电自动化系统等领域。调试与分析101报文通常需要借助抓包工具如Wireshark。
2.4.1 工程实例分析与报文示例
以一个遥信信息召唤为例,主站发送如下请求帧:
7E 01 03 04 64 01 06 00 01 00 00 14
其中:
-
7E:起始字符; -
01:地址; -
03:控制域(I帧); -
04:长度; -
64:功能类型(遥信); -
01 06:信息对象地址; -
00 01 00 00:召唤参数; -
14:校验和。
子站响应如下:
7E 01 01 06 64 01 06 00 01 01 00 00 00 17
-
64:功能类型; -
01 06:对象地址; -
01:遥信状态(闭合); -
00 00 00:品质与时间戳。
2.4.2 常用抓包工具(如Wireshark)的应用
Wireshark支持101规约的解码插件,可通过串口或TCP/IP捕获通信流量。配置步骤如下:
- 安装Wireshark;
- 启用串口捕获(如使用com0com虚拟串口);
- 打开Wireshark,选择对应串口设备;
- 设置过滤条件(如
101); - 开始捕获并查看解析后的报文。
提示 :在Wireshark中,可使用如下显示过滤器查看101报文:
iec101
通过Wireshark可以直观地查看帧结构、控制域、信息体等内容,便于调试与问题定位。
通过本章的深入解析,我们已经全面了解了101规约的协议结构、通信流程、解析方法以及工程调试技巧。这些知识为后续深入理解101规约的报文结构与数据处理打下了坚实基础。
3. 101规约报文头、控制域、信息体解析
在电力自动化通信协议中,IEC 60870-5-101(简称101规约)作为底层串行通信协议,其报文结构清晰、功能明确,广泛应用于变电站自动化系统中。理解101规约的报文结构,尤其是报文头、控制域和信息体的组成与解析方法,是实现通信解析、数据采集与系统调试的关键基础。
3.1 报文头的组成与解析方法
101规约的报文头是整个帧结构的起始部分,用于标识帧的开始、长度、地址和协议类型等基本信息,是接收端解析报文的基础。
3.1.1 起始字符、长度字段与地址域
101规约的报文头通常由以下几个关键字段组成:
| 字段名称 | 长度(字节) | 描述 |
|---|---|---|
| 起始字符(Start Byte) | 1 | 固定为 0x68 ,标识帧的开始 |
| 长度字段(Length Field) | 1 | 表示整个帧的长度(包括控制域、地址域和信息体) |
| 控制域(Control Field) | 1 | 包含帧类型和控制信息 |
| 地址域(Address Field) | 1 | 表示目标地址,用于主站和子站之间的识别 |
示意图如下(使用 Mermaid 流程图表示):
sequenceDiagram
participant Sender
participant Receiver
Sender->>Receiver: 发送起始字符0x68
Sender->>Receiver: 发送长度字段
Sender->>Receiver: 发送控制域
Sender->>Receiver: 发送地址域
Sender->>Receiver: 发送信息体(可变长度)
Receiver->>Receiver: 解析起始字符是否为0x68
Receiver->>Receiver: 根据长度字段读取剩余数据
3.1.2 协议标识与控制字段的解析逻辑
控制字段(Control Field)是101规约中用于标识帧类型和控制信息的核心字段。它通常为1个字节,其位结构如下:
| 位7 | 位6 | 位5 | 位4 | 位3 | 位2 | 位1 | 位0 |
|---|---|---|---|---|---|---|---|
| PRM | FCB | FCV | 功能码(Function Code) |
- PRM (Primary/Secondary):主站/从站标志位,1表示主站发送,0表示从站发送。
- FCB (Frame Count Bit):帧计数位,用于确认帧的顺序。
- FCV (Frame Count Valid):帧计数有效位。
- 功能码 (Function Code):表示帧的类型,如请求、响应、确认等。
例如,功能码 0x07 表示“确认帧”,功能码 0x0B 表示“召唤帧”。
下面是一个控制字段解析的代码示例(C语言):
typedef struct {
unsigned char start_byte; // 固定为0x68
unsigned char length; // 帧长度
unsigned char control; // 控制字段
unsigned char address; // 地址域
} IEC101_Header;
void parse_header(const unsigned char *buffer, IEC101_Header *header) {
header->start_byte = buffer[0]; // 起始字符
header->length = buffer[1]; // 长度字段
header->control = buffer[2]; // 控制字段
header->address = buffer[3]; // 地址域
}
代码逻辑分析:
- 从输入缓冲区
buffer中依次读取四个字节,分别对应起始字符、长度字段、控制字段和地址域。 -
header->control的位结构可以通过位运算进一步拆解,获取 PRM、FCB、FCV 和功能码的具体值。
3.2 控制域字段的定义与功能解析
控制域字段是101规约通信控制的核心,决定了帧的类型、方向、确认机制等。
3.2.1 控制字节的位结构与功能定义
控制字节(Control Byte)的结构如下:
| 位 | 含义 |
|---|---|
| 7 | PRM(主从标识) |
| 6 | FCB(帧计数位) |
| 5 | FCV(帧计数有效) |
| 4-0 | 功能码(Function Code) |
例如,当 control = 0x73 时:
- 二进制为
0111 0011 - PRM = 0(从站发送)
- FCB = 1(当前帧计数位为1)
- FCV = 1(帧计数有效)
- 功能码 = 0x03(响应帧)
3.2.2 请求、响应、确认与重传机制的实现
101规约通过控制域中的功能码和帧计数位实现通信的请求-响应机制和确认重传机制。
- 请求帧 :功能码通常为
0x05或0x0A,表示主站发起的请求。 - 响应帧 :功能码通常为
0x0B,表示子站的响应。 - 确认帧 :功能码为
0x07,表示接收方确认已收到数据。 - 重传机制 :主站根据帧计数位(FCB)判断是否重传,若未收到确认,则切换 FCB 状态并重发。
示例:请求与响应交互流程
sequenceDiagram
participant Master
participant Slave
Master->>Slave: 发送请求帧(功能码0x05)
Slave->>Master: 发送确认帧(功能码0x07)
Slave->>Master: 发送响应帧(功能码0x0B)
Master->>Slave: 发送确认帧(功能码0x07)
3.3 信息体的结构与数据表示方式
信息体是101规约中承载实际数据的部分,包含遥测、遥信、遥控等信息对象。
3.3.1 信息对象地址与信息元素编码
每个信息体包含:
- 信息对象地址(Information Object Address) :2字节或3字节,用于标识数据点。
- 信息元素(Information Element) :包含具体数据值。
- 时间戳(Timestamp) :可选字段,表示数据采集时间。
- 品质描述(Quality Descriptor) :表示数据有效性。
例如:
| 信息对象地址 | 数据值 | 时间戳 | 品质描述 |
|---|---|---|---|
| 0x0001 | 0x1234 | 可选 | 0x80 |
3.3.2 时间戳、品质描述与数据类型解析
- 时间戳 :3字节表示毫秒 + 2字节表示分钟,例如
0x12 0x34 0x56 0x78 0x9A。 - 品质描述 :1字节,每一位表示不同状态(如无效、溢出、替代等)。
- 数据类型 :如单点遥信(SP)、双点遥信(DP)、测量值(MV)等。
解析信息体的代码示例(Python):
def parse_information_element(data, offset):
ioa = int.from_bytes(data[offset:offset+2], byteorder='little')
offset += 2
value = int.from_bytes(data[offset:offset+2], byteorder='little')
offset += 2
quality = data[offset]
offset += 1
return {
'ioa': ioa,
'value': value,
'quality': quality
}
代码说明:
-
ioa:信息对象地址,2字节小端格式。 -
value:数据值,如电压、电流值。 -
quality:品质描述字节,需进一步按位解析。
3.4 典型信息体类型与应用示例
101规约定义了多种信息体类型,常见类型包括:
| 类型标识 | 信息体类型 | 示例 |
|---|---|---|
| M_SP_NA_1 | 单点遥信 | 开关状态 |
| M_DP_NA_1 | 双点遥信 | 断路器状态 |
| M_ME_NC_1 | 测量值(浮点) | 电压、电流 |
| C_SC_NA_1 | 单点遥控 | 控制开关合闸 |
3.4.1 遥测、遥信、遥控等信息体解析
遥测信息体(M_ME_NC_1)示例:
typedef struct {
unsigned short ioa; // 信息对象地址
float value; // 测量值(如电压)
unsigned char quality; // 品质描述
} MeasuredValue;
遥信信息体(M_SP_NA_1)示例:
typedef struct {
unsigned short ioa;
unsigned char state; // 0表示断开,1表示闭合
unsigned char quality;
} SinglePoint;
遥控信息体(C_SC_NA_1)示例:
typedef struct {
unsigned short ioa;
unsigned char command; // 0表示分闸,1表示合闸
} ControlCommand;
3.4.2 实际工程报文解析与代码实现
以下是一个完整的101规约报文示例(16进制):
68 13 07 01 01 00 01 00 00 00 00 00 00 00 00 00 00 00
逐字段解析:
- 起始字符:
0x68 - 长度:
0x13(19字节) - 控制字段:
0x07(确认帧) - 地址域:
0x01 - 信息体:从第4字节开始,包含信息对象地址
0x0001,数据值为0x00,品质描述为0x00
代码实现(Python):
def parse_full_frame(data):
start = data[0]
length = data[1]
control = data[2]
address = data[3]
info_body = data[4:]
print(f"起始字符: {hex(start)}")
print(f"长度字段: {hex(length)}")
print(f"控制字段: {hex(control)}")
print(f"地址域: {hex(address)}")
print("信息体解析:")
for i in range(0, len(info_body), 4):
ioa = int.from_bytes(info_body[i:i+2], 'little')
value = int.from_bytes(info_body[i+2:i+4], 'little')
print(f"IOA: {hex(ioa)}, Value: {value}")
执行逻辑分析:
- 从完整报文中依次读取各字段。
- 使用
int.from_bytes解析信息对象地址和数据值。 - 打印解析结果,便于调试和验证。
本章从101规约的报文头、控制域、信息体结构入手,详细解析了各字段的含义、解析方法,并结合代码示例展示了实际工程中的解析实现,为后续通信协议的开发与调试提供了坚实基础。
4. 104规约结构与网络通信特性
4.1 104规约的体系结构与网络模型
4.1.1 ISO/OSI模型中的位置与协议栈构成
IEC 60870-5-104(简称104规约)是一种基于TCP/IP协议栈的远程控制通信协议,广泛应用于电力自动化系统中,用于站控层与间隔层之间的信息交换。104规约是IEC 60870-5系列协议中的一部分,其设计基于ISO/OSI七层模型。
从协议栈的角度来看,104规约主要位于OSI模型的 应用层(Application Layer) ,其底层依赖于 传输层的TCP协议 、 网络层的IP协议 以及 链路层和物理层 的以太网或串行通信接口。
| OSI 层次 | 104规约相关协议 |
|---|---|
| 应用层(Layer 7) | IEC 60870-5-104(APCI + ASDU) |
| 传输层(Layer 4) | TCP |
| 网络层(Layer 3) | IP |
| 链路层(Layer 2) | Ethernet、PPP、HDLC等 |
| 物理层(Layer 1) | 双绞线、光纤、串口等 |
在该协议栈中,APCI(Application Protocol Control Information)负责建立、维护和终止通信会话,而ASDU(Application Service Data Unit)则承载具体的遥测、遥信、遥控等应用数据。
4.1.2 104规约在TCP/IP环境中的实现
104规约的实现依赖于TCP/IP协议栈,其通信过程主要通过以下几个步骤:
- 建立TCP连接 :主站(SCADA系统)作为客户端主动发起TCP连接,子站(RTU/IED)作为服务器监听特定端口(默认端口为2404)。
- 启动APCI握手 :连接建立后,主站发送
STARTDT(Start Data Transfer)指令,子站确认后发送STARTOK,正式进入数据交换阶段。 - 数据传输 :通过APCI帧封装ASDU数据,进行遥测、遥信、遥控等信息的传输。
- 会话终止 :通信结束后,主站发送
STOPDT指令,子站确认后断开连接。
以下是TCP连接建立和APCI握手的简化流程图:
sequenceDiagram
participant Master as 主站
participant Slave as 子站
Master->>Slave: TCP连接请求(SYN)
Slave-->>Master: TCP连接确认(SYN-ACK)
Master->>Slave: TCP连接确认(ACK)
Master->>Slave: STARTDT(APCI)
Slave-->>Master: STARTOK(APCI)
代码示例:TCP连接与APCI握手的Python模拟
以下是一个简单的Python代码片段,模拟主站发起TCP连接并发送 STARTDT 指令的过程:
import socket
# 定义子站IP和端口
ip = "192.168.1.10"
port = 2404
# 创建TCP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接子站
sock.connect((ip, port))
print("已建立TCP连接")
# 构造STARTDT指令(APCI帧)
# APCI帧格式:0x68(起始符)+ 0x04(长度)+ 0x07(STARTDT)+ 0x00(保留)+ 0x00(保留)+ 0x00(保留)
apci_startdt = bytes([0x68, 0x04, 0x07, 0x00, 0x00, 0x00])
# 发送STARTDT指令
sock.send(apci_startdt)
print("已发送STARTDT指令")
# 接收子站的STARTOK响应
response = sock.recv(1024)
print("收到响应:", response.hex())
# 关闭连接
sock.close()
代码逻辑分析:
- socket.socket() :创建TCP客户端Socket对象。
- sock.connect() :主动连接到子站IP和端口2404。
- apci_startdt :构造APCI帧,起始符为0x68,长度为0x04,控制域为0x07(表示STARTDT)。
- sock.send() :发送APCI帧到子站。
- sock.recv() :接收子站返回的STARTOK响应。
- sock.close() :关闭TCP连接。
此代码模拟了104规约中TCP连接建立和APCI握手的基本流程,适用于理解协议在网络层和应用层的交互方式。
4.2 104规约的通信机制与会话管理
4.2.1 基于TCP的连接建立与维护
104规约基于TCP协议进行通信,因此其连接建立与维护机制完全依赖于TCP的可靠传输特性。TCP提供了面向连接的、有序的、可靠的字节流传输服务,这使得104规约在数据传输过程中具备了良好的稳定性和错误恢复能力。
在104规约中,主站通常作为TCP客户端主动发起连接,而子站作为服务器端监听2404端口。连接建立后,主站通过发送APCI帧控制会话状态,如 STARTDT 、 STOPDT 等,来启动或终止数据传输。
为了保证连接的持续性,104规约还引入了 心跳机制 (Test Command)和 超时重传机制 ,以应对网络不稳定或设备异常断开的情况。
4.2.2 会话启动、数据交换与会话终止
104规约的通信过程可分为三个阶段:
- 会话启动阶段 :主站发送
STARTDT指令,子站响应STARTOK,表示数据传输准备就绪。 - 数据交换阶段 :主站与子站之间通过APCI帧封装ASDU进行数据交互,包括遥测、遥信、遥控等信息。
- 会话终止阶段 :主站发送
STOPDT指令,子站响应STOPDTCONF,随后关闭TCP连接。
以下是这三个阶段的典型交互流程:
sequenceDiagram
participant Master
participant Slave
Master->>Slave: TCP连接建立
Master->>Slave: STARTDT
Slave-->>Master: STARTOK
Master->>Slave: I-帧(ASDU数据)
Slave-->>Master: S-帧(确认)
Master->>Slave: STOPDT
Slave-->>Master: STOPDTCONF
Master->>Slave: TCP连接关闭
会话状态机示意
104规约定义了多个会话状态,包括:
- 未连接(Not Connected)
- 等待连接(Wait Connect)
- 等待STARTDT(Wait STARTDT)
- 运行中(Running)
- 等待STOPDT确认(Wait STOPDT Confirm)
状态之间的转换依赖于APCI帧的类型和通信状态。
4.3 104规约的数据传输模式与可靠性保障
4.3.1 无确认与有确认传输机制
104规约支持两种主要的数据传输机制:
- 无确认传输(Unconfirmed Data Transfer) :适用于周期性上送的遥测、遥信数据,如总召唤(General Interrogation)响应。主站不期望收到确认帧。
- 有确认传输(Confirmed Data Transfer) :适用于遥控、设点等关键操作,子站必须返回确认帧(S-帧)。
在无确认传输中,主站发送I-帧后不等待确认;而在有确认传输中,主站发送I-帧后需等待子站的S-帧确认,否则将触发超时重传。
4.3.2 序列号管理与数据顺序控制
104规约使用 发送序列号(Send Sequence Number, SN) 和 接收序列号(Receive Sequence Number, RN) 来管理数据帧的顺序和确认机制。每个I-帧都携带SN,接收方在S-帧中携带RN,用于确认已收到的数据帧。
例如:
| 帧类型 | SN | RN | 说明 |
|---|---|---|---|
| I-帧 | 1 | 0 | 主站发送第一个数据帧 |
| S-帧 | 0 | 1 | 子站确认收到SN=1的数据帧 |
| I-帧 | 2 | 1 | 主站发送第二个数据帧 |
| S-帧 | 0 | 2 | 子站确认收到SN=2的数据帧 |
这种方式可以有效防止数据丢失和重复,确保数据传输的顺序性和完整性。
4.4 104规约的典型网络部署与配置实践
4.4.1 站控层与间隔层通信的部署方式
在典型的智能变电站中,104规约常用于 站控层(Station Control Level) 与 间隔层(Bay Level) 之间的通信。具体部署方式如下:
- 站控层设备 (如SCADA系统、监控主机)作为主站,负责发起通信并接收数据。
- 间隔层设备 (如保护测控装置、RTU)作为子站,监听TCP端口并响应主站请求。
- 网络结构 通常采用以太网双网冗余结构,确保通信可靠性。
部署示意图如下:
graph TD
A[SCADA主站] -- TCP连接 --> B(子站1)
A -- TCP连接 --> C(子站2)
A -- TCP连接 --> D(子站3)
B -- GOOSE/MMS --> E(智能终端)
C -- GOOSE/MMS --> F(保护装置)
D -- GOOSE/MMS --> G(测控单元)
4.4.2 工程组网与协议配置实例
在实际工程中,配置104规约主要包括以下几个方面:
- IP地址配置 :为主站和子站分配固定IP地址。
- 端口设置 :子站监听2404端口。
- 心跳周期设置 :主站定期发送
TESTFR指令检测连接状态。 - 超时与重传配置 :设置重传次数和超时时间,防止通信中断。
- ASDU地址映射 :定义遥测、遥信、遥控等数据的ASDU地址。
示例配置(基于IEC 60870-5-104配置文件):
[Master]
IP=192.168.1.1
Port=2404
Heartbeat=60s
Timeout=10s
Retries=3
[Slave]
IP=192.168.1.10
Port=2404
Listen=yes
[DataMapping]
ASDU_1=1-100 ; 遥测数据地址范围
ASDU_3=101-200 ; 遥信数据地址范围
ASDU_45=201-250 ; 遥控命令地址范围
说明:
- Heartbeat=60s :每60秒发送一次测试帧,保持连接活跃。
- Timeout=10s :每次发送请求后等待10秒响应,否则超时。
- Retries=3 :失败重试3次后断开连接。
- ASDU地址映射 :定义不同类型的ASDU数据对应的实际设备地址。
这种配置方式在实际项目中广泛使用,适用于变电站、调度中心等场景。
5. TCP连接建立与释放流程(三次握手/四次挥手)
TCP(Transmission Control Protocol)作为传输层的核心协议之一,以其面向连接、可靠传输和流量控制的特性,广泛应用于工业自动化通信中,尤其是在IEC 60870-5-104规约中,TCP连接的建立与释放是通信过程中的关键环节。本章将深入剖析TCP连接建立的三次握手流程与连接释放的四次挥手机制,结合状态机模型与异常处理机制,进一步探讨在104规约中的连接管理实践与工程调试经验。
5.1 TCP连接建立过程详解
TCP连接的建立是通过三次握手(Three-Way Handshake)来完成的。这个过程确保了通信双方都具备发送和接收能力,并为后续的数据传输建立同步状态。
5.1.1 三次握手的交互流程
三次握手过程如下图所示:
sequenceDiagram
participant Client
participant Server
Client->>Server: SYN(seq=x)
Server->>Client: SYN-ACK(seq=y, ack=x+1)
Client->>Server: ACK(ack=y+1)
步骤详解:
-
客户端发送SYN段(同步标志位) :
- 客户端选择一个初始序列号seq=x,并将SYN标志位置1。
- 此时客户端进入SYN_SENT状态。
- 报文段不携带数据,但消耗一个序列号。 -
服务器响应SYN-ACK段 :
- 服务器收到SYN后,回复SYN和ACK标志位为1的段。
- 服务器选择自己的初始序列号seq=y,并将确认号设为ack=x+1。
- 服务器进入SYN_RCVD状态。 -
客户端发送ACK段(确认标志位) :
- 客户端收到SYN-ACK后,发送ACK段,确认号为ack=y+1。
- 此时连接建立完成,双方进入ESTABLISHED状态。
示例代码(使用Python的socket库模拟三次握手):
import socket
# 服务端代码
def server():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', 8888))
s.listen(1)
print("Server is listening...")
conn, addr = s.accept()
print(f"Connection from {addr}")
conn.sendall(b"Hello Client")
conn.close()
# 客户端代码
def client():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 8888))
print("Connected to server")
data = s.recv(1024)
print("Received:", data.decode())
s.close()
# 启动服务端和客户端
if __name__ == "__main__":
import threading
server_thread = threading.Thread(target=server)
server_thread.start()
client()
代码解析:
-
socket.socket(socket.AF_INET, socket.SOCK_STREAM):创建TCP套接字。 -
s.listen(1):监听连接请求,队列长度为1。 -
s.connect():发起连接请求,触发三次握手。 -
s.recv():接收数据,表示连接已建立。
5.1.2 握手失败与重传机制
TCP握手过程可能因网络丢包、服务器过载等原因失败。系统通常会设置超时机制并进行重传。
常见失败原因及处理机制:
| 故障类型 | 描述 | 处理机制 |
|---|---|---|
| SYN丢包 | 客户端发送SYN未被服务器接收 | 客户端超时后重传SYN |
| SYN-ACK丢包 | 服务器回复SYN-ACK未被客户端接收 | 客户端未收到回复,超时重传SYN |
| ACK丢包 | 客户端发送ACK未被服务器接收 | 服务器未收到ACK,超时重传SYN-ACK |
TCP协议中,重传次数由系统内核参数控制,例如Linux中可通过 /proc/sys/net/ipv4/tcp_syn_retries 设置SYN重试次数。
5.2 TCP连接释放过程详解
TCP连接的释放是通过四次挥手(Four-Way Handshake)来完成的。这个过程确保了双方都能确认数据传输的结束,并释放资源。
5.2.1 四次挥手的交互流程
sequenceDiagram
participant Client
participant Server
Client->>Server: FIN
Server->>Client: ACK
Server->>Client: FIN
Client->>Server: ACK
步骤详解:
-
客户端发送FIN段 :
- 客户端发送FIN标志位为1的段,表示不再发送数据。
- 客户端进入FIN-WAIT-1状态。 -
服务器回应ACK段 :
- 服务器收到FIN后,发送ACK段进行确认。
- 服务器进入CLOSE-WAIT状态;客户端进入FIN-WAIT-2状态。 -
服务器发送FIN段 :
- 服务器完成数据发送后,发送FIN段。
- 服务器进入LAST-ACK状态。 -
客户端发送ACK段 :
- 客户端收到FIN后,发送ACK段进行确认。
- 客户端进入TIME-WAIT状态,等待2MSL(Maximum Segment Lifetime)后关闭连接。
示例代码(模拟四次挥手):
def client_shutdown():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 8888))
print("Connected to server")
s.sendall(b"Close connection")
s.shutdown(socket.SHUT_WR) # 发送FIN
data = s.recv(1024)
print("Received:", data.decode())
s.close()
代码解析:
-
s.shutdown(socket.SHUT_WR):关闭写通道,触发FIN段发送。 -
s.recv():等待服务器响应数据或FIN。 -
s.close():关闭连接,释放资源。
5.2.2 半关闭与连接复用机制
TCP支持“半关闭”机制,即一方关闭写通道,但仍可接收数据。这对于104规约中“单向数据上传”场景非常有用。
半关闭流程图:
graph LR
A[Client] --> B[Server]
A -- FIN --> B
B -- ACK --> A
B -- 数据传输 --> A
B -- FIN --> A
A -- ACK --> B
连接复用机制:
- TCP连接建立后可重复用于多次数据传输,减少握手开销。
- 在104规约中,连接复用是提高通信效率的重要手段。
5.3 TCP状态转换与异常处理
TCP协议定义了多个状态,用于表示连接生命周期中的不同阶段。理解状态转换有助于调试连接问题。
5.3.1 各种TCP状态的含义与转换路径
| 状态 | 含义 |
|---|---|
| LISTEN | 监听来自客户端的连接请求 |
| SYN_SENT | 客户端发送SYN后等待服务器确认 |
| SYN_RCVD | 服务器收到SYN后发送SYN-ACK等待客户端确认 |
| ESTABLISHED | 连接已建立,可进行数据传输 |
| FIN-WAIT-1 | 客户端发送FIN后等待服务器ACK |
| FIN-WAIT-2 | 客户端收到ACK后等待服务器FIN |
| CLOSE-WAIT | 服务器收到FIN后等待本地关闭 |
| LAST-ACK | 服务器发送FIN后等待客户端ACK |
| CLOSING | 双方同时发送FIN |
| TIME-WAIT | 客户端发送ACK后等待2MSL释放连接 |
| CLOSED | 连接已关闭 |
状态转换图(部分):
stateDiagram
[*] --> LISTEN
LISTEN --> SYN_RCVD: 收到SYN
LISTEN --> SYN_SENT: 主动连接
SYN_SENT --> SYN_RCVD: 收到SYN
SYN_SENT --> ESTABLISHED: 收到SYN-ACK
SYN_RCVD --> ESTABLISHED: 收到ACK
ESTABLISHED --> FIN_WAIT_1: 发送FIN
FIN_WAIT_1 --> FIN_WAIT_2: 收到ACK
FIN_WAIT_2 --> TIME_WAIT: 收到FIN
TIME_WAIT --> CLOSED: 2MSL超时
5.3.2 异常断连与恢复机制
常见异常场景:
| 异常类型 | 表现 | 恢复机制 |
|---|---|---|
| 网络中断 | 连接无响应 | 心跳机制检测断连,重新连接 |
| 应用崩溃 | 未发送FIN直接断开 | 客户端检测到连接断开,自动重连 |
| 超时未响应 | ACK/FIN未收到 | 设置超时重传机制 |
| 端口占用 | 连接建立失败 | 释放端口或更换端口号 |
恢复机制实现建议:
- 使用心跳包(Heartbeat)定期检测连接状态。
- 设置连接超时时间(如30秒),超时后主动重连。
- 记录连接状态日志,便于调试与分析。
5.4 在104规约中的TCP连接管理实践
IEC 60870-5-104规约基于TCP/IP协议栈,因此其连接管理必须遵循TCP规范,同时结合规约自身特点进行优化。
5.4.1 连接超时与心跳机制的配置
心跳机制配置示例(C语言):
// 心跳包发送函数
void send_heartbeat(int sockfd) {
char heartbeat[] = {0x68, 0x04, 0x07, 0x00, 0x00, 0x00}; // U帧心跳包
send(sockfd, heartbeat, sizeof(heartbeat), 0);
}
// 定时器触发心跳发送
void *heartbeat_thread(void *arg) {
int sockfd = *(int *)arg;
while (1) {
send_heartbeat(sockfd);
sleep(30); // 每30秒发送一次心跳
}
}
参数说明:
-
heartbeat[]:U帧格式的心跳报文,符合104规约U帧结构。 -
send():发送心跳包。 -
sleep(30):每30秒发送一次,防止连接空闲超时。
5.4.2 工程案例分析与调试经验分享
案例1:连接频繁断开
现象 :连接建立后不久即断开,无明显数据传输。
分析 :
- 检查心跳包是否正常发送。
- 检查TCP Keepalive设置是否启用。
- 查看防火墙是否阻断连接。
解决方法 :
- 启用系统级Keepalive: net.ipv4.tcp_keepalive_time=300 。
- 调整心跳间隔,缩短为15秒。
- 检查中间设备(如交换机、防火墙)配置。
案例2:连接建立失败
现象 :客户端无法连接到服务器,返回连接超时。
分析 :
- 检查IP地址与端口号是否正确。
- 检查服务器是否启动并监听端口。
- 使用Wireshark抓包分析SYN是否到达服务器。
解决方法 :
- 使用 netstat -antp | grep 8888 检查端口监听状态。
- 使用 tcpdump 抓包确认SYN是否到达。
- 检查服务器防火墙规则,是否允许入站连接。
通过本章的深入分析,我们系统性地掌握了TCP连接的建立与释放机制,理解了状态转换逻辑与异常处理策略,并结合104规约的实际应用,提供了连接管理的工程实践与调试经验。这些内容对于深入理解工业自动化通信协议、优化网络连接性能具有重要意义。
6. ASDU与TCPU的构建与拆解
6.1 ASDU数据单元的结构与组成
6.1.1 应用服务数据单元的基本格式
在IEC 60870-5-101/104规约中,ASDU(Application Service Data Unit,应用服务数据单元)是应用层的核心数据结构,用于承载遥测、遥信、遥控等信息。ASDU的基本格式如下:
| 类型标识(TYP) | 可变结构限定词(VSQ) | 传送原因(COT) | 公共地址(ADDR) | 信息对象地址(OA) | 信息元素(INF) |
- 类型标识(TYP) :定义信息体的类型和结构,如单点遥信、双点遥信、浮点遥测等。
- 可变结构限定词(VSQ) :用于指示信息对象的数量及地址是否连续。
- 传送原因(Cause of Transmission, COT) :指示报文发送的原因,如周期上送、变位上送、总召等。
- 公共地址(ADDR) :标识设备或站的地址,用于多站通信时的区分。
- 信息对象地址(OA) :信息对象的起始地址。
- 信息元素(INF) :具体的数据内容,如遥测值、状态位等。
6.1.2 类型标识、可变结构限定词与信息体的组成
以类型标识 M_ME_NA_1 (浮点遥测,无品质描述)为例,其典型结构如下:
| 字段 | 长度(字节) | 说明 |
|---|---|---|
| TYP | 1 | 类型标识为 0x0D |
| VSQ | 1 | 例如:0x01 表示一个信息对象,地址连续 |
| COT | 1 | 例如:0x03 表示总召唤 |
| ADDR | 2 | 站地址,例如 0x00 0x01 |
| OA | 3 | 信息对象地址,例如 0x00 0x00 0x01 |
| INF | 4 | 浮点数数据,如 0x42 0x48 0x00 0x00 表示 50.0 |
6.2 ASDU的构建与编码方法
6.2.1 信息对象的组装与序列化
ASDU的构建流程如下:
- 确定类型标识 :根据数据类型选择合适的TYP值。
- 构造VSQ字段 :设定信息对象数量与地址连续性。
- 设置COT与ADDR :根据通信上下文设定传送原因和站地址。
- 生成信息对象地址 :根据数据点的地址生成OA字段。
- 填充信息元素 :将遥测值、状态等数据按规约格式进行编码。
以下是一个构建ASDU的Python伪代码示例:
def build_asdu(type_id, vsq, cot, station_addr, obj_addr, data):
asdu = bytearray()
asdu.append(type_id) # 类型标识
asdu.append(vsq) # 可变结构限定词
asdu.append(cot) # 传送原因
asdu.extend(station_addr.to_bytes(2, 'little')) # 站地址
asdu.extend(obj_addr.to_bytes(3, 'little')) # 信息对象地址
asdu.extend(data) # 数据内容
return asdu
6.2.2 时间戳与品质字段的填充方法
某些ASDU类型(如带时标的信息体)需要添加时间戳和品质字段。例如, M_SP_TB_1 (带时间标签的单点信息)包含:
| 类型标识 | VSQ | COT | 地址 | 时间戳(7字节) | 品质描述(1字节) |
时间戳格式为 CP56Time2a ,表示从1970年1月1日00:00:00 UTC到当前时间的毫秒数,采用6字节+1字节的结构。
def add_timestamp(asdu, timestamp_ms):
# timestamp_ms: 毫秒时间戳
ts_bytes = timestamp_ms.to_bytes(6, 'little') + b'\x00'
asdu.extend(ts_bytes)
品质字段用于描述数据的可信度,例如:
| 位 | 含义 |
|---|---|
| 0 | 有效位(0=有效,1=无效) |
| 1 | 替代值位 |
| 2 | 测试位 |
| 3 | 被封锁位 |
def add_quality(asdu, quality):
asdu.append(quality) # 品质字节
6.3 TCPU协议数据单元的封装与传输
6.3.1 TCP/UDP与IP协议头的构建
在IEC 60870-5-104中,APDU(应用协议数据单元)通过TCP进行传输。完整的TCPU结构包括:
| TCP头 | IP头 | 以太网头 | APDU数据 |
- TCP头 :包含源端口、目标端口、序列号、确认号等。
- IP头 :包含源IP、目标IP、协议类型等。
- 以太网头 :包含源MAC、目标MAC、类型字段(如0x0800表示IP)。
使用Python构建一个基本的TCP/IP头部示例如下:
import socket
import struct
def build_ip_header(src_ip, dst_ip):
ip_ihl = 5
ip_ver = 4
ip_tos = 0
ip_tot_len = 0 # kernel will fill the correct value
ip_id = socket.htons(54321)
ip_frag_off = 0
ip_ttl = 255
ip_proto = socket.IPPROTO_TCP
ip_check = 0
ip_saddr = socket.inet_aton(src_ip)
ip_daddr = socket.inet_aton(dst_ip)
ip_ihl_ver = (ip_ver << 4) + ip_ihl
return struct.pack('!BBHHHBBH4s4s', ip_ihl_ver, ip_tos, ip_tot_len, ip_id,
ip_frag_off, ip_ttl, ip_proto, ip_check, ip_saddr, ip_daddr)
6.3.2 APDU在TCP中的封装方式
APDU在TCP中的封装过程如下:
- 构建ASDU数据。
- 添加应用协议控制信息(APCI)形成APDU。
- 将APDU作为TCP负载进行封装。
- 通过socket发送TCP/IP数据包。
APCI包括:
- 启动字符 :0x68(固定长度为4字节的开始标志)
- 长度字段 :后续字节数
- 控制字段 :I、S、U帧的控制信息
def build_apdu(asdu_data):
apci = b'\x68' + bytes([len(asdu_data)]) + b'\x00\x00' # 简化处理
apdu = apci + asdu_data
return apdu
6.4 报文的解码与完整性校验
6.4.1 报文分片重组与校验流程
在TCP传输中,APDU可能被分片传输。接收端需:
- 接收TCP数据流。
- 提取APDU长度字段。
- 持续接收直到完整APDU接收完成。
- 提取ASDU部分并进行解析。
分片重组流程如下:
graph TD
A[开始接收TCP数据] --> B{是否收到完整APDU?}
B -->|是| C[提取APDU并解析]
B -->|否| D[缓存已有数据]
D --> E[继续接收下一段]
E --> B
6.4.2 CRC校验与错误恢复机制的实现
虽然IEC 104使用TCP进行传输,但在某些底层链路(如101)中仍需CRC校验。CRC16-CCITT算法如下:
def crc16_ccitt(data):
crc = 0xFFFF
for byte in data:
crc ^= (byte << 8)
for _ in range(8):
if crc & 0x8000:
crc = (crc << 1) ^ 0x1021
else:
crc <<= 1
return crc & 0xFFFF
接收端在接收到数据后,应重新计算CRC并与报文中的CRC值进行比对。若不一致,则丢弃该帧并请求重传。
下一章节将深入讨论IEC 104规约中的应用服务数据单元(ASDU)在不同控制类型下的交互逻辑与实现方式。
简介:在电力自动化系统中,101和104规约是实现设备远程通信的关键协议,广泛应用于SCADA系统中。101规约基于串行通信,适用于主站与RTU之间的数据交互;而104规约基于TCP/IP,更适合现代网络环境。本文详细介绍了101和104规约的基本结构、帧格式及解析流程,并深入解析104规约的源码实现,包括TCP连接管理、ASDU与TCPU结构、序列号控制、错误检测与恢复机制等内容。通过学习规约源码,有助于提升电力系统通信的稳定性与开发调试效率。
101与104规约解析及源码实现
2万+

被折叠的 条评论
为什么被折叠?



