Qt Modbus TCP 通讯源码

一、系统架构设计

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    Qt Modbus TCP 通讯系统                  │
├─────────────────────────────────────────────────────────────┤
│  客户端层    │  协议层      │  网络层      │  数据层      │
│             │              │              │              │
│  • UI界面   │  • Modbus协议 │  • TCP连接   │  • 寄存器读写 │
│  • 参数配置 │  • 功能码处理 │  • 数据收发  │  • 线圈控制  │
│  • 状态显示 │  • 异常处理   │  • 超时重连  │  • 数据解析  │
│  • 日志记录 │  • CRC校验    │  • 多线程    │  • 数据缓存  │
└─────────────────────────────────────────────────────────────┘

二、源码实现

2.1 项目文件结构

复制代码
ModbusTCPClient/
├── CMakeLists.txt
├── ModbusTCPClient.pro
├── include/
│   ├── modbustcpclient.h
│   ├── modbusprotocol.h
│   ├── modbusregister.h
│   └── modbusexception.h
├── src/
│   ├── main.cpp
│   ├── modbustcpclient.cpp
│   ├── modbusprotocol.cpp
│   ├── modbusregister.cpp
│   └── modbusexception.cpp
├── ui/
│   └── mainwindow.ui
└── resources/
    └── icons/

2.2 主窗口界面 (mainwindow.h/cpp)

cpp 复制代码
// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTimer>
#include <QTableWidgetItem>
#include "modbustcpclient.h"

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    // 连接管理
    void on_pushButton_connect_clicked();
    void on_pushButton_disconnect_clicked();
    
    // 数据读写
    void on_pushButton_readCoils_clicked();
    void on_pushButton_readInputs_clicked();
    void on_pushButton_readHoldingRegisters_clicked();
    void on_pushButton_readInputRegisters_clicked();
    void on_pushButton_writeSingleCoil_clicked();
    void on_pushButton_writeSingleRegister_clicked();
    void on_pushButton_writeMultipleCoils_clicked();
    void on_pushButton_writeMultipleRegisters_clicked();
    
    // 状态更新
    void updateConnectionStatus(bool connected);
    void updateRegisterTable(const QVector<quint16> &registers);
    void updateCoilTable(const QVector<bool> &coils);
    void logMessage(const QString &message);
    
    // 定时刷新
    void on_pushButton_autoRefresh_clicked();
    void autoRefreshTimeout();
    
private:
    Ui::MainWindow *ui;
    ModbusTcpClient *modbusClient;
    QTimer *autoRefreshTimer;
    bool autoRefreshEnabled;
    
    void setupUI();
    void setupConnections();
    void initializeTables();
    void clearTables();
};

#endif // MAINWINDOW_H
cpp 复制代码
// mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDateTime>
#include <QMessageBox>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    , autoRefreshEnabled(false)
{
    ui->setupUi(this);
    
    // 初始化Modbus客户端
    modbusClient = new ModbusTcpClient(this);
    
    // 初始化定时器
    autoRefreshTimer = new QTimer(this);
    
    // 设置UI
    setupUI();
    setupConnections();
    initializeTables();
    
    // 设置默认值
    ui->lineEdit_ip->setText("192.168.1.100");
    ui->spinBox_port->setValue(502);
    ui->spinBox_slaveId->setValue(1);
    ui->spinBox_startAddress->setValue(0);
    ui->spinBox_quantity->setValue(10);
    
    logMessage("Modbus TCP Client 已启动");
}

void MainWindow::setupUI() {
    setWindowTitle("Modbus TCP 通讯客户端 v1.0");
    resize(1000, 700);
    
    // 设置表格列宽
    ui->tableWidget_holdingRegisters->setColumnWidth(0, 80);
    ui->tableWidget_holdingRegisters->setColumnWidth(1, 80);
    ui->tableWidget_holdingRegisters->setColumnWidth(2, 100);
    ui->tableWidget_holdingRegisters->setColumnWidth(3, 100);
    
    ui->tableWidget_coils->setColumnWidth(0, 80);
    ui->tableWidget_coils->setColumnWidth(1, 80);
    ui->tableWidget_coils->setColumnWidth(2, 100);
}

void MainWindow::setupConnections() {
    // Modbus客户端信号连接
    connect(modbusClient, &ModbusTcpClient::connectionStatusChanged,
            this, &MainWindow::updateConnectionStatus);
    connect(modbusClient, &ModbusTcpClient::logMessage,
            this, &MainWindow::logMessage);
    connect(modbusClient, &ModbusTcpClient::holdingRegistersRead,
            this, &MainWindow::updateRegisterTable);
    connect(modbusClient, &ModbusTcpClient::coilsRead,
            this, &MainWindow::updateCoilTable);
    
    // 定时器连接
    connect(autoRefreshTimer, &QTimer::timeout,
            this, &MainWindow::autoRefreshTimeout);
}

