Modbus TCP同步通信方式实现异步级效率

同步方式实现异步级效率:Modbus TCP 深度方案与 Qt 实现

深度分析:为何同步不可能?为何又可能?关键瓶颈在哪?如何逼近异步性能?完整 Qt 代码实现。


零、一个反直觉的真相

大多数开发者认为:

异步 = 高性能、高并发

同步 = 低性能、阻塞、卡顿

这个认知在 Modbus 场景下是错的--或者说,过于简化了。

让我们从第一性原理出发,看看真正的瓶颈是什么。


一、性能瓶颈的本质分析

1.1 Modbus 请求的生命周期

复制代码
──── 时间轴 ──────────────────────────────────────────────►

                          T响应
├── T请求发送 ──┤├── 网络传输 ──┤├── 设备处理 ──┤├── 网络返回 ──┤
                   ▲              ▲              ▲
                CPU空闲         CPU空闲         CPU空闲

核心观察: 在一个同步请求的完整生命周期中,CPU 绝大多数时间在空闲等待(IO 等待)。这正是异步能提高效率的根本原因--它利用这些空闲时间去处理其他任务。

1.2 Modbus 通讯各环节耗时分布(实测参考值)

阶段 局域网 (100Mbps) 工业现场总线 4G/5G 远程
请求序列化 ~0.01ms ~0.01ms ~0.01ms
网络传输 (往返) ~0.5-2ms ~1-5ms ~20-100ms
设备处理 ~1-20ms ~5-50ms ~5-50ms
CPU 有效占比 < 1% < 0.1% < 0.01%

关键结论: 对于 Modbus 通讯,90%-99.99% 的时间 CPU 在等待 IO。异步和同步的效率差异,本质上是对这段"空白时间"的利用方式不同。

1.3 异步的高效模式

复制代码
时间  ├─ 请求A ──┤├─ 请求B ──┤├─ 请求C ──┤├─ 请求D ──┤  ← 单线程
      ████████░░░░████████░░░░████████░░░░████████░░░░
      ↑ 发送    ↑ 收A    ↑ 收B    ↑ 收C    ↑ 收D
      不等待,立即发B    不等待,立即发C ...

异步核心策略:在等待 A 的响应时,不阻塞线程,而是转而发送 B、C、D......

1.4 同步的方式也可以做到这一点

复制代码
                  线程1 ├─── 请求A ────┤
                  线程2 ├─── 请求B ────┤
                  线程3 ├─── 请求C ────┤
                  线程4 ├─── 请求D ────┤

同步的核心策略:N 个线程 × 各自独立阻塞 来替代异步的 1 个线程 × 轮转调度

当 N 足够大时,流水线填满,吞吐量逼近甚至超过单线程异步。


二、专业分析:同步逼近异步效率的充分必要条件

2.1 公式推演

设:

  • T_req = 发送请求耗时
  • T_resp = 等待响应耗时(IO 等待)
  • T_proc = 收到响应后的处理耗时
  • T_total = T_req + T_resp + T_proc = 单个请求周期

单线程异步吞吐量(理想流水线):

复制代码
Async_throughput ≈ 1 / max(T_req + T_proc)
// 注:T_resp 被完全流水线化了
// 极限情况:T_proc ≈ 0 时 → 1/T_req

N 线程同步吞吐量:

复制代码
Sync(N)_throughput ≈ N / T_total

令二者相等:

复制代码
N / T_total = 1 / max(T_req + T_proc)
N = T_total / max(T_req + T_proc)

代入典型局域网值(T_req=0.01ms, T_resp=2ms, T_proc=0.1ms):
N = (0.01 + 2 + 0.1) / max(0.01 + 0.1) = 2.11 / 0.11 ≈ 19.2

结论:约 20 个同步线程即可在局域网环境下达到单线程异步的吞吐量。

2.2 进一步优化--多连接并行

python 复制代码
N_needed = T_total / max(T_req + T_proc)

当单个 TCP 连接成为瓶颈时(如设备端按顺序处理请求),可拆分为多个连接:

复制代码
多连接同步:
    连接1线程 ├─── 请求A ──┤├─── 请求E ──┤
    连接2线程 ├─── 请求B ──┤├─── 请求F ──┤
    连接3线程 ├─── 请求C ──┤├─── 请求G ──┤
    连接4线程 ├─── 请求D ──┤├─── 请求H ──┤

吞吐量 = Min(N_connections × device_throughput, N_threads / T_total)

2.3 真正的制约因素

因素 影响 同步方案应对策略
CPU 核数 线程不能超过 CPU 核数太多(上下文切换开销) 使用 N = CPU核数 × 2 的经验值
设备处理能力 单个设备串行处理请求的极限 多个 TCP 连接 + 负载均衡
网络带宽 带宽瓶颈(Modbus 数据量小,通常不构成问题) 忽略
响应解析耗时 处理数据需要 CPU 时间 合并响应批量处理,减少每请求开销
线程上下文切换 线程过多时性能下降 使用 QThreadPool 自适应管理

