IEC 104 协议 C 语言实现

基于 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

测试流程

  1. 启动服务端

    • 监听2404端口
    • 等待客户端连接
  2. 启动客户端

    • 自动发送STARTDT激活请求
    • 建立IEC 104连接
  3. 客户端操作

    • 发送总召唤命令
    • 接收遥测数据
    • 发送遥控命令
    • 查看通信统计

协议要点说明

  1. 连接建立

    • 客户端发送U格式STARTDT激活帧
    • 服务端回复STARTDT确认帧
    • 开始数据传输
  2. I格式帧

    • 用于传输ASDU数据
    • 包含发送和接收序列号
    • 实现流量控制
  3. S格式帧

    • 仅用于确认接收
    • 不包含ASDU
    • 提高传输效率
  4. U格式帧

    • 用于控制功能
    • 启动/停止数据传输
    • 测试链路
  5. ASDU结构

    • 类型标识:定义信息类型
    • VSQ:信息体数目和结构
    • 传输原因:信息产生原因
    • 公共地址:站地址
    • 信息体:具体数据

参考代码 C语言IEC104服务端客户端 www.youwenfan.com/contentcsu/57082.html

6. 注意事项

  1. 序列号管理

    • 发送和接收序列号需独立维护
    • 序列号范围0-32767
    • 达到最大值后从0重新开始
  2. 超时处理

    • 连接建立超时(t0)
    • 发送确认超时(t1)
    • 空闲测试超时(t3)
  3. 错误处理

    • 校验帧格式
    • 处理序列号异常
    • 连接断开重连
  4. 多客户端支持

    • 服务端支持多连接
    • 每个连接独立维护状态
    • 线程安全处理

这个实现提供了IEC 104协议的核心功能,可以根据实际需求进行扩展和优化,如增加更多ASDU类型支持、完善错误恢复机制、添加配置管理等。

相关推荐
摇滚侠1 小时前
DBeaver 导入数据库 导入 SQL 文件 MySQL 备份恢复
java·数据库·mysql
若兰幽竹2 小时前
【从零开始编写数据库系统:架构设计与实现】第5章:查询执行引擎与火山模型
数据库·架构·数据库内核·toydb
天空属于哈夫克32 小时前
企业微信API常见的错误和解决方案
java·数据库·企业微信
东风破1372 小时前
DM8达梦数据库备份、恢复原理介绍
数据库·oracle·dm达梦数据库
鹏子训3 小时前
AI记忆新思路:用SQLite替代向量数据库,去EMBEDDINGS化,谷歌开源Google Always On Memory Agent
数据库·人工智能·sqlite·embedding
啧不应该啊3 小时前
Day1 Python 与 C 的类型区别
c语言·开发语言
Frank_refuel3 小时前
终端环境下:Ubuntu 22.04.1 安装 MySQL 数据库
数据库·mysql·ubuntu
cen__y3 小时前
Linux07(信号01)
linux·运维·服务器·c语言·开发语言
虹科网络安全4 小时前
艾体宝产品|深度解读 Redis 8.4 新增功能:原子化 Slot 迁移(下)
数据库·redis·bootstrap