void MainWindow::initializeTables() {
    // 初始化保持寄存器表格
    QStringList registerHeaders = {"地址", "值(十进制)", "值(十六进制)", "值(二进制)"};
    ui->tableWidget_holdingRegisters->setHorizontalHeaderLabels(registerHeaders);
    ui->tableWidget_holdingRegisters->setRowCount(10);
    
    // 初始化线圈表格
    QStringList coilHeaders = {"地址", "状态", "值"};
    ui->tableWidget_coils->setHorizontalHeaderLabels(coilHeaders);
    ui->tableWidget_coils->setRowCount(10);
}

void MainWindow::on_pushButton_connect_clicked() {
    QString ipAddress = ui->lineEdit_ip->text();
    quint16 port = ui->spinBox_port->value();
    quint8 slaveId = ui->spinBox_slaveId->value();
    
    if (modbusClient->connectToServer(ipAddress, port, slaveId)) {
        logMessage(QString("正在连接服务器 %1:%2").arg(ipAddress).arg(port));
    } else {
        QMessageBox::warning(this, "连接失败", "无法连接到Modbus服务器!");
    }
}

void MainWindow::on_pushButton_disconnect_clicked() {
    modbusClient->disconnectFromServer();
    logMessage("已断开连接");
}

void MainWindow::on_pushButton_readHoldingRegisters_clicked() {
    if (!modbusClient->isConnected()) {
        QMessageBox::warning(this, "未连接", "请先连接到服务器!");
        return;
    }
    
    quint16 startAddress = ui->spinBox_startAddress->value();
    quint16 quantity = ui->spinBox_quantity->value();
    
    logMessage(QString("读取保持寄存器: 起始地址=%1, 数量=%2").arg(startAddress).arg(quantity));
    modbusClient->readHoldingRegisters(startAddress, quantity);
}

void MainWindow::on_pushButton_writeSingleRegister_clicked() {
    if (!modbusClient->isConnected()) {
        QMessageBox::warning(this, "未连接", "请先连接到服务器!");
        return;
    }
    
    quint16 address = ui->spinBox_singleAddress->value();
    quint16 value = ui->spinBox_singleValue->value();
    
    logMessage(QString("写入单个寄存器: 地址=%1, 值=%2").arg(address).arg(value));
    modbusClient->writeSingleRegister(address, value);
}

void MainWindow::updateConnectionStatus(bool connected) {
    if (connected) {
        ui->label_connectionStatus->setText("已连接");
        ui->label_connectionStatus->setStyleSheet("QLabel { color: green; }");
        ui->pushButton_connect->setEnabled(false);
        ui->pushButton_disconnect->setEnabled(true);
    } else {
        ui->label_connectionStatus->setText("未连接");
        ui->label_connectionStatus->setStyleSheet("QLabel { color: red; }");
        ui->pushButton_connect->setEnabled(true);
        ui->pushButton_disconnect->setEnabled(false);
    }
}

void MainWindow::updateRegisterTable(const QVector<quint16> &registers) {
    ui->tableWidget_holdingRegisters->setRowCount(registers.size());
    
    for (int i = 0; i < registers.size(); i++) {
        quint16 address = ui->spinBox_startAddress->value() + i;
        quint16 value = registers[i];
        
        // 地址列
        QTableWidgetItem *addressItem = new QTableWidgetItem(QString::number(address));
        addressItem->setTextAlignment(Qt::AlignCenter);
        ui->tableWidget_holdingRegisters->setItem(i, 0, addressItem);
        
        // 十进制值
        QTableWidgetItem *decimalItem = new QTableWidgetItem(QString::number(value));
        decimalItem->setTextAlignment(Qt::AlignCenter);
        ui->tableWidget_holdingRegisters->setItem(i, 1, decimalItem);
        
        // 十六进制值
        QTableWidgetItem *hexItem = new QTableWidgetItem(QString("0x%1").arg(value, 4, 16, QChar('0')));
        hexItem->setTextAlignment(Qt::AlignCenter);
        ui->tableWidget_holdingRegisters->setItem(i, 2, hexItem);
        
        // 二进制值
        QString binaryStr = QString("%1").arg(value, 16, 2, QChar('0'));
        QTableWidgetItem *binaryItem = new QTableWidgetItem(binaryStr);
        binaryItem->setTextAlignment(Qt::AlignCenter);
        ui->tableWidget_holdingRegisters->setItem(i, 3, binaryItem);
    }
}

void MainWindow::logMessage(const QString &message) {
    QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
    ui->textEdit_log->append(QString("[%1] %2").arg(timestamp).arg(message));
}

void MainWindow::on_pushButton_autoRefresh_clicked() {
    autoRefreshEnabled = !autoRefreshEnabled;
    
    if (autoRefreshEnabled) {
        ui->pushButton_autoRefresh->setText("停止自动刷新");
        autoRefreshTimer->start(1000); // 1秒刷新一次
        logMessage("自动刷新已启动");
    } else {
        ui->pushButton_autoRefresh->setText("自动刷新");
        autoRefreshTimer->stop();
        logMessage("自动刷新已停止");
    }
}