2.4 决策矩阵:什么时候同步方案可行?

场景 同步线程池 异步IO 推荐方案
单设备,局域网,< 10 Hz 轮询 ✅ 随便写 ✅ 随便写 同步单线程
单设备,局域网,100 Hz 轮询 ✅ 3-5 线程即可 ✅ 优秀 异步,或同步小线程池
多设备(5+),局域网,各 50 Hz ✅ 线程池 ✅ 优秀 异步更简洁
多设备,远程(4G/5G),延迟 > 50ms ✅ 线程池 + 多连接 ✅ 优秀 均可行
极高并发(50+ 设备) ⚠️ 线程数过多需谨慎 ✅ 最佳 异步为主,同步辅助

三、Qt 代码实现:高性能同步 Modbus 引擎

3.1 设计架构

复制代码
┌──────────────────────────────────────────────────────┐
│                 业务调用层 (主线程)                     │
│  不阻塞,通过信号槽接收结果                             │
│  requestRead(addr, count) → 入队                     │
└──────────────────┬───────────────────────────────────┘
                   │ 线程安全队列 (QQueue + QMutex)
┌──────────────────▼───────────────────────────────────┐
│             请求调度器 (Dispatcher)                    │
│  维护请求队列,分发给空闲的工作线程                     │
│  支持请求合并、优先级调度                              │
└──────┬──────────────┬──────────────┬─────────────────┘
       │              │              │
   ┌───▼───┐     ┌───▼───┐     ┌───▼───┐
   │Worker1│     │Worker2│     │Worker3│  ← QThreadPool
   │ 连接1  │     │ 连接2  │     │ 连接3  │  ← 各持独立连接
   └───┬───┘     └───┬───┘     └───┬───┘
       │              │              │
   ┌───▼──────────────▼──────────────▼───┐
   │        Modbus TCP 设备               │
   │      (支持 N 个并发连接)              │
   └──────────────────────────────────────┘

3.2 线程安全的请求队列

cpp 复制代码
// request_queue.h
#pragma once

#include <QQueue>
#include <QMutex>
#include <QWaitCondition>
#include <QSharedPointer>
#include <QModbusDataUnit>

/// 一个 Modbus 请求的完整描述
struct ModbusRequest {
    int requestId;              // 唯一标识,用于关联结果
    int deviceId;               // 设备/从站地址 (Unit ID)
    int functionCode;           // 功能码 (预留,当前通过 registerType 识别)
    QModbusDataUnit::RegisterType registerType;  // Coils / HoldingRegisters / ...
    int startAddress;           // 起始地址
    int count;                  // 数量
    int priority;               // 优先级 (数值越小优先级越高)
    qint64 enqueueTimestamp;    // 入队时间戳

    // 以下用于写请求
    QVector<quint16> writeValues;
    bool isWriteRequest = false;
};

/// 线程安全的请求-结果队列
class ThreadSafeRequestQueue {
public:
    ThreadSafeRequestQueue() = default;

    /// 入队一个请求(生产者:主线程调用)
    void enqueue(const ModbusRequest &req) {
        QMutexLocker lock(&m_mutex);
        ModbusRequest copy = req;
        copy.enqueueTimestamp = QDateTime::currentMSecsSinceEpoch();
        m_queue.enqueue(copy);
        m_notEmpty.wakeOne(); // 唤醒一个等待的工作线程
    }

    /// 出队一个请求(消费者:工作线程调用)
    /// 如果队列为空,阻塞等待最多 timeoutMs 毫秒
    /// @return true 成功出队,false 超时
    bool dequeue(ModbusRequest &out, int timeoutMs = 5000) {
        QMutexLocker lock(&m_mutex);
        while (m_queue.isEmpty()) {
            if (!m_notEmpty.wait(&m_mutex, timeoutMs)) {
                return false; // 超时
            }
        }

        // 优先级调度:取最高优先级的请求
        // 对于同优先级的,保持 FIFO
        int bestIdx = 0;
        int bestPriority = m_queue[0].priority;
        for (int i = 1; i < m_queue.size(); ++i) {
            if (m_queue[i].priority < bestPriority) {
                bestPriority = m_queue[i].priority;
                bestIdx = i;
            }
        }
        out = m_queue.takeAt(bestIdx);
        return true;
    }

    /// 批量入队(用于请求合并优化)
    void enqueueBatch(const QList<ModbusRequest> &requests) {
        QMutexLocker lock(&m_mutex);
        for (const auto &req : requests) {
            ModbusRequest copy = req;
            copy.enqueueTimestamp = QDateTime::currentMSecsSinceEpoch();
            m_queue.enqueue(copy);
        }
        m_notEmpty.wakeAll(); // 唤醒所有等待线程
    }

    /// 清空队列(断线时调用)
    void clear() {
        QMutexLocker lock(&m_mutex);
        m_queue.clear();
    }

