基于 C 语言的 IEC 104 协议服务端和客户端实现框架。IEC 104 是电力自动化系统中常用的通信协议,基于 TCP/IP 传输。
1. 协议定义与结构
c
// iec104_def.h
#ifndef IEC104_DEF_H
#define IEC104_DEF_H
#include <stdint.h>
#include <stdbool.h>
// IEC 104 帧结构定义
#define IEC104_START_CHAR 0x68
#define IEC104_MIN_APDU_LEN 4
#define IEC104_MAX_ASDU_LEN 253
#define IEC104_MAX_APDU_LEN (IEC104_MAX_ASDU_LEN + 6) // 6=启动(1)+长度(1)+控制域(4)
// 控制域类型
typedef enum {
IEC104_CTRL_I = 0, // 信息传输格式(I格式)
IEC104_CTRL_S = 1, // 监视功能(S格式)
IEC104_CTRL_U = 3 // 控制功能(U格式)
} IEC104_CtrlType;
// U格式控制功能
typedef enum {
U_STARTDT_ACT = 0x07, // 启动数据传输
U_STARTDT_CON = 0x0B, // 启动数据传输确认
U_STOPDT_ACT = 0x13, // 停止数据传输
U_STOPDT_CON = 0x23, // 停止数据传输确认
U_TESTFR_ACT = 0x43, // 测试帧
U_TESTFR_CON = 0x83 // 测试帧确认
} IEC104_U_Function;
// 传输原因
typedef enum {
PERIODIC = 1, // 周期/循环
BACKGROUND_SCAN = 2, // 背景扫描
SPONTANEOUS = 3, // 突发
INITIALIZED = 4, // 初始化
REQUEST = 5, // 请求或被请求
ACTIVATION = 6, // 激活
ACTIVATION_CON = 7, // 激活确认
DEACTIVATION = 8, // 停止激活
DEACTIVATION_CON = 9, // 停止激活确认
ACTIVATION_TERM = 10, // 激活终止
INTERROGATED = 20, // 站召唤
INTERROGATED_TERM = 21 // 站召唤终止
} IEC104_CauseOfTransmission;
// 信息体类型标识(常见类型)
typedef enum {
M_SP_NA_1 = 1, // 单点信息
M_DP_NA_1 = 3, // 双点信息
M_ME_NA_1 = 9, // 测量值,归一化值
M_ME_NB_1 = 11, // 测量值,标度化值
M_ME_NC_1 = 13, // 测量值,短浮点数
C_IC_NA_1 = 100, // 总召唤命令
C_CI_NA_1 = 101, // 电能量总召唤命令
C_SC_NA_1 = 45 // 单点遥控命令
} IEC104_TypeID;
// 单点信息值
typedef enum {
IEC104_SP_OFF = 0, // 分/关
IEC104_SP_ON = 1, // 合/开
IEC104_SP_INDETERMINATE = 2
} IEC104_SinglePoint;
// 双点信息值
typedef enum {
IEC104_DP_OFF = 1, // 分
IEC104_DP_ON = 2, // 合
IEC104_DP_INDETERMINATE_OFF = 0,
IEC104_DP_INDETERMINATE_ON = 3
} IEC104_DoublePoint;
// 信息体地址(常用范围)
typedef enum {
ADDR_SINGLE_POINT_START = 0x0001, // 单点信息起始地址
ADDR_SINGLE_POINT_END = 0x0FFF,
ADDR_MEASUREMENT_START = 0x1000, // 测量值起始地址
ADDR_MEASUREMENT_END = 0x1FFF,
ADDR_REMOTE_CTRL_START = 0x6001, // 遥控起始地址
ADDR_REMOTE_CTRL_END = 0x6FFF
} IEC104_InfoAddr;
// ASDU结构
typedef struct {
uint8_t typeId; // 类型标识
uint8_t vsq; // 可变结构限定词
uint8_t causeTx; // 传输原因
uint16_t commonAddr; // 公共地址(站地址)
// 信息体(根据typeId不同而不同)
uint8_t *infoData; // 信息体数据指针
uint16_t infoLength; // 信息体数据长度
} IEC104_ASDU;
// APDU结构
typedef struct {
uint8_t startChar; // 启动字符0x68
uint8_t apduLen; // APDU长度
// 控制域(4字节)
union {
struct {
uint8_t ctrl1; // 控制域字节1
uint8_t ctrl2; // 控制域字节2
uint8_t ctrl3; // 控制域字节3
uint8_t ctrl4; // 控制域字节4
};
// I格式帧控制域
struct {
uint16_t sendSeqNum : 15; // 发送序列号(I格式)
uint16_t iFrame : 1; // I格式标志
uint16_t recvSeqNum : 15; // 接收序列号(I格式)
uint16_t reserved1 : 1; // 保留
} iCtrl;
// S格式帧控制域
struct {
uint16_t sFrame : 1; // S格式标志
uint16_t reserved2 : 1; // 保留
uint16_t recvSeqNumS: 15; // 接收序列号(S格式)
uint16_t reserved3 : 15; // 保留
} sCtrl;
// U格式帧控制域
struct {
uint8_t uFrame : 4; // U格式标志
uint8_t uFunction : 6; // U格式功能
uint16_t reserved4 : 18; // 保留
} uCtrl;
};
// ASDU(可选,长度可变)
IEC104_ASDU asdu;
} IEC104_APDU;
// 通信连接状态
typedef enum {
IEC104_STATE_STOPPED = 0, // 停止
IEC104_STATE_IDLE, // 空闲
IEC104_STATE_TEST, // 测试
IEC104_STATE_ACTIVE, // 激活
IEC104_STATE_INVALID // 无效
} IEC104_ConnectionState;
// 通信参数结构
typedef struct {
uint16_t k; // 未确认I帧最大数目
uint16_t w; // 接收序列号确认前最大I帧数
uint16_t t0; // 连接建立超时(秒)
uint16_t t1; // 发送或测试APDU超时(秒)
uint16_t t2; // 无数据报文时确认超时(秒)
uint16_t t3; // 长期空闲发送测试帧周期(秒)
} IEC104_Parameters;
#endif
2. 服务端实现
c
// iec104_server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <time.h>
#include "iec104_def.h"
#define SERVER_PORT 2404
#define MAX_CLIENTS 10
#define BUFFER_SIZE 1024
// 客户端连接信息
typedef struct {
int socket;
struct sockaddr_in addr;
pthread_t threadId;
// IEC 104 协议相关
uint16_t sendSeqNum; // 发送序列号
uint16_t recvSeqNum; // 接收序列号
IEC104_ConnectionState state;
time_t lastActivity; // 最后活动时间
// 统计信息
uint32_t totalFrames;
uint32_t iFrames;
uint32_t sFrames;
uint32_t uFrames;
} ClientConnection;
// 全局变量
static ClientConnection clients[MAX_CLIENTS];
static int serverFd = -1;
static volatile int serverRunning = 1;
static pthread_mutex_t clientMutex = PTHREAD_MUTEX_INITIALIZER;
// 默认通信参数
static IEC104_Parameters defaultParams = {
.k = 12, // 未确认I帧最大数目
.w = 8, // 接收序列号确认前最大I帧数
.t0 = 30, // 连接建立超时
.t1 = 15, // 发送或测试APDU超时
.t2 = 10, // 无数据报文时确认超时
.t3 = 20 // 长期空闲发送测试帧周期
};
// 初始化服务器
int IEC104_Server_Init(int port) {
struct sockaddr_in serverAddr;
// 创建Socket
serverFd = socket(AF_INET, SOCK_STREAM, 0);
if (serverFd < 0) {
perror("Socket creation failed");
return -1;
}
// 设置SO_REUSEADDR选项
int opt = 1;
if (setsockopt(serverFd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
perror("Setsockopt failed");
close(serverFd);
return -1;
}
// 绑定地址
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
serverAddr.sin_port = htons(port);
if (bind(serverFd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
perror("Bind failed");
close(serverFd);
return -1;
}
// 监听连接
if (listen(serverFd, 5) < 0) {
perror("Listen failed");
close(serverFd);
return -1;
}
printf("IEC 104 Server started on port %d\n", port);
return 0;
}
// 解析APDU
int ParseAPDU(uint8_t *buffer, int len, IEC104_APDU *apdu) {
if (len < IEC104_MIN_APDU_LEN) {
return -1; // 长度不足
}
if (buffer[0] != IEC104_START_CHAR) {
return -2; // 启动字符错误
}
apdu->startChar = buffer[0];
apdu->apduLen = buffer[1];
if (apdu->apduLen != len - 2) { // 减去启动字符和长度字段
return -3; // 长度不匹配
}
// 解析控制域
apdu->ctrl1 = buffer[2];
apdu->ctrl2 = buffer[3];
apdu->ctrl3 = buffer[4];
apdu->ctrl4 = buffer[5];
// 判断帧类型
if ((apdu->ctrl1 & 0x01) == 0) { // I格式
apdu->iCtrl.iFrame = 1;
apdu->iCtrl.sendSeqNum = ((apdu->ctrl1 >> 1) | (apdu->ctrl2 << 7)) & 0x7FFF;
apdu->iCtrl.recvSeqNum = ((apdu->ctrl3 >> 1) | (apdu->ctrl4 << 7)) & 0x7FFF;
} else if ((apdu->ctrl1 & 0x03) == 1) { // S格式
apdu->sCtrl.sFrame = 1;
apdu->sCtrl.recvSeqNumS = ((apdu->ctrl3 >> 1) | (apdu->ctrl4 << 7)) & 0x7FFF;
} else { // U格式
apdu->uCtrl.uFrame = 1;
apdu->uCtrl.uFunction = (apdu->ctrl1 >> 2) & 0x3F;
}
return 0;
}
// 构建I格式APDU
int Build_I_Frame(uint8_t *buffer, uint16_t sendSeq, uint16_t recvSeq, IEC104_ASDU *asdu) {
int pos = 0;
// 启动字符
buffer[pos++] = IEC104_START_CHAR;
// 计算APDU长度
uint8_t apduLen = 4; // 控制域长度
if (asdu != NULL) {
// ASDU长度 = 类型(1) + VSQ(1) + 传输原因(1) + 公共地址(2) + 信息体
apduLen += 4 + asdu->infoLength;
}
buffer[pos++] = apduLen;
// 控制域(I格式)
buffer[pos++] = (sendSeq << 1) & 0xFE; // 低7位
buffer[pos++] = (sendSeq >> 7) & 0xFF; // 高8位
buffer[pos++] = (recvSeq << 1) & 0xFE; // 低7位
buffer[pos++] = (recvSeq >> 7) & 0xFF; // 高8位
// 如果有ASDU
if (asdu != NULL) {
buffer[pos++] = asdu->typeId;
buffer[pos++] = asdu->vsq;
buffer[pos++] = asdu->causeTx;
// 公共地址(低字节在前)
buffer[pos++] = asdu->commonAddr & 0xFF;
buffer[pos++] = (asdu->commonAddr >> 8) & 0xFF;
// 信息体
if (asdu->infoData != NULL && asdu->infoLength > 0) {
memcpy(&buffer[pos], asdu->infoData, asdu->infoLength);
pos += asdu->infoLength;
}
}
return pos;
}
// 构建U格式APDU
int Build_U_Frame(uint8_t *buffer, uint8_t uFunction) {
int pos = 0;
buffer[pos++] = IEC104_START_CHAR;
buffer[pos++] = 4; // 只有控制域
// 控制域(U格式)
switch(uFunction) {
case U_STARTDT_ACT:
buffer[pos++] = 0x07; // 0000 0111
break;
case U_STARTDT_CON:
buffer[pos++] = 0x0B; // 0000 1011
break;
case U_STOPDT_ACT:
buffer[pos++] = 0x13; // 0001 0011
break;
case U_STOPDT_CON:
buffer[pos++] = 0x23; // 0010 0011
break;
case U_TESTFR_ACT:
buffer[pos++] = 0x43; // 0100 0011
break;
case U_TESTFR_CON:
buffer[pos++] = 0x83; // 1000 0011
break;
default:
return -1;
}
buffer[pos++] = 0x00;
buffer[pos++] = 0x00;
buffer[pos++] = 0x00;
return pos;
}
// 处理U格式帧
void Process_U_Frame(ClientConnection *client, IEC104_APDU *apdu) {
switch(apdu->uCtrl.uFunction) {
case U_STARTDT_ACT:
printf("Client %s:%d - STARTDT ACT received\n",
inet_ntoa(client->addr.sin_addr), ntohs(client->addr.sin_port));
// 发送确认
uint8_t response[6];
int len = Build_U_Frame(response, U_STARTDT_CON);
send(client->socket, response, len, 0);
client->state = IEC104_STATE_ACTIVE;
break;
case U_STOPDT_ACT:
printf("Client %s:%d - STOPDT ACT received\n",
inet_ntoa(client->addr.sin_addr), ntohs(client->addr.sin_port));
len = Build_U_Frame(response, U_STOPDT_CON);
send(client->socket, response, len, 0);
client->state = IEC104_STATE_IDLE;
break;
case U_TESTFR_ACT:
printf("Client %s:%d - TESTFR ACT received\n",
inet_ntoa(client->addr.sin_addr), ntohs(client->addr.sin_port));
len = Build_U_Frame(response, U_TESTFR_CON);
send(client->socket, response, len, 0);
break;
default:
printf("Unknown U format function: 0x%02X\n", apdu->uCtrl.uFunction);
}
}
// 处理I格式帧
void Process_I_Frame(ClientConnection *client, IEC104_APDU *apdu, uint8_t *buffer, int dataLen) {
if (dataLen < 6) { // 控制域+至少1字节ASDU
printf("I格式帧数据长度不足\n");
return;
}
// 解析ASDU
IEC104_ASDU asdu;
asdu.typeId = buffer[6];
asdu.vsq = buffer[7];
asdu.causeTx = buffer[8];
asdu.commonAddr = buffer[9] | (buffer[10] << 8);
// 信息体数据
int infoStart = 11;
asdu.infoLength = dataLen - 6 - 5; // 减去控制域和ASDU固定部分
asdu.infoData = &buffer[infoStart];
// 处理不同类型
switch(asdu.typeId) {
case M_SP_NA_1: // 单点信息
ProcessSinglePoint(client, &asdu);
break;
case M_DP_NA_1: // 双点信息
ProcessDoublePoint(client, &asdu);
break;
case M_ME_NA_1: // 测量值
case M_ME_NB_1:
case M_ME_NC_1:
ProcessMeasurement(client, &asdu);
break;
case C_IC_NA_1: // 总召唤命令
ProcessInterrogationCommand(client, &asdu);
break;
case C_SC_NA_1: // 单点遥控命令
ProcessRemoteControl(client, &asdu);
break;
default:
printf("Unknown ASDU type: %d\n", asdu.typeId);
}
// 更新接收序列号
client->recvSeqNum = (apdu->iCtrl.sendSeqNum + 1) % 32768;
// 发送S格式确认帧(如果需要)
if ((client->recvSeqNum - client->sendSeqNum) >= defaultParams.w) {
SendSFrame(client);
}
}
// 处理单点信息
void ProcessSinglePoint(ClientConnection *client, IEC104_ASDU *asdu) {
int numObjects = asdu->vsq & 0x7F; // 获取信息体数目
int sq = (asdu->vsq >> 7) & 0x01; // 获取SQ标志
printf("收到单点信息: 站址=%d, 原因=%d, 数量=%d\n",
asdu->commonAddr, asdu->causeTx, numObjects);
if (sq) { // SQ=1, 连续地址
uint16_t startAddr = asdu->infoData[0] | (asdu->infoData[1] << 8);
uint8_t *values = &asdu->infoData[2];
for (int i = 0; i < numObjects; i++) {
uint8_t value = values[i] & 0x01; // 只取最低位
uint8_t quality = (values[i] >> 7) & 0x01; // 品质描述词
printf(" 地址 %d: 值=%d, 品质=%s\n",
startAddr + i, value, quality ? "无效" : "有效");
}
} else { // SQ=0, 离散地址
uint8_t *ptr = asdu->infoData;
for (int i = 0; i < numObjects; i++) {
uint16_t addr = ptr[0] | (ptr[1] << 8);
uint8_t value = ptr[2] & 0x01;
uint8_t quality = (ptr[2] >> 7) & 0x01;
printf(" 地址 %d: 值=%d, 品质=%s\n",
addr, value, quality ? "无效" : "有效");
ptr += 3; // 每个信息体3字节
}
}
}
// 处理总召唤命令
void ProcessInterrogationCommand(ClientConnection *client, IEC104_ASDU *asdu) {
uint8_t qualifier = asdu->infoData[0]; // 召唤限定词
printf("收到总召唤命令: 站址=%d, 限定词=0x%02X\n",
asdu->commonAddr, qualifier);
if (qualifier == 0x14) { // 召唤全部信息
// 发送召唤确认(激活确认)
SendInterrogationConfirm(client, asdu->commonAddr);
// 模拟发送遥测数据
SendMeasurements(client, asdu->commonAddr);
// 发送召唤终止
SendInterrogationTermination(client, asdu->commonAddr);
}
}
// 发送测量值
void SendMeasurements(ClientConnection *client, uint16_t commonAddr) {
uint8_t buffer[256];
IEC104_ASDU asdu;
// 构建归一化测量值(假设有5个测量点)
uint8_t measurementData[5 * 3]; // 每个点: 地址(2)+值(2)+品质(1) = 5字节
for (int i = 0; i < 5; i++) {
uint16_t addr = 0x1000 + i; // 遥测地址
int16_t value = 1000 + i * 100; // 模拟值
uint8_t quality = 0; // 品质描述词(有效)
measurementData[i*3] = addr & 0xFF;
measurementData[i*3 + 1] = (addr >> 8) & 0xFF;
measurementData[i*3 + 2] = value & 0xFF;
measurementData[i*3 + 3] = (value >> 8) & 0xFF;
measurementData[i*3 + 4] = quality;
}
asdu.typeId = M_ME_NA_1;
asdu.vsq = 0x85; // SQ=1, 信息体数量=5
asdu.causeTx = SPONTANEOUS;
asdu.commonAddr = commonAddr;
asdu.infoData = measurementData;
asdu.infoLength = 5 * 3;
// 构建I格式帧
int len = Build_I_Frame(buffer, client->sendSeqNum, client->recvSeqNum, &asdu);
if (send(client->socket, buffer, len, 0) > 0) {
client->sendSeqNum = (client->sendSeqNum + 1) % 32768;
}
}
// 发送召唤确认
void SendInterrogationConfirm(ClientConnection *client, uint16_t commonAddr) {
uint8_t buffer[256];
IEC104_ASDU asdu;
uint8_t confirmData[1] = {0x14}; // 激活确认,召唤限定词0x14
asdu.typeId = C_IC_NA_1;
asdu.vsq = 0x01; // 单个信息体
asdu.causeTx = ACTIVATION_CON;
asdu.commonAddr = commonAddr;
asdu.infoData = confirmData;
asdu.infoLength = 1;
int len = Build_I_Frame(buffer, client->sendSeqNum, client->recvSeqNum, &asdu);
if (send(client->socket, buffer, len, 0) > 0) {
client->sendSeqNum = (client->sendSeqNum + 1) % 32768;
}
}
// 发送召唤终止
void SendInterrogationTermination(ClientConnection *client, uint16_t commonAddr) {
uint8_t buffer[256];
IEC104_ASDU asdu;
uint8_t termData[1] = {0x14}; // 召唤终止,召唤限定词0x14
asdu.typeId = C_IC_NA_1;
asdu.vsq = 0x01;
asdu.causeTx = INTERROGATED_TERM;
asdu.commonAddr = commonAddr;
asdu.infoData = termData;
asdu.infoLength = 1;
int len = Build_I_Frame(buffer, client->sendSeqNum, client->recvSeqNum, &asdu);
if (send(client->socket, buffer, len, 0) > 0) {
client->sendSeqNum = (client->sendSeqNum + 1) % 32768;
}
}
// 发送S格式确认帧
void SendSFrame(ClientConnection *client) {
uint8_t buffer[6];
buffer[0] = IEC104_START_CHAR;
buffer[1] = 0x04; // 控制域长度
// S格式帧控制域: 01 RR RRRRRR RR RRRRRR
buffer[2] = 0x01; // 低字节: 0000 0001
buffer[3] = 0x00; // 高字节
// 接收序列号(低7位)
uint16_t recvSeq = client->recvSeqNum;
buffer[4] = (recvSeq << 1) & 0xFE;
buffer[5] = (recvSeq >> 7) & 0xFF;
send(client->socket, buffer, 6, 0);
}
// 客户端处理线程
void *ClientHandler(void *arg) {
ClientConnection *client = (ClientConnection *)arg;
uint8_t buffer[BUFFER_SIZE];
IEC104_APDU apdu;
printf("New client connected: %s:%d\n",
inet_ntoa(client->addr.sin_addr), ntohs(client->addr.sin_port));
// 初始化客户端状态
client->state = IEC104_STATE_IDLE;
client->sendSeqNum = 0;
client->recvSeqNum = 0;
client->lastActivity = time(NULL);
while (serverRunning) {
fd_set readfds;
struct timeval timeout = {1, 0}; // 1秒超时
FD_ZERO(&readfds);
FD_SET(client->socket, &readfds);
int ret = select(client->socket + 1, &readfds, NULL, NULL, &timeout);
if (ret < 0) {
perror("Select error");
break;
} else if (ret == 0) {
// 超时,检查是否需要发送测试帧
time_t now = time(NULL);
if (difftime(now, client->lastActivity) > defaultParams.t3) {
SendTestFrame(client);
client->lastActivity = now;
}
continue;
}
// 接收数据
int n = recv(client->socket, buffer, BUFFER_SIZE, 0);
if (n <= 0) {
printf("Client %s:%d disconnected\n",
inet_ntoa(client->addr.sin_addr), ntohs(client->addr.sin_port));
break;
}
client->lastActivity = time(NULL);
// 解析APDU
int parseResult = ParseAPDU(buffer, n, &apdu);
if (parseResult < 0) {
printf("Parse APDU failed: %d\n", parseResult);
continue;
}
// 处理不同类型的帧
if (apdu.iCtrl.iFrame) {
client->iFrames++;
Process_I_Frame(client, &apdu, buffer, n);
} else if (apdu.sCtrl.sFrame) {
client->sFrames++;
// 处理S格式帧
printf("S format frame received\n");
} else if (apdu.uCtrl.uFrame) {
client->uFrames++;
Process_U_Frame(client, &apdu);
}
client->totalFrames++;
}
close(client->socket);
client->socket = -1;
return NULL;
}
// 发送测试帧
void SendTestFrame(ClientConnection *client) {
uint8_t buffer[6];
int len = Build_U_Frame(buffer, U_TESTFR_ACT);
if (send(client->socket, buffer, len, 0) > 0) {
printf("Test frame sent to client %s:%d\n",
inet_ntoa(client->addr.sin_addr), ntohs(client->addr.sin_port));
}
}
// 主服务器循环
void IEC104_Server_Run() {
struct sockaddr_in clientAddr;
socklen_t addrLen = sizeof(clientAddr);
while (serverRunning) {
// 接受新连接
int clientFd = accept(serverFd, (struct sockaddr*)&clientAddr, &addrLen);
if (clientFd < 0) {
perror("Accept failed");
continue;
}
// 查找空闲客户端槽位
int slot = -1;
pthread_mutex_lock(&clientMutex);
for (int i = 0; i < MAX_CLIENTS; i++) {
if (clients[i].socket == -1) {
slot = i;
break;
}
}
if (slot != -1) {
clients[slot].socket = clientFd;
clients[slot].addr = clientAddr;
// 创建客户端处理线程
pthread_create(&clients[slot].threadId, NULL, ClientHandler, &clients[slot]);
} else {
printf("No available slot for new client\n");
close(clientFd);
}
pthread_mutex_unlock(&clientMutex);
}
}
// 清理资源
void IEC104_Server_Cleanup() {
serverRunning = 0;
// 等待所有客户端线程结束
for (int i = 0; i < MAX_CLIENTS; i++) {
if (clients[i].socket != -1) {
pthread_join(clients[i].threadId, NULL);
close(clients[i].socket);
}
}
if (serverFd != -1) {
close(serverFd);
}
pthread_mutex_destroy(&clientMutex);
}
int main() {
// 初始化客户端列表
for (int i = 0; i < MAX_CLIENTS; i++) {
clients[i].socket = -1;
}
// 启动服务器
if (IEC104_Server_Init(SERVER_PORT) != 0) {
return 1;
}
// 运行服务器
IEC104_Server_Run();
// 清理
IEC104_Server_Cleanup();
return 0;
}
3. 客户端实现
c
// iec104_client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <time.h>
#include "iec104_def.h"
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 2404
#define BUFFER_SIZE 1024
typedef struct {
int socket;
pthread_t recvThread;
pthread_t sendThread;
// IEC 104 协议相关
uint16_t sendSeqNum;
uint16_t recvSeqNum;
IEC104_ConnectionState state;
time_t lastSendTime;
time_t lastRecvTime;
// 统计
uint32_t iFrameCount;
uint32_t sFrameCount;
uint32_t uFrameCount;
} IEC104_Client;
// 全局客户端实例
static IEC104_Client client;
static volatile int clientRunning = 1;
// 连接服务器
int IEC104_Client_Connect(const char *ip, int port) {
struct sockaddr_in serverAddr;
// 创建Socket
client.socket = socket(AF_INET, SOCK_STREAM, 0);
if (client.socket < 0) {
perror("Socket creation failed");
return -1;
}
// 设置服务器地址
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(port);
if (inet_pton(AF_INET, ip, &serverAddr.sin_addr) <= 0) {
perror("Invalid address");
close(client.socket);
return -1;
}
// 连接服务器
if (connect(client.socket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
perror("Connection failed");
close(client.socket);
return -1;
}
printf("Connected to server %s:%d\n", ip, port);
// 初始化客户端状态
client.sendSeqNum = 0;
client.recvSeqNum = 0;
client.state = IEC104_STATE_STOPPED;
client.iFrameCount = 0;
client.sFrameCount = 0;
client.uFrameCount = 0;
client.lastSendTime = time(NULL);
client.lastRecvTime = time(NULL);
return 0;
}
// 启动数据传输
int StartDataTransmission() {
uint8_t buffer[6];
int len = Build_U_Frame(buffer, U_STARTDT_ACT);
if (send(client.socket, buffer, len, 0) <= 0) {
perror("Send STARTDT_ACT failed");
return -1;
}
printf("STARTDT ACT sent\n");
// 等待确认
uint8_t recvBuffer[BUFFER_SIZE];
fd_set readfds;
struct timeval timeout = {5, 0}; // 5秒超时
FD_ZERO(&readfds);
FD_SET(client.socket, &readfds);
int ret = select(client.socket + 1, &readfds, NULL, NULL, &timeout);
if (ret <= 0) {
printf("No response to STARTDT_ACT\n");
return -1;
}
int n = recv(client.socket, recvBuffer, BUFFER_SIZE, 0);
if (n <= 0) {
perror("Receive failed");
return -1;
}
// 解析响应
IEC104_APDU apdu;
if (ParseAPDU(recvBuffer, n, &apdu) == 0 &&
apdu.uCtrl.uFrame && apdu.uCtrl.uFunction == U_STARTDT_CON) {
client.state = IEC104_STATE_ACTIVE;
printf("STARTDT confirmed, data transmission started\n");
return 0;
}
printf("Invalid response to STARTDT_ACT\n");
return -1;
}
// 发送总召唤命令
void SendGeneralInterrogation(uint16_t commonAddr) {
uint8_t buffer[256];
IEC104_ASDU asdu;
uint8_t interrogationData[1] = {0x14}; // 召唤全部信息
asdu.typeId = C_IC_NA_1;
asdu.vsq = 0x01; // 单个信息体
asdu.causeTx = ACTIVATION;
asdu.commonAddr = commonAddr;
asdu.infoData = interrogationData;
asdu.infoLength = 1;
int len = Build_I_Frame(buffer, client.sendSeqNum, client.recvSeqNum, &asdu);
if (send(client.socket, buffer, len, 0) > 0) {
client.sendSeqNum = (client.sendSeqNum + 1) % 32768;
client.lastSendTime = time(NULL);
printf("General interrogation sent (seq=%d)\n", client.sendSeqNum - 1);
} else {
perror("Send interrogation failed");
}
}
// 发送单点遥控命令
void SendRemoteControl(uint16_t commonAddr, uint16_t pointAddr, uint8_t command) {
uint8_t buffer[256];
IEC104_ASDU asdu;
// 遥控命令信息体: 地址(2) + 命令(1) + 命令限定词(1)
uint8_t ctrlData[4];
ctrlData[0] = pointAddr & 0xFF; // 地址低字节
ctrlData[1] = (pointAddr >> 8) & 0xFF; // 地址高字节
ctrlData[2] = command & 0x01; // 命令(0=分, 1=合)
ctrlData[3] = 0x00; // 命令限定词(0=无时标, 选择/执行)
asdu.typeId = C_SC_NA_1;
asdu.vsq = 0x01; // 单个信息体
asdu.causeTx = ACTIVATION;
asdu.commonAddr = commonAddr;
asdu.infoData = ctrlData;
asdu.infoLength = 4;
int len = Build_I_Frame(buffer, client.sendSeqNum, client.recvSeqNum, &asdu);
if (send(client.socket, buffer, len, 0) > 0) {
client.sendSeqNum = (client.sendSeqNum + 1) % 32768;
client.lastSendTime = time(NULL);
printf("Remote control command sent: addr=0x%04X, cmd=%d\n", pointAddr, command);
} else {
perror("Send remote control failed");
}
}
// 接收数据线程
void *ReceiveThread(void *arg) {
uint8_t buffer[BUFFER_SIZE];
while (clientRunning) {
fd_set readfds;
struct timeval timeout = {1, 0};
FD_ZERO(&readfds);
FD_SET(client.socket, &readfds);
int ret = select(client.socket + 1, &readfds, NULL, NULL, &timeout);
if (ret < 0) {
perror("Select error in receive thread");
break;
} else if (ret == 0) {
// 超时,继续循环
continue;
}
// 接收数据
int n = recv(client.socket, buffer, BUFFER_SIZE, 0);
if (n <= 0) {
printf("Server disconnected\n");
break;
}
client.lastRecvTime = time(NULL);
// 解析APDU
IEC104_APDU apdu;
if (ParseAPDU(buffer, n, &apdu) < 0) {
printf("Parse APDU failed\n");
continue;
}
// 处理不同类型的帧
if (apdu.iCtrl.iFrame) {
client.iFrameCount++;
printf("I frame received: SendSeq=%d, RecvSeq=%d\n",
apdu.iCtrl.sendSeqNum, apdu.iCtrl.recvSeqNum);
// 处理ASDU
if (n > 6) { // 有ASDU数据
ProcessReceivedASDU(&apdu, buffer + 6, n - 6);
}
// 更新接收序列号
client.recvSeqNum = (apdu.iCtrl.sendSeqNum + 1) % 32768;
} else if (apdu.sCtrl.sFrame) {
client.sFrameCount++;
printf("S frame received: RecvSeq=%d\n", apdu.sCtrl.recvSeqNumS);
} else if (apdu.uCtrl.uFrame) {
client.uFrameCount++;
ProcessUFrame(&apdu);
}
}
return NULL;
}
// 处理接收到的ASDU
void ProcessReceivedASDU(IEC104_APDU *apdu, uint8_t *data, int dataLen) {
if (dataLen < 5) { // ASDU至少5字节
return;
}
IEC104_ASDU asdu;
asdu.typeId = data[0];
asdu.vsq = data[1];
asdu.causeTx = data[2];
asdu.commonAddr = data[3] | (data[4] << 8);
int numObjects = asdu.vsq & 0x7F;
printf("ASDU received: Type=0x%02X, Cause=%d, Addr=%d, Num=%d\n",
asdu.typeId, asdu.causeTx, asdu.commonAddr, numObjects);
// 根据类型处理
switch(asdu.typeId) {
case M_SP_NA_1:
printf("Single point information\n");
break;
case M_ME_NA_1:
case M_ME_NB_1:
case M_ME_NC_1:
printf("Measurement value\n");
break;
case C_IC_NA_1:
if (asdu.causeTx == INTERROGATED_TERM) {
printf("General interrogation terminated\n");
}
break;
default:
printf("Unknown ASDU type: 0x%02X\n", asdu.typeId);
}
}
// 处理U格式帧
void ProcessUFrame(IEC104_APDU *apdu) {
switch(apdu.uCtrl.uFunction) {
case U_STARTDT_CON:
printf("STARTDT confirmed\n");
break;
case U_STOPDT_CON:
printf("STOPDT confirmed\n");
break;
case U_TESTFR_ACT:
printf("Test frame received, sending confirmation\n");
SendTestFrameConfirmation();
break;
case U_TESTFR_CON:
printf("Test frame confirmed\n");
break;
default:
printf("Unknown U function: 0x%02X\n", apdu.uCtrl.uFunction);
}
}
// 发送测试帧确认
void SendTestFrameConfirmation() {
uint8_t buffer[6];
int len = Build_U_Frame(buffer, U_TESTFR_CON);
if (send(client.socket, buffer, len, 0) > 0) {
printf("Test frame confirmation sent\n");
}
}
// 发送S格式确认帧
void SendSFrame() {
uint8_t buffer[6];
buffer[0] = IEC104_START_CHAR;
buffer[1] = 0x04;
// S格式控制域
buffer[2] = 0x01;
buffer[3] = 0x00;
buffer[4] = (client.recvSeqNum << 1) & 0xFE;
buffer[5] = (client.recvSeqNum >> 7) & 0xFF;
if (send(client.socket, buffer, 6, 0) > 0) {
printf("S frame sent: RecvSeq=%d\n", client.recvSeqNum);
}
}
// 停止数据传输
void StopDataTransmission() {
uint8_t buffer[6];
int len = Build_U_Frame(buffer, U_STOPDT_ACT);
if (send(client.socket, buffer, len, 0) > 0) {
printf("STOPDT ACT sent\n");
}
}
// 主函数
int main(int argc, char *argv[]) {
const char *serverIp = SERVER_IP;
int serverPort = SERVER_PORT;
if (argc >= 3) {
serverIp = argv[1];
serverPort = atoi(argv[2]);
}
// 连接服务器
if (IEC104_Client_Connect(serverIp, serverPort) != 0) {
return 1;
}
// 启动数据传输
if (StartDataTransmission() != 0) {
close(client.socket);
return 1;
}
// 创建接收线程
pthread_create(&client.recvThread, NULL, ReceiveThread, NULL);
// 主循环
int choice;
while (clientRunning) {
printf("\n=== IEC 104 Client Menu ===\n");
printf("1. Send General Interrogation\n");
printf("2. Send Remote Control Command\n");
printf("3. Send Test Frame\n");
printf("4. Send S Frame\n");
printf("5. Stop Data Transmission\n");
printf("6. Show Statistics\n");
printf("7. Exit\n");
printf("Choice: ");
scanf("%d", &choice);
getchar(); // 清除换行符
switch(choice) {
case 1:
SendGeneralInterrogation(1); // 站地址为1
break;
case 2: {
uint16_t pointAddr;
uint8_t command;
printf("Enter point address (hex): ");
scanf("%hx", &pointAddr);
printf("Enter command (0=OFF, 1=ON): ");
scanf("%hhu", &command);
SendRemoteControl(1, pointAddr, command);
break;
}
case 3: {
uint8_t buffer[6];
int len = Build_U_Frame(buffer, U_TESTFR_ACT);
send(client.socket, buffer, len, 0);
printf("Test frame sent\n");
break;
}
case 4:
SendSFrame();
break;
case 5:
StopDataTransmission();
break;
case 6:
printf("Statistics:\n");
printf(" I Frames: %u\n", client.iFrameCount);
printf(" S Frames: %u\n", client.sFrameCount);
printf(" U Frames: %u\n", client.uFrameCount);
printf(" Send Sequence: %u\n", client.sendSeqNum);
printf(" Recv Sequence: %u\n", client.recvSeqNum);
break;
case 7:
clientRunning = 0;
break;
default:
printf("Invalid choice\n");
}
// 检查是否需要发送S帧
time_t now = time(NULL);
if (difftime(now, client.lastRecvTime) > 10) { // 10秒无接收
SendSFrame();
}
}
// 清理
pthread_join(client.recvThread, NULL);
close(client.socket);
printf("Client terminated\n");
return 0;
}
4. Makefile 编译文件
makefile
# Makefile
CC = gcc
CFLAGS = -Wall -g -pthread
TARGETS = iec104_server iec104_client
all: $(TARGETS)
iec104_server: iec104_server.c
$(CC) $(CFLAGS) -o iec104_server iec104_server.c
iec104_client: iec104_client.c
$(CC) $(CFLAGS) -o iec104_client iec104_client.c
clean:
rm -f $(TARGETS) *.o
run_server: iec104_server
./iec104_server
run_client: iec104_client
./iec104_client 127.0.0.1 2404
5. 使用说明
编译和运行
bash
# 编译
make
# 运行服务端
./iec104_server
# 运行客户端(另一个终端)
./iec104_client 127.0.0.1 2404
测试流程
-
启动服务端:
- 监听2404端口
- 等待客户端连接
-
启动客户端:
- 自动发送STARTDT激活请求
- 建立IEC 104连接
-
客户端操作:
- 发送总召唤命令
- 接收遥测数据
- 发送遥控命令
- 查看通信统计
协议要点说明
-
连接建立:
- 客户端发送U格式STARTDT激活帧
- 服务端回复STARTDT确认帧
- 开始数据传输
-
I格式帧:
- 用于传输ASDU数据
- 包含发送和接收序列号
- 实现流量控制
-
S格式帧:
- 仅用于确认接收
- 不包含ASDU
- 提高传输效率
-
U格式帧:
- 用于控制功能
- 启动/停止数据传输
- 测试链路
-
ASDU结构:
- 类型标识:定义信息类型
- VSQ:信息体数目和结构
- 传输原因:信息产生原因
- 公共地址:站地址
- 信息体:具体数据
参考代码 C语言IEC104服务端客户端 www.youwenfan.com/contentcsu/57082.html
6. 注意事项
-
序列号管理:
- 发送和接收序列号需独立维护
- 序列号范围0-32767
- 达到最大值后从0重新开始
-
超时处理:
- 连接建立超时(t0)
- 发送确认超时(t1)
- 空闲测试超时(t3)
-
错误处理:
- 校验帧格式
- 处理序列号异常
- 连接断开重连
-
多客户端支持:
- 服务端支持多连接
- 每个连接独立维护状态
- 线程安全处理
这个实现提供了IEC 104协议的核心功能,可以根据实际需求进行扩展和优化,如增加更多ASDU类型支持、完善错误恢复机制、添加配置管理等。