void MainWindow::autoRefreshTimeout() {
    if (modbusClient->isConnected()) {
        quint16 startAddress = ui->spinBox_startAddress->value();
        quint16 quantity = ui->spinBox_quantity->value();
        modbusClient->readHoldingRegisters(startAddress, quantity);
    }
}

MainWindow::~MainWindow() {
    delete ui;
}

2.3 Modbus TCP 客户端核心类 (modbustcpclient.h/cpp)

cpp 复制代码
// modbustcpclient.h
#ifndef MODBUSTCPCLIENT_H
#define MODBUSTCPCLIENT_H

#include <QObject>
#include <QTcpSocket>
#include <QTimer>
#include <QVector>
#include <QMutex>
#include "modbusprotocol.h"

class ModbusTcpClient : public QObject
{
    Q_OBJECT

public:
    explicit ModbusTcpClient(QObject *parent = nullptr);
    ~ModbusTcpClient();

    // 连接管理
    bool connectToServer(const QString &ipAddress, quint16 port, quint8 slaveId);
    void disconnectFromServer();
    bool isConnected() const { return connected; }
    
    // 读取功能
    bool readCoils(quint16 startAddress, quint16 quantity);
    bool readDiscreteInputs(quint16 startAddress, quint16 quantity);
    bool readHoldingRegisters(quint16 startAddress, quint16 quantity);
    bool readInputRegisters(quint16 startAddress, quint16 quantity);
    
    // 写入功能
    bool writeSingleCoil(quint16 address, bool value);
    bool writeSingleRegister(quint16 address, quint16 value);
    bool writeMultipleCoils(quint16 startAddress, const QVector<bool> &values);
    bool writeMultipleRegisters(quint16 startAddress, const QVector<quint16> &values);
    
    // 配置
    void setTimeout(quint32 timeoutMs) { responseTimeout = timeoutMs; }
    void setAutoReconnect(bool enable) { autoReconnect = enable; }

signals:
    void connectionStatusChanged(bool connected);
    void logMessage(const QString &message);
    void coilsRead(const QVector<bool> &coils);
    void discreteInputsRead(const QVector<bool> &inputs);
    void holdingRegistersRead(const QVector<quint16> &registers);
    void inputRegistersRead(const QVector<quint16> &registers);
    void writeComplete(bool success);

private slots:
    void onConnected();
    void onDisconnected();
    void onReadyRead();
    void onErrorOccurred(QAbstractSocket::SocketError socketError);
    void onTimeout();

private:
    QTcpSocket *tcpSocket;
    QTimer *responseTimer;
    QMutex mutex;
    
    // 连接状态
    bool connected;
    QString serverIp;
    quint16 serverPort;
    quint8 slaveId;
    
    // 协议参数
    quint16 transactionId;
    quint32 responseTimeout;
    bool autoReconnect;
    
    // 当前请求信息
    ModbusRequest currentRequest;
    QByteArray pendingResponse;
    
    // 私有方法
    bool sendRequest(const ModbusRequest &request);
    void processResponse(const QByteArray &response);
    void handleException(quint8 exceptionCode);
    void reconnect();
    
    // 事务ID管理
    quint16 generateTransactionId();
    void resetTransactionId() { transactionId = 0; }
};

#endif // MODBUSTCPCLIENT_H
cpp 复制代码
// modbustcpclient.cpp
#include "modbustcpclient.h"
#include "modbusprotocol.h"
#include <QHostAddress>
#include <QThread>

ModbusTcpClient::ModbusTcpClient(QObject *parent)
    : QObject(parent)
    , connected(false)
    , transactionId(0)
    , responseTimeout(3000)
    , autoReconnect(true)
{
    // 创建TCP套接字
    tcpSocket = new QTcpSocket(this);
    
    // 创建响应定时器
    responseTimer = new QTimer(this);
    responseTimer->setSingleShot(true);
    
    // 连接信号槽
    connect(tcpSocket, &QTcpSocket::connected, this, &ModbusTcpClient::onConnected);
    connect(tcpSocket, &QTcpSocket::disconnected, this, &ModbusTcpClient::onDisconnected);
    connect(tcpSocket, &QTcpSocket::readyRead, this, &ModbusTcpClient::onReadyRead);
    connect(tcpSocket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::errorOccurred),
            this, &ModbusTcpClient::onErrorOccurred);
    connect(responseTimer, &QTimer::timeout, this, &ModbusTcpClient::onTimeout);
}

ModbusTcpClient::~ModbusTcpClient() {
    disconnectFromServer();
}