    int size() const {
        QMutexLocker lock(&m_mutex);
        return m_queue.size();
    }

private:
    mutable QMutex m_mutex;
    QQueue<ModbusRequest> m_queue;
    QWaitCondition m_notEmpty;
};

3.3 工作线程:每个线程持有独立连接,同步阻塞收发

cpp 复制代码
// sync_worker.h
#pragma once

#include <QObject>
#include <QThread>
#include <QModbusTcpClient>
#include <QModbusDataUnit>
#include <QModbusReply>
#include <QElapsedTimer>
#include <QDebug>
#include "request_queue.h"

/// Modbus 同步工作线程
///
/// 核心设计思想:
/// 每个 Worker 持有一个独立的 QModbusTcpClient 连接,
/// 在独立线程中以同步方式(QEventLoop 阻塞)执行请求。
/// 多个 Worker 并行运行,达到类似异步的流水线效果。
///
class SyncModbusWorker : public QObject {
    Q_OBJECT
public:
    /// @param workerId      工作线程编号,用于日志
    /// @param host          Modbus TCP 服务器地址
    /// @param port          端口 (默认 502)
    /// @param timeoutMs     单次请求超时时间
    /// @param retryCount    超时重试次数
    explicit SyncModbusWorker(int workerId,
                              const QString &host,
                              quint16 port = 502,
                              int timeoutMs = 3000,
                              int retryCount = 2,
                              QObject *parent = nullptr)
        : QObject(parent)
        , m_workerId(workerId)
        , m_host(host)
        , m_port(port)
        , m_timeoutMs(timeoutMs)
        , m_maxRetries(retryCount)
    {
        // Worker 对象会被 moveToThread,因此 m_client
        // 需要在工作线程的构造函数或槽函数中创建
    }

    ~SyncModbusWorker() {
        disconnectFromDevice();
    }

    /// 关联请求队列(必须在使用前调用)
    void setQueue(std::shared_ptr<ThreadSafeRequestQueue> queue) {
        m_queue = queue;
    }

public slots:
    /// 在工作线程中初始化连接(需通过信号槽触发,确保在正确线程执行)
    bool connectToDevice() {
        if (m_client) {
            m_client->disconnectFromHost();
            m_client->deleteLater();
            m_client = nullptr;
        }

        m_client = new QModbusTcpClient(this);
        m_client->setTimeout(m_timeoutMs);
        m_client->setNumberOfRetries(m_maxRetries);

        qDebug() << "[Worker" << m_workerId << "] 连接" << m_host << ":" << m_port;
        return m_client->connectToHost(m_host, m_port);
    }

    /// 断开连接
    void disconnectFromDevice() {
        if (m_client) {
            m_client->disconnectFromHost();
            m_client->deleteLater();
            m_client = nullptr;
        }
    }

    /// 工作线程主循环--从队列取请求,同步执行,发出结果信号
    ///
    /// 这是核心方法:在一个无限循环中不断从队列取请求,
    /// 执行同步 Modbus 操作,发出结果信号。
    /// 当收到停止信号时退出循环。
    void startWorking() {
        if (!m_client) {
            emit workerError(m_workerId, "客户端未初始化");
            return;
        }

        qDebug() << "[Worker" << m_workerId << "] 开始工作";

        // 等待连接建立
        if (m_client->state() != QModbusDevice::ConnectedState) {
            if (!m_client->connectToHost(m_host, m_port)) {
                emit workerError(m_workerId, "连接失败");
                return;
            }
            // 同步等待连接完成(最多 3 秒)
            if (!waitForConnected(3000)) {
                emit workerError(m_workerId, "连接超时");
                return;
            }
        }

        m_running = true;
        m_stats.reset();

        while (m_running) {
            // 1. 从队列取出一个请求(阻塞式,最多等 1 秒以便检查停止信号)
            ModbusRequest req;
            if (!m_queue || !m_queue->dequeue(req, 1000)) {
                continue; // 超时重试(同时可响应停止信号)
            }

            // 2. 执行请求并测量耗时
            m_stats.totalRequests++;
            m_stats.lastRequestTime = QDateTime::currentMSecsSinceEpoch();

            QElapsedTimer timer;
            timer.start();

            bool success = executeRequest(req);

            qint64 elapsed = timer.elapsed();
            m_stats.totalTime += elapsed;

            if (success) {
                m_stats.successCount++;
                qDebug() << "[Worker" << m_workerId << "] 完成请求 #" << req.requestId
                         << "耗时:" << elapsed << "ms";
            } else {
                m_stats.failCount++;
                qWarning() << "[Worker" << m_workerId << "] 请求 #" << req.requestId << "失败";
            }
        }

        qDebug() << "[Worker" << m_workerId << "] 停止工作";
        emit workerFinished(m_workerId);
    }

    /// 停止工作循环
    void stop() {
        m_running = false;
    }

signals:
    /// 读取线圈的结果
    /// @param workerId  工作线程编号
    /// @param requestId 请求 ID
    /// @param addr      起始地址
    /// @param values    线圈值列表
    void coilsRead(int workerId, int requestId, int addr, QVector<bool> values);