bool ModbusTcpClient::connectToServer(const QString &ipAddress, quint16 port, quint8 slaveId) {
    QMutexLocker locker(&mutex);
    
    if (connected) {
        emit logMessage("已经连接到服务器");
        return true;
    }
    
    serverIp = ipAddress;
    serverPort = port;
    this->slaveId = slaveId;
    
    emit logMessage(QString("正在连接服务器 %1:%2").arg(ipAddress).arg(port));
    
    tcpSocket->connectToHost(QHostAddress(ipAddress), port);
    
    // 等待连接完成
    if (tcpSocket->waitForConnected(responseTimeout)) {
        return true;
    } else {
        emit logMessage(QString("连接失败: %1").arg(tcpSocket->errorString()));
        return false;
    }
}

void ModbusTcpClient::disconnectFromServer() {
    QMutexLocker locker(&mutex);
    
    if (connected) {
        tcpSocket->disconnectFromHost();
        if (tcpSocket->state() != QAbstractSocket::UnconnectedState) {
            tcpSocket->waitForDisconnected(1000);
        }
        connected = false;
        emit connectionStatusChanged(false);
        emit logMessage("已断开服务器连接");
    }
}

bool ModbusTcpClient::readHoldingRegisters(quint16 startAddress, quint16 quantity) {
    QMutexLocker locker(&mutex);
    
    if (!connected) {
        emit logMessage("未连接到服务器");
        return false;
    }
    
    if (quantity == 0 || quantity > 125) {
        emit logMessage("读取寄存器数量无效 (1-125)");
        return false;
    }
    
    ModbusRequest request;
    request.functionCode = MODBUS_FUNC_READ_HOLDING_REGISTERS;
    request.slaveId = slaveId;
    request.startAddress = startAddress;
    request.quantity = quantity;
    request.transactionId = generateTransactionId();
    
    return sendRequest(request);
}

bool ModbusTcpClient::writeSingleRegister(quint16 address, quint16 value) {
    QMutexLocker locker(&mutex);
    
    if (!connected) {
        emit logMessage("未连接到服务器");
        return false;
    }
    
    ModbusRequest request;
    request.functionCode = MODBUS_FUNC_WRITE_SINGLE_REGISTER;
    request.slaveId = slaveId;
    request.startAddress = address;
    request.values.append(value);
    request.transactionId = generateTransactionId();
    
    return sendRequest(request);
}

bool ModbusTcpClient::sendRequest(const ModbusRequest &request) {
    QByteArray mbapHeader = ModbusProtocol::buildMBAPHeader(request.transactionId, request.slaveId, request);
    QByteArray pdu = ModbusProtocol::buildPDU(request);
    QByteArray fullFrame = mbapHeader + pdu;
    
    currentRequest = request;
    pendingResponse.clear();
    
    // 发送数据
    qint64 bytesWritten = tcpSocket->write(fullFrame);
    if (bytesWritten != fullFrame.size()) {
        emit logMessage(QString("发送数据失败: %1").arg(tcpSocket->errorString()));
        return false;
    }
    
    // 启动响应超时定时器
    responseTimer->start(responseTimeout);
    
    emit logMessage(QString("发送请求: 事务ID=%1, 功能码=0x%2, 地址=%3, 数量=%4")
                    .arg(request.transactionId)
                    .arg(request.functionCode, 2, 16, QChar('0'))
                    .arg(request.startAddress)
                    .arg(request.quantity));
    
    return true;
}

void ModbusTcpClient::onConnected() {
    connected = true;
    resetTransactionId();
    emit connectionStatusChanged(true);
    emit logMessage(QString("成功连接到服务器 %1:%2").arg(serverIp).arg(serverPort));
}

void ModbusTcpClient::onDisconnected() {
    connected = false;
    responseTimer->stop();
    emit connectionStatusChanged(false);
    emit logMessage("服务器连接已断开");
    
    // 自动重连
    if (autoReconnect) {
        QTimer::singleShot(5000, this, &ModbusTcpClient::reconnect);
    }
}

void ModbusTcpClient::onReadyRead() {
    QMutexLocker locker(&mutex);
    
    pendingResponse.append(tcpSocket->readAll());
    
    // 检查是否接收到完整的响应
    if (pendingResponse.size() >= 8) { // MBAP头(7字节) + 功能码(1字节) + 数据
        quint16 expectedLength = ModbusProtocol::getExpectedResponseLength(pendingResponse);
        if (pendingResponse.size() >= expectedLength) {
            responseTimer->stop();
            processResponse(pendingResponse.left(expectedLength));
            pendingResponse.remove(0, expectedLength);
        }
    }
}

void ModbusTcpClient::processResponse(const QByteArray &response) {
    ModbusResponse modbusResponse = ModbusProtocol::parseResponse(response);
    
    if (modbusResponse.isValid) {
        emit logMessage(QString("收到响应: 事务ID=%1, 功能码=0x%2")
                        .arg(modbusResponse.transactionId)
                        .arg(modbusResponse.functionCode, 2, 16, QChar('0')));
        
        // 检查是否为异常响应
        if (modbusResponse.isException) {
            handleException(modbusResponse.exceptionCode);
            return;
        }
        
        // 根据功能码处理响应数据
        switch (modbusResponse.functionCode) {
            case MODBUS_FUNC_READ_COILS:
            case MODBUS_FUNC_READ_DISCRETE_INPUTS:
                emit coilsRead(modbusResponse.coilValues);
                break;
                
            case MODBUS_FUNC_READ_HOLDING_REGISTERS:
            case MODBUS_FUNC_READ_INPUT_REGISTERS:
                emit holdingRegistersRead(modbusResponse.registerValues);
                break;
                
            case MODBUS_FUNC_WRITE_SINGLE_COIL:
            case MODBUS_FUNC_WRITE_SINGLE_REGISTER:
            case MODBUS_FUNC_WRITE_MULTIPLE_COILS:
            case MODBUS_FUNC_WRITE_MULTIPLE_REGISTERS:
                emit writeComplete(true);
                break;
                
            default:
                emit logMessage(QString("未知的功能码: 0x%1").arg(modbusResponse.functionCode, 2, 16, QChar('0')));
                break;
        }
    } else {
        emit logMessage("无效的响应数据");
    }
}

void ModbusTcpClient::handleException(quint8 exceptionCode) {
    QString exceptionMsg = ModbusProtocol::getExceptionMessage(exceptionCode);
    emit logMessage(QString("Modbus异常: %1 (代码: 0x%2)")
                    .arg(exceptionMsg)
                    .arg(exceptionCode, 2, 16, QChar('0')));
}

void ModbusTcpClient::reconnect() {
    if (!connected) {
        emit logMessage("尝试重新连接...");
        connectToServer(serverIp, serverPort, slaveId);
    }
}

quint16 ModbusTcpClient::generateTransactionId() {
    transactionId++;
    if (transactionId > 65535) {
        transactionId = 1;
    }
    return transactionId;
}

void ModbusTcpClient::onErrorOccurred(QAbstractSocket::SocketError socketError) {
    Q_UNUSED(socketError)
    emit logMessage(QString("网络错误: %1").arg(tcpSocket->errorString()));
}

void ModbusTcpClient::onTimeout() {
    emit logMessage("响应超时");
    pendingResponse.clear();
}

2.4 Modbus 协议处理类 (modbusprotocol.h/cpp)

cpp 复制代码
// modbusprotocol.h
#ifndef MODBUSPROTOCOL_H
#define MODBUSPROTOCOL_H

#include <QByteArray>
#include <QVector>
#include <QString>

// Modbus功能码
#define MODBUS_FUNC_READ_COILS                0x01
#define MODBUS_FUNC_READ_DISCRETE_INPUTS      0x02
#define MODBUS_FUNC_READ_HOLDING_REGISTERS    0x03
#define MODBUS_FUNC_READ_INPUT_REGISTERS      0x04
#define MODBUS_FUNC_WRITE_SINGLE_COIL         0x05
#define MODBUS_FUNC_WRITE_SINGLE_REGISTER     0x06
#define MODBUS_FUNC_WRITE_MULTIPLE_COILS      0x0F
#define MODBUS_FUNC_WRITE_MULTIPLE_REGISTERS   0x10

// Modbus异常码
#define MODBUS_EXCEPTION_ILLEGAL_FUNCTION     0x01
#define MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS 0x02
#define MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE   0x03
#define MODBUS_EXCEPTION_SLAVE_DEVICE_FAILURE 0x04
#define MODBUS_EXCEPTION_ACKNOWLEDGE          0x05
#define MODBUS_EXCEPTION_SLAVE_DEVICE_BUSY    0x06

// Modbus请求结构体
struct ModbusRequest {
    quint16 transactionId;
    quint8 slaveId;
    quint8 functionCode;
    quint16 startAddress;
    quint16 quantity;
    QVector<quint16> values;
    QVector<bool> coilValues;
};

// Modbus响应结构体
struct ModbusResponse {
    bool isValid;
    bool isException;
    quint16 transactionId;
    quint8 slaveId;
    quint8 functionCode;
    quint8 exceptionCode;
    QVector<quint16> registerValues;
    QVector<bool> coilValues;
    quint16 byteCount;
};

class ModbusProtocol {
public:
    // MBAP头处理
    static QByteArray buildMBAPHeader(quint16 transactionId, quint8 slaveId, const ModbusRequest &request);
    static quint16 getExpectedResponseLength(const QByteArray &response);
    
    // PDU处理
    static QByteArray buildPDU(const ModbusRequest &request);
    static ModbusResponse parseResponse(const QByteArray &response);
    