    /// 读取寄存器的结果
    void holdingRegistersRead(int workerId, int requestId, int addr, QVector<quint16> values);

    /// 写操作完成
    void writeCompleted(int workerId, int requestId, bool success);

    /// 协议异常(异常码)
    void protocolException(int workerId, int requestId, quint8 exceptionCode);

    /// 工作线程发生错误
    void workerError(int workerId, const QString &error);

    /// 工作线程结束
    void workerFinished(int workerId);

public:
    /// 统计信息
    struct WorkerStats {
        qint64 totalRequests = 0;
        qint64 successCount = 0;
        qint64 failCount = 0;
        qint64 totalTime = 0;       // 所有请求总耗时 (ms)
        qint64 lastRequestTime = 0; // 上次请求时间戳

        void reset() {
            totalRequests = 0;
            successCount = 0;
            failCount = 0;
            totalTime = 0;
            lastRequestTime = 0;
        }

        double avgLatencyMs() const {
            return totalRequests > 0 ? (double)totalTime / totalRequests : 0.0;
        }

        double throughputPerSec() const {
            if (totalTime == 0) return 0.0;
            return (double)totalRequests / (totalTime / 1000.0);
        }
    };

    WorkerStats stats() const { return m_stats; }

private:
    /// 同步方式执行一个 Modbus 请求
    /// 使用 QEventLoop 阻塞当前线程直到响应返回或超时
    /// @return true 表示成功
    bool executeRequest(const ModbusRequest &req) {
        if (!m_client || m_client->state() != QModbusDevice::ConnectedState) {
            // 断线自动重连
            qWarning() << "[Worker" << m_workerId << "] 连接断开,尝试重连...";
            if (!m_client || !m_client->connectToHost(m_host, m_port)) {
                return false;
            }
            if (!waitForConnected(5000)) {
                return false;
            }
        }

        if (req.isWriteRequest) {
            return executeWriteRequest(req);
        } else {
            return executeReadRequest(req);
        }
    }

    /// 同步执行读请求
    bool executeReadRequest(const ModbusRequest &req) {
        QModbusDataUnit readUnit(req.registerType, req.startAddress, req.count);

        // 发送请求
        QModbusReply *reply = m_client->sendReadRequest(readUnit, req.deviceId);
        if (!reply) {
            return false;
        }

        // ─── 核心技巧:用 QEventLoop 实现同步等待 ───
        // 这不比异步"低级",它只是将异步信号槽封装为同步语义。
        // 在工作线程中运行,不阻塞主线程。
        QEventLoop loop;
        QTimer timeoutGuard;
        timeoutGuard.setSingleShot(true);

        // 方案 A: 收到响应 → 退出事件循环
        connect(reply, &QModbusReply::finished, &loop, &QEventLoop::quit);
        // 方案 B: 超时保护 → 退出事件循环
        connect(&timeoutGuard, &QTimer::timeout, &loop, &QEventLoop::quit);

        timeoutGuard.start(m_timeoutMs * (m_maxRetries + 1));
        loop.exec();  // ★ 在此处阻塞,但 CPU 可调度其他线程

        // 检查结果
        bool success = false;
        if (reply->isFinished() && reply->error() == QModbusDevice::NoError) {
            const QModbusDataUnit result = reply->result();
            success = true;

            // 根据寄存器类型发出对应的信号
            switch (req.registerType) {
            case QModbusDataUnit::Coils: {
                QVector<bool> coils;
                for (quint16 v : result.values()) {
                    coils.append(v != 0);
                }
                emit coilsRead(m_workerId, req.requestId, req.startAddress, coils);
                break;
            }
            case QModbusDataUnit::HoldingRegisters: {
                QVector<quint16> regs = result.values();
                emit holdingRegistersRead(m_workerId, req.requestId,
                                          req.startAddress, regs);
                break;
            }
            default:
                break;
            }
        } else if (reply->isFinished() && reply->error() == QModbusDevice::ProtocolError) {
            quint8 exCode = reply->rawResult().data().at(0);
            emit protocolException(m_workerId, req.requestId, exCode);
        }

        reply->deleteLater();
        return success;
    }

    /// 同步执行写请求
    bool executeWriteRequest(const ModbusRequest &req) {
        QModbusDataUnit writeUnit(req.registerType, req.startAddress,
                                  req.writeValues.size());
        for (int i = 0; i < req.writeValues.size(); ++i) {
            writeUnit.setValue(i, req.writeValues[i]);
        }

        QModbusReply *reply = m_client->sendWriteRequest(writeUnit, req.deviceId);
        if (!reply) return false;

        QEventLoop loop;
        QTimer timeoutGuard;
        timeoutGuard.setSingleShot(true);

        connect(reply, &QModbusReply::finished, &loop, &QEventLoop::quit);
        connect(&timeoutGuard, &QTimer::timeout, &loop, &QEventLoop::quit);

        timeoutGuard.start(m_timeoutMs * (m_maxRetries + 1));
        loop.exec();

        bool success = (reply->isFinished() && reply->error() == QModbusDevice::NoError);
        if (!success && reply->isFinished() && reply->error() == QModbusDevice::ProtocolError) {
            quint8 exCode = reply->rawResult().data().at(0);
            emit protocolException(m_workerId, req.requestId, exCode);
        }

        reply->deleteLater();
        emit writeCompleted(m_workerId, req.requestId, success);
        return success;
    }