    // 异常信息
    static QString getExceptionMessage(quint8 exceptionCode);
    
private:
    static QByteArray encodeRegisterValues(const QVector<quint16> &values);
    static QByteArray encodeCoilValues(const QVector<bool> &values);
    static QVector<quint16> decodeRegisterValues(const QByteArray &data, quint16 byteCount);
    static QVector<bool> decodeCoilValues(const QByteArray &data, quint16 quantity);
};

#endif // MODBUSPROTOCOL_H
cpp 复制代码
// modbusprotocol.cpp
#include "modbusprotocol.h"

QByteArray ModbusProtocol::buildMBAPHeader(quint16 transactionId, quint8 slaveId, const ModbusRequest &request) {
    QByteArray header;
    header.reserve(7);
    
    // 事务标识符 (2字节)
    header.append(static_cast<char>(transactionId >> 8));
    header.append(static_cast<char>(transactionId & 0xFF));
    
    // 协议标识符 (2字节) - 0表示Modbus协议
    header.append(static_cast<char>(0x00));
    header.append(static_cast<char>(0x00));
    
    // 长度字段 (2字节) - 后面字节数
    quint16 length = 1 + 2 + 2; // 功能码(1) + 起始地址(2) + 数量(2)
    if (request.functionCode == MODBUS_FUNC_WRITE_SINGLE_REGISTER ||
        request.functionCode == MODBUS_FUNC_WRITE_SINGLE_COIL) {
        length += 2; // 值(2字节)
    } else if (request.functionCode == MODBUS_FUNC_WRITE_MULTIPLE_REGISTERS ||
               request.functionCode == MODBUS_FUNC_WRITE_MULTIPLE_COILS) {
        length += 2 + 1 + request.values.size() * 2; // 数量(2) + 字节数(1) + 值
    }
    
    header.append(static_cast<char>(length >> 8));
    header.append(static_cast<char>(length & 0xFF));
    
    // 单元标识符 (1字节)
    header.append(static_cast<char>(slaveId));
    
    return header;
}

QByteArray ModbusProtocol::buildPDU(const ModbusRequest &request) {
    QByteArray pdu;
    
    // 功能码
    pdu.append(static_cast<char>(request.functionCode));
    
    // 起始地址
    pdu.append(static_cast<char>(request.startAddress >> 8));
    pdu.append(static_cast<char>(request.startAddress & 0xFF));
    
    // 数量
    pdu.append(static_cast<char>(request.quantity >> 8));
    pdu.append(static_cast<char>(request.quantity & 0xFF));
    
    // 数据(如果有)
    if (request.functionCode == MODBUS_FUNC_WRITE_SINGLE_REGISTER) {
        pdu.append(static_cast<char>(request.values[0] >> 8));
        pdu.append(static_cast<char>(request.values[0] & 0xFF));
    } else if (request.functionCode == MODBUS_FUNC_WRITE_MULTIPLE_REGISTERS) {
        // 字节数
        quint8 byteCount = request.values.size() * 2;
        pdu.append(static_cast<char>(byteCount));
        
        // 寄存器值
        for (quint16 value : request.values) {
            pdu.append(static_cast<char>(value >> 8));
            pdu.append(static_cast<char>(value & 0xFF));
        }
    }
    
    return pdu;
}

ModbusResponse ModbusProtocol::parseResponse(const QByteArray &response) {
    ModbusResponse result;
    result.isValid = false;
    result.isException = false;
    
    if (response.size() < 9) {
        return result;
    }
    
    // 解析MBAP头
    result.transactionId = (static_cast<quint8>(response[0]) << 8) | static_cast<quint8>(response[1]);
    result.slaveId = static_cast<quint8>(response[6]);
    result.functionCode = static_cast<quint8>(response[7]);
    
    // 检查是否为异常响应
    if (result.functionCode & 0x80) {
        result.isException = true;
        result.exceptionCode = static_cast<quint8>(response[8]);
        result.isValid = true;
        return result;
    }
    
    // 正常响应
    quint8 byteCount = static_cast<quint8>(response[8]);
    QByteArray data = response.mid(9, byteCount);
    
    switch (result.functionCode) {
        case MODBUS_FUNC_READ_HOLDING_REGISTERS:
        case MODBUS_FUNC_READ_INPUT_REGISTERS:
            result.registerValues = decodeRegisterValues(data, byteCount);
            break;
            
        case MODBUS_FUNC_READ_COILS:
        case MODBUS_FUNC_READ_DISCRETE_INPUTS:
            result.coilValues = decodeCoilValues(data, result.byteCount);
            break;
    }
    
    result.isValid = true;
    return result;
}

QString ModbusProtocol::getExceptionMessage(quint8 exceptionCode) {
    switch (exceptionCode) {
        case MODBUS_EXCEPTION_ILLEGAL_FUNCTION:
            return "非法功能码";
        case MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS:
            return "非法数据地址";
        case MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE:
            return "非法数据值";
        case MODBUS_EXCEPTION_SLAVE_DEVICE_FAILURE:
            return "从站设备故障";
        case MODBUS_EXCEPTION_ACKNOWLEDGE:
            return "确认";
        case MODBUS_EXCEPTION_SLAVE_DEVICE_BUSY:
            return "从站设备忙";
        default:
            return QString("未知异常 (0x%1)").arg(exceptionCode, 2, 16, QChar('0'));
    }
}