    /// 同步等待连接建立
    bool waitForConnected(int timeoutMs) {
        if (!m_client) return false;

        // 通过嵌套事件循环等待连接信号
        QEventLoop loop;
        QTimer timer;
        timer.setSingleShot(true);

        connect(m_client, &QModbusClient::stateChanged, &loop, [&](QModbusDevice::State state) {
            if (state == QModbusDevice::ConnectedState) {
                loop.quit();
            }
        });
        connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);

        timer.start(timeoutMs);
        if (m_client->state() != QModbusDevice::ConnectedState) {
            loop.exec();
        }

        return m_client->state() == QModbusDevice::ConnectedState;
    }

    int m_workerId;
    QString m_host;
    quint16 m_port;
    int m_timeoutMs;
    int m_maxRetries;

    QModbusTcpClient *m_client = nullptr;
    std::shared_ptr<ThreadSafeRequestQueue> m_queue;
    volatile bool m_running = false;
    WorkerStats m_stats;
};

3.4 线程池管理器--自动缩放的引擎

cpp 复制代码
// sync_engine.h
#pragma once

#include <QObject>
#include <QThread>
#include <QList>
#include <QThreadPool>
#include <QAtomicInt>
#include <memory>
#include "request_queue.h"
#include "sync_worker.h"

/// 高性能同步 Modbus 引擎
///
/// 使用线程池 + 多连接同步阻塞 Worker 实现高吞吐量。
/// 设计目标:以同步编程的简单性,达到接近异步的性能。
///
/// 关键优化策略:
/// 1. 多线程并行(每个 Worker 一个线程,独立连接)
/// 2. 请求合并(相邻地址的读请求合并为一个大请求)
/// 3. 优先级调度(高优先级请求被优先处理)
/// 4. 自动重连(连接断开时自动恢复)
/// 5. 统计监控(实时吞吐量、延迟)
///
class SyncModbusEngine : public QObject {
    Q_OBJECT
public:
    /// @param host          Modbus 设备地址
    /// @param port          TCP 端口
    /// @param workerCount   工作线程数(建议 CPU 核数 × 1.5 ~ 2)
    /// @param timeoutMs     单请求超时(毫秒)
    explicit SyncModbusEngine(const QString &host,
                              quint16 port = 502,
                              int workerCount = 4,
                              int timeoutMs = 3000,
                              QObject *parent = nullptr)
        : QObject(parent)
        , m_host(host)
        , m_port(port)
        , m_workerCount(workerCount)
        , m_timeoutMs(timeoutMs)
    {
        m_queue = std::make_shared<ThreadSafeRequestQueue>();
        m_nextRequestId.storeRelaxed(1);
    }

    ~SyncModbusEngine() {
        stop();
    }

    /// 启动引擎:创建并启动所有工作线程
    void start() {
        if (m_running) return;
        m_running = true;

        // 选择最佳线程数(如果未指定)
        if (m_workerCount <= 0) {
            m_workerCount = QThread::idealThreadCount() * 2;
            if (m_workerCount < 2) m_workerCount = 2;
            if (m_workerCount > 16) m_workerCount = 16; // 上限保护
        }

        qDebug() << "[Engines] 启动" << m_workerCount << "个同步工作线程";
        qDebug() << "[Engines] 目标设备" << m_host << ":" << m_port;

        for (int i = 0; i < m_workerCount; ++i) {
            auto *worker = new SyncModbusWorker(i, m_host, m_port, m_timeoutMs, 2);
            worker->setQueue(m_queue);

            auto *thread = new QThread(this);
            worker->moveToThread(thread);

            // 启动时连接
            connect(thread, &QThread::started, worker, [worker]() {
                worker->connectToDevice();
                worker->startWorking();
            });

            // 清理
            connect(worker, &SyncModbusWorker::workerFinished, this, [this, worker, thread](int) {
                thread->quit();
                thread->wait();
                worker->deleteLater();
                thread->deleteLater();
            });

            // 转发结果信号
            connect(worker, &SyncModbusWorker::coilsRead,
                    this, &SyncModbusEngine::coilsRead);
            connect(worker, &SyncModbusWorker::holdingRegistersRead,
                    this, &SyncModbusEngine::holdingRegistersRead);
            connect(worker, &SyncModbusWorker::writeCompleted,
                    this, &SyncModbusEngine::writeCompleted);
            connect(worker, &SyncModbusWorker::protocolException,
                    this, &SyncModbusEngine::protocolException);
            connect(worker, &SyncModbusWorker::workerError,
                    this, &SyncModbusEngine::workerError);

            m_workers.append(worker);
            m_threads.append(thread);
            thread->start();
        }
    }

    /// 停止引擎
    void stop() {
        if (!m_running) return;
        m_running = false;

        // 停止所有工作线程
        for (auto *worker : m_workers) {
            worker->stop();
        }

        // 唤醒队列(防止线程在 dequeue 中阻塞)
        // 入队一个空的停止请求
        for (int i = 0; i < m_workers.size(); ++i) {
            ModbusRequest stopReq;
            stopReq.requestId = -1;
            stopReq.priority = -1; // 最高优先级
            m_queue->enqueue(stopReq);
        }

        // 等待线程结束
        for (auto *thread : m_threads) {
            thread->quit();
            thread->wait(3000);
        }

        m_workers.clear();
        m_threads.clear();
        m_queue->clear();
    }

    // ─── 公开 API ─────────────────────────────────────

    /// 读取线圈(异步风格调用,但内部由同步工作线程执行)
    /// @return requestId 用于跟踪请求
    int readCoils(int addr, int count, int unitId = 1, int priority = 0) {
        ModbusRequest req = makeBaseRequest(unitId, priority);
        req.registerType = QModbusDataUnit::Coils;
        req.startAddress = addr;
        req.count = count;
        req.isWriteRequest = false;
        m_queue->enqueue(req);
        return req.requestId;
    }

    /// 读取保持寄存器
    int readHoldingRegisters(int addr, int count, int unitId = 1, int priority = 0) {
        ModbusRequest req = makeBaseRequest(unitId, priority);
        req.registerType = QModbusDataUnit::HoldingRegisters;
        req.startAddress = addr;
        req.count = count;
        req.isWriteRequest = false;
        m_queue->enqueue(req);
        return req.requestId;
    }

    /// 写单个线圈
    int writeCoil(int addr, bool value, int unitId = 1, int priority = 0) {
        ModbusRequest req = makeBaseRequest(unitId, priority);
        req.registerType = QModbusDataUnit::Coils;
        req.startAddress = addr;
        req.count = 1;
        req.isWriteRequest = true;
        req.writeValues = { value ? 0xFF00u : 0x0000u };
        m_queue->enqueue(req);
        return req.requestId;
    }

    /// 写单个寄存器
    int writeRegister(int addr, quint16 value, int unitId = 1, int priority = 0) {
        ModbusRequest req = makeBaseRequest(unitId, priority);
        req.registerType = QModbusDataUnit::HoldingRegisters;
        req.startAddress = addr;
        req.count = 1;
        req.isWriteRequest = true;
        req.writeValues = { value };
        m_queue->enqueue(req);
        return req.requestId;
    }

    /// 批量读取(请求合并优化)
    ///
    /// 自动将多个连续地址的读取合并为一个大请求,减少网络开销。
    /// 适用于轮询场景中读取一组连续的寄存器。
    ///
    /// @param addrRanges {起始地址, 数量} 列表
    QList<int> batchRead(const QVector<QPair<int, int>> &addrRanges,
                         QModbusDataUnit::RegisterType type =
                             QModbusDataUnit::HoldingRegisters,
                         int unitId = 1) {
        QList<int> requestIds;

        // 请求合并:将相邻的地址区间合并
        // 例如:[0,5] + [5,3] → [0,8] (合并条件:endA == startB)
        QVector<QPair<int, int>> merged;
        auto sorted = addrRanges;
        std::sort(sorted.begin(), sorted.end());

        for (const auto &range : sorted) {
            if (merged.isEmpty()) {
                merged.append(range);
                continue;
            }
            auto &last = merged.last();
            int lastEnd = last.first + last.second;
            if (lastEnd >= range.first) {
                // 相邻或有重叠 → 合并
                int newEnd = qMax(lastEnd, range.first + range.second);
                last.second = newEnd - last.first;
                // 保护:不超过单次请求上限(125 寄存器 / 2000 线圈)
                if (type == QModbusDataUnit::HoldingRegisters && last.second > 125) {
                    // 拆分:保持前面的,后面的作为新区间
                    QPair<int, int> overflow(range.first + 125,
                                             last.second - 125);
                    last.second = 125;
                    merged.append(overflow);
                }
            } else {
                merged.append(range);
            }
        }

        // 入队合并后的请求
        QList<ModbusRequest> batchReqs;
        for (const auto &range : merged) {
            ModbusRequest req = makeBaseRequest(unitId, 0);
            req.registerType = type;
            req.startAddress = range.first;
            req.count = range.second;
            req.isWriteRequest = false;
            batchReqs.append(req);
            requestIds.append(req.requestId);
        }

        m_queue->enqueueBatch(batchReqs);
        return requestIds;
    }