QVector<quint16> ModbusProtocol::decodeRegisterValues(const QByteArray &data, quint16 byteCount) {
    QVector<quint16> values;
    quint16 registerCount = byteCount / 2;
    
    for (quint16 i = 0; i < registerCount; i++) {
        quint16 value = (static_cast<quint8>(data[i * 2]) << 8) | 
                       static_cast<quint8>(data[i * 2 + 1]);
        values.append(value);
    }
    
    return values;
}

quint16 ModbusProtocol::getExpectedResponseLength(const QByteArray &response) {
    if (response.size() < 8) return 0;
    
    quint16 length = (static_cast<quint8>(response[4]) << 8) | static_cast<quint8>(response[5]);
    return length + 6; // MBAP头(6字节) + 长度字段(2字节) + 数据
}

2.5 主程序 (main.cpp)

cpp 复制代码
#include "mainwindow.h"
#include <QApplication>
#include <QTranslator>
#include <QLocale>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    
    // 设置应用程序信息
    QApplication::setApplicationName("Modbus TCP Client");
    QApplication::setApplicationVersion("1.0");
    QApplication::setOrganizationName("Qt Modbus Tools");
    
    // 设置语言
    QTranslator translator;
    const QStringList uiLanguages = QLocale::system().uiLanguages();
    for (const QString &locale : uiLanguages) {
        const QString baseName = "ModbusTCPClient_" + QLocale(locale).name();
        if (translator.load(":/i18n/" + baseName)) {
            a.installTranslator(&translator);
            break;
        }
    }
    
    // 创建并显示主窗口
    MainWindow w;
    w.show();
    
    return a.exec();
}

三、Qt Creator 项目文件

3.1 .pro 文件

qmake 复制代码
QT       += core gui network

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = ModbusTCPClient
TEMPLATE = app

# 源文件
SOURCES += \
    src/main.cpp \
    src/mainwindow.cpp \
    src/modbustcpclient.cpp \
    src/modbusprotocol.cpp

# 头文件
HEADERS += \
    include/mainwindow.h \
    include/modbustcpclient.h \
    include/modbusprotocol.h

# UI文件
FORMS += \
    ui/mainwindow.ui

# 资源文件
RESOURCES += \
    resources/resources.qrc

# 编译配置
CONFIG += c++11
CONFIG += warn_on

# 输出目录
DESTDIR = bin

# 包含路径
INCLUDEPATH += include

# 调试信息
CONFIG(debug, debug|release) {
    CONFIG += console
    DEFINES += QT_DEBUG
}

# 发布版本优化
CONFIG(release, debug|release) {
    DEFINES += QT_NO_DEBUG_OUTPUT
    QMAKE_CXXFLAGS_RELEASE += -O2
}

3.2 CMakeLists.txt

cmake 复制代码
cmake_minimum_required(VERSION 3.16)
project(ModbusTCPClient VERSION 1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Qt5 COMPONENTS Widgets Network REQUIRED)

# 包含目录
include_directories(${PROJECT_SOURCE_DIR}/include)

# 源文件
set(SOURCES
    src/main.cpp
    src/mainwindow.cpp
    src/modbustcpclient.cpp
    src/modbusprotocol.cpp
)

# 头文件
set(HEADERS
    include/mainwindow.h
    include/modbustcpclient.h
    include/modbusprotocol.h
)

# UI文件
set(UIS
    ui/mainwindow.ui
)

# 资源文件
set(RESOURCES
    resources/resources.qrc
)

# 创建可执行文件
add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS} ${UIS} ${RESOURCES})

# 链接库
target_link_libraries(${PROJECT_NAME}
    Qt5::Widgets
    Qt5::Network
)

# 安装配置
install(TARGETS ${PROJECT_NAME}
    RUNTIME DESTINATION bin
)

参考代码 Modbus tcp 通讯源码 QT编写 www.youwenfan.com/contentcsu/60515.html