    /// 获取统计信息(所有 Worker 汇总)
    struct EngineStats {
        qint64 totalRequests = 0;
        qint64 successCount = 0;
        qint64 failCount = 0;
        double avgLatencyMs = 0.0;
        double throughputPerSec = 0.0;
        int workerCount = 0;
        int queueSize = 0;

        QString toString() const {
            return QString("Engine Stats [%1 workers]:\n"
                           "  总请求: %2\n"
                           "  成功: %3\n"
                           "  失败: %4\n"
                           "  平均延迟: %5 ms\n"
                           "  吞吐量: %6 req/s\n"
                           "  队列积压: %7\n")
                .arg(workerCount)
                .arg(totalRequests)
                .arg(successCount)
                .arg(failCount)
                .arg(avgLatencyMs, 0, 'f', 2)
                .arg(throughputPerSec, 0, 'f', 1)
                .arg(queueSize);
        }
    };

    EngineStats getStats() const {
        EngineStats stats;
        stats.workerCount = m_workers.size();
        stats.queueSize = m_queue->size();

        for (const auto *worker : m_workers) {
            auto wStats = worker->stats();
            stats.totalRequests += wStats.totalRequests;
            stats.successCount += wStats.successCount;
            stats.failCount += wStats.failCount;
            stats.avgLatencyMs += wStats.avgLatencyMs();
            stats.throughputPerSec += wStats.throughputPerSec();
        }

        if (m_workers.size() > 0) {
            stats.avgLatencyMs /= m_workers.size();
        }
        return stats;
    }

signals:
    // 转发自工作线程的信号(对上层透明)
    void coilsRead(int workerId, int requestId, int addr, QVector<bool> values);
    void holdingRegistersRead(int workerId, int requestId, int addr, QVector<quint16> values); 
    void writeCompleted(int workerId, int requestId, bool success); 
    void protocolException(int workerId, int requestId, quint8 exceptionCode);
    void workerError(int workerId, const QString &error);

private:
    ModbusRequest makeBaseRequest(int unitId, int priority) {
        ModbusRequest req;
        req.requestId = m_nextRequestId.fetchAndAddRelaxed(1);
        req.deviceId = unitId;
        req.priority = priority;
        return req;
    }

    QString m_host;
    quint16 m_port;
    int m_workerCount;
    int m_timeoutMs;
    volatile bool m_running = false;

    std::shared_ptr<ThreadSafeRequestQueue> m_queue;
    QList<SyncModbusWorker *> m_workers;
    QList<QThread *> m_threads;
    QAtomicInt m_nextRequestId;
};

3.5 使用示例

cpp 复制代码
// main.cpp --- 完整使用示例
#include <QCoreApplication>
#include <QDebug>
#include <QTimer>
#include "sync_engine.h"

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    // 创建引擎(4 个工作线程,分别持有独立 TCP 连接)
    SyncModbusEngine *engine = new SyncModbusEngine("192.168.1.100", 502, 4, 3000);

    // 连接结果信号
    QObject::connect(engine, &SyncModbusEngine::holdingRegistersRead,
        [](int wid, int rid, int addr, QVector<quint16> values) {
            qDebug() << "Worker" << wid << "寄存器[" << addr << "]:" << values;
        });

    // 启动引擎
    engine->start();

    // 周期性轮询(主线程定时器,不阻塞)
    QTimer pollTimer;
    QObject::connect(&pollTimer, &QTimer::timeout, [engine]() {
        engine->readHoldingRegisters(0, 10, 1, 0);   // 读 10 个寄存器
        engine->readCoils(0, 16, 1, 0);               // 读 16 个线圈
    });
    pollTimer.start(50); // 20 Hz

    // 定时输出统计
    QTimer statsTimer;
    QObject::connect(&statsTimer, &QTimer::timeout, [engine]() {
        qDebug().noquote() << engine->getStats().toString();
    });
    statsTimer.start(5000);

    // 30 秒后退出
    QTimer::singleShot(30000, &app, [engine, &app]() {
        engine->stop();
        app.quit();
    });

    return app.exec();
}

3.6 批量读取 + 请求合并

cpp 复制代码
void exampleBatchRead(SyncModbusEngine *engine) {
    // 读取地址 0-9、10-19、50-54
    // 自动合并为 [0,20] + [50,5] → 原 3 请求变 2 请求
    QVector<QPair<int, int>> ranges = {{0,10}, {10,10}, {50,5}};
    QList<int> reqIds = engine->batchRead(ranges);
}

四、专业视角:同步 vs 异步的终极分析

4.1 出发点不同,终点相同

复制代码
异步的出发点:"我不等,先去干别的"
同步的出发点:"我等,但让别人替我干"

结果:都是利用等待时间去处理其他请求
本质一致性:CPU 永远不闲着

4.2 11 维深度评测