四、UI 设计文件 (mainwindow.ui)

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>1000</width>
    <height>700</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Modbus TCP 通讯客户端</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QHBoxLayout" name="horizontalLayout">
    
    <!-- 左侧控制面板 -->
    <widget class="QGroupBox" name="groupBox_control">
     <property name="title">
      <string>连接配置</string>
     </property>
     <layout class="QFormLayout" name="formLayout">
      <item row="0" column="0">
       <widget class="QLabel" name="label_ip">
        <property name="text">
         <string>服务器IP:</string>
        </property>
       </widget>
      </item>
      <item row="0" column="1">
       <widget class="QLineEdit" name="lineEdit_ip">
        <property name="text">
         <string>192.168.1.100</string>
        </property>
       </widget>
      </item>
      <item row="1" column="0">
       <widget class="QLabel" name="label_port">
        <property name="text">
         <string>端口:</string>
        </property>
       </widget>
      </item>
      <item row="1" column="1">
       <widget class="QSpinBox" name="spinBox_port">
        <property name="maximum">
         <number>65535</number>
        </property>
        <property name="value">
         <number>502</number>
        </property>
       </widget>
      </item>
      <item row="2" column="0">
       <widget class="QLabel" name="label_slaveId">
        <property name="text">
         <string>从站ID:</string>
        </property>
       </widget>
      </item>
      <item row="2" column="1">
       <widget class="QSpinBox" name="spinBox_slaveId">
        <property name="maximum">
         <number>247</number>
        </property>
        <property name="value">
         <number>1</number>
        </property>
       </widget>
      </item>
      <item row="3" column="0" colspan="2">
       <widget class="QPushButton" name="pushButton_connect">
        <property name="text">
         <string>连接</string>
        </property>
       </widget>
      </item>
      <item row="4" column="0" colspan="2">
       <widget class="QPushButton" name="pushButton_disconnect">
        <property name="text">
         <string>断开连接</string>
        </property>
        <property name="enabled">
         <bool>false</bool>
        </property>
       </widget>
      </item>
     </layout>
    </widget>
    
    <!-- 右侧数据显示 -->
    <widget class="QTabWidget" name="tabWidget">
     <property name="currentIndex">
      <number>0</number>
     </property>
     <widget class="QWidget" name="tab_registers">
      <attribute name="title">
       <string>寄存器</string>
      </attribute>
      <layout class="QVBoxLayout" name="verticalLayout_2">
       <widget class="QTableWidget" name="tableWidget_holdingRegisters"/>
      </layout>
     </widget>
     <widget class="QWidget" name="tab_coils">
      <attribute name="title">
       <string>线圈</string>
      </attribute>
      <layout class="QVBoxLayout" name="verticalLayout_3">
       <widget class="QTableWidget" name="tableWidget_coils"/>
      </layout>
     </widget>
     <widget class="QWidget" name="tab_log">
      <attribute name="title">
       <string>日志</string>
      </attribute>
      <layout class="QVBoxLayout" name="verticalLayout_4">
       <widget class="QTextEdit" name="textEdit_log"/>
      </layout>
     </widget>
    </widget>
   </layout>
  </widget>
  <widget class="QStatusBar" name="statusbar">
   <property name="showMessage">
    <string>就绪</string>
   </property>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

五、功能扩展建议

5.1 高级功能

  1. 数据记录:添加SQLite数据库存储历史数据
  2. 图表显示:集成QCustomPlot显示实时趋势图
  3. 报警功能:设置阈值报警和邮件通知
  4. 多设备支持:同时连接多个Modbus服务器

5.2 性能优化

  1. 异步处理:使用QThread处理耗时操作
  2. 连接池:支持多个并发连接
  3. 数据缓存:减少重复请求
  4. 压缩传输:支持数据压缩

5.3 用户体验

  1. 主题切换:支持深色/浅色主题
  2. 多语言:支持中英文切换
  3. 快捷键:添加常用快捷键
  4. 帮助文档:集成在线帮助

六、编译和运行

bash 复制代码
# 使用qmake编译
qmake ModbusTCPClient.pro
make

# 使用CMake编译
mkdir build
cd build
cmake ..
make

# 运行程序
./ModbusTCPClient
相关推荐
龙仔7251 小时前
【麒麟V10系统 SSH自动防暴力破解(失败3次封IP)完整配置笔记】
笔记·tcp/ip·ssh·防攻击
sz4972385992 小时前
双网卡通过路由器实现外网和内网同时上网
网络·tcp/ip·智能路由器·hmi
Hua-Jay2 小时前
OpenCV联合C++/Qt 学习笔记(十六)----图像细化、轮廓检测、轮廓信息统计及轮廓外接多边形
c++·笔记·qt·opencv·学习·计算机视觉
minji...2 小时前
Linux 网络基础之UDP协议(四)传输层协议 UDP,再谈端口号,UDP 特点
linux·服务器·开发语言·网络·c++·tcp/ip·udp
沧州刺史2 小时前
有域名但是没有带公网IP的服务器,怎么能够让服务通过域名暴露出去?
服务器·网络协议·tcp/ip·cloudflared
lilong(DLC)3 小时前
Qt信号槽在异步连接时需要将参数进行复制吗?
开发语言·qt
渣渣灰95873 小时前
VSCode开发环境开发Qt程序
ide·vscode·qt
读书札记20223 小时前
Qt Creator 调试报错:Unable to create a debugging engine.
开发语言·qt
透明的玻璃杯3 小时前
Qt Creator + Windows + Protobuf 最优方案(Mqqt通讯采用的方式)
开发语言·windows·qt