维度 单线程异步 多线程同步(本文方案)
并发模型 事件循环 + 回调 线程池 + 阻塞调用
CPU 利用率 高(单核吃满) 高(多核均衡)
IO 等待利用率 100%(一个线程做所有事) N × 100%(N 个线程各自做事)
代码可读性 ❌ 回调嵌套 / 状态机 ✅ 线性顺序代码
上下文切换 零(单线程) 有(Modbus 场景可忽略)
多核利用 ❌ 单核(除非手动拆分) ✅ 自动利用所有核心
吞吐量上限 Min(单连接上限, CPU) Min(N × 单连接上限, CPU)
延迟 低(无线程切换) 略高(队列 + 切换 < 0.1ms)
调试难度 高(回调不易追踪) 低(调用栈完整)
资源消耗 低(1 线程) 较高(N 线程 + N 连接)

4.3 核心数学结论

复制代码
令:
  L  = 网络延迟 (RTT)
  Tp = 设备处理时间
  C  = 每请求 CPU 时间 (序列化/反序列化)
  Nc = 设备支持的并发连接数
  Nt = 同步线程数

异步单线程吞吐量:
  TA_max ≈ 1 / max(C, (L + Tp) / Nc)

同步多线程吞吐量(本文方案):
  TS_max ≈ Nt / (L + Tp + C)

令 TS_max >= TA_max:
  Nt >= (L + Tp + C) / max(C, (L + Tp) / Nc)

4.4 何时不应该用同步多线程

  1. 设备只支持单个 TCP 连接 --- 多线程不增加吞吐量,反增加开销
  2. 极高并发(100+ 设备) --- 400 线程的上下文切换是灾难
  3. 设备极快 + 局域网延迟极低 (L + Tp < C) --- CPU 本身是瓶颈

五、最终结论

5.1 选型决策树

复制代码
设备支持多连接?
├── 是 → 同步线程池可行,代码简单 ✅
└── 否 → 只能异步

团队擅长哪种模型?
├── 同步思维 → 线程池(本文方案)
└── 事件驱动 → 用异步

并发 > 50 设备?
├── 是 → 强制异步
└── 否 → 两者均可

代码需要长期维护?
├── 是 → 同步(调试/排错/新人上手更友好)
└── 否 → 选熟悉的

5.2 一句话

同步通过线程级并行填补 IO 等待空白,异步通过事件级轮转填补同一空白。当线程数 ≈ 设备并发连接数时,同步的吞吐量与异步等价,但代码复杂度低一个数量级。

5.3 推荐架构

复制代码
┌──────────────────────────────────────┐
│  业务逻辑层                           │
│  engine->readRegs() → 信号接收结果    │
│  (完全不知道内部是同步还是异步)        │
├──────────────────────────────────────┤
│  SyncModbusEngine (主线程或独立线程)   │
│  ├─ 请求队列                          │
│  ├─ Worker 线程池管理                 │
│  └─ 请求合并优化                      │
├──────────────────────────────────────┤
│  SyncModbusWorker × N (工作线程)      │
│  ├─ 每个持有一个 QModbusTcpClient     │
│  ├─ 内部同步阻塞执行                  │
│  └─ 结果通过信号发出(跨线程自动排队)  │
└──────────────────────────────────────┘

关键设计: 内部实现从同步切换到异步,甚至从 Modbus TCP 切换到 RTU------上层业务代码完全不需要修改。


本文完整实现了"同步方式获得异步级效率"的方案。核心思想不是让同步变快,而是用 N 个同步线程模拟异步的流水线效果。在绝大多数工业 Modbus 场景中,这种模式以更少的代码换取足够性能,同时显著降低维护成本。当设备不支持多连接或需 100+ 设备并发时,再回到纯异步。

相关推荐
田里的水稻1 小时前
OE_临时配置网络_linux系统终端命令行ip setting
linux·网络·tcp/ip
Cx330❀1 小时前
【Linux网络】从零构建高性能UDP服务器:从Echo到英译汉业务级实现
大数据·linux·服务器·开发语言·网络·c++·udp
不吃土豆的马铃薯1 小时前
TCP 三次握手 / 四次挥手详解
服务器·开发语言·网络·c++·网络协议·tcp/ip
羑悻的小杀马特1 小时前
【动态规划篇】正则表达式与通配符:开启代码匹配的赛博奇幻之旅
c++·算法·leetcode·正则表达式
Huangjin007_2 小时前
【C++ STL篇(十三)】无序关联容器 unordered_set / unordered_map解析
开发语言·c++
爱吃苹果的梨叔2 小时前
2026年KVM over IP分布式方案选型指南:清虹创智远程集中管控与坐席协作
分布式·网络协议·tcp/ip
Amnesia0_02 小时前
传输层协议UDP和TCP
linux·网络·tcp/ip·udp
minji...2 小时前
Linux 高级IO(三)多路转接之poll,poll的原理,poll版本的TCP服务器的实现
linux·服务器·网络·select·多路转接·epoll·poll
梦奇不是胖猫2 小时前
[ 计算机网络 | 第四章 ] 网络层 01 概述
网络·网络协议·计算机网络