A detailed implementation plan for developing a high-performance real-time communication host, covering multiple communication interfaces, real-time data acquisition and storage, data waveform visualization, and low-latency optimization.
1. System Architecture Design
Utilizing layered architecture + plugin design, the core modules are decoupled, supporting rapid expansion of new communication interfaces. The architecture diagram is as follows:
┌───────────────────────┐
│ Main Interface Layer │ <- Data waveform visualization, control buttons, log display
├───────────────────────┤
│ Business Logic Layer │ <- Data caching, protocol parsing, state management
├───────────────────────┤
│ Communication Interface Layer │ <- USB/BLE/WiFi/Serial plugins (dynamically loaded)
├───────────────────────┤
│ Data Storage Layer │ <- SQLite/Binary files (asynchronous writing)
└───────────────────────┘
2. Key Technology Implementation
1. Plugin-based Multiple Communication Interfaces (Core Extension Point)
Encapsulating different communication interfaces through Qt plugin mechanism, the main program dynamically loads plugins, supporting hot swapping.
(1) Communication Interface Plugin Specification
Define a unified plugin interface <span>CommunicationPluginInterface</span>, all communication plugins must implement the following functions:
// CommunicationPluginInterface.h
#pragma once
#include <QObject>
#include <QByteArray>
#include <QMetaType>
enum class CommType { USB, BLE, WiFi, SerialPort };
enum class ConnStatus { Disconnected, Connecting, Connected, Error };
class CommunicationPluginInterface {
public:
virtual ~CommunicationPluginInterface() = default;
virtual CommType getType() const = 0; // Interface type
virtual bool connect(const QString& params) = 0; // Connect (parameters like port/address)
virtual void disconnect() = 0; // Disconnect
virtual bool sendData(const QByteArray& data) = 0; // Send data
virtual QVector<QByteArray> receive() = 0; // Receive data (batch)
virtual ConnStatus getStatus() const = 0; // Current status
virtual QString getStatusDesc() const = 0; // Status description
signals:
void dataReceived(const QByteArray& data); // Data arrival signal
void statusChanged(ConnStatus status, const QString& desc); // Status change signal
};
Q_DECLARE_INTERFACE(CommunicationPluginInterface, "com.yourcompany.CommPlugin/1.0")
(2) Typical Communication Plugin Implementation (USB Example)
Using the <span>libusb</span> library to implement the USB communication plugin, note the following:
- •
Asynchronous IO: Use
<span>libusb_hotplug</span>to monitor device plug/unplug events, using a separate thread to handle data read/write. - •
Low latency: Set reasonable timeout values (e.g.,
<span>LIBUSB_TRANSFER_TIMEOUT_MS=10</span>).// UsbPlugin.h #pragma once #include "CommunicationPluginInterface.h" #include <libusb-1.0/libusb.h> #include <QThread> #include <QQueue> #include <QMutex> class UsbPlugin : public QObject, public CommunicationPluginInterface { Q_OBJECT Q_INTERFACES(CommunicationPluginInterface) public: explicit UsbPlugin(QObject* parent = nullptr); ~UsbPlugin(); CommType getType() const override { return CommType::USB; } bool connect(const QString& params) override; // params format: "vid:pid" void disconnect() override; bool sendData(const QByteArray& data) override; QVector<QByteArray> receive() override; ConnStatus getStatus() const override { return m_status; } QString getStatusDesc() const override; private slots: void onUsbDataReceived(); // USB data arrival slot function private: libusb_device_handle* m_devHandle = nullptr; ConnStatus m_status = ConnStatus::Disconnected; QThread* m_workThread; // Work thread (handles USB IO) QQueue<QByteArray> m_rxBuffer; // Receive buffer (thread-safe) QMutex m_bufferMutex; // Buffer lock };
(3) Dynamically Loading Plugins in the Main Program
In <span>MainWindow</span>, scan the plugin directory and load all compliant communication plugins:
// MainWindow.cpp
void MainWindow::loadCommunicationPlugins() {
QString pluginPath = QCoreApplication::applicationDirPath() + "/plugins";
QDir dir(pluginPath);
foreach (QString fileName, dir.entryList(QDir::Files)) {
QPluginLoader loader(dir.filePath(fileName));
QObject* plugin = loader.instance();
if (plugin) {
CommunicationPluginInterface* commPlugin =
qobject_cast<CommunicationPluginInterface*>(plugin);
if (commPlugin) {
m_plugins.append(commPlugin);
// Add to communication type dropdown
ui->cbCommType->addItem(commPlugin->getTypeName(), QVariant::fromValue(commPlugin));
// Connect signals
connect(commPlugin, &CommunicationPluginInterface::dataReceived,
this, &MainWindow::onDataReceived);
connect(commPlugin, &CommunicationPluginInterface::statusChanged,
this, &MainWindow::onCommStatusChanged);
}
}
}
}
2. Real-Time Data Acquisition and Storage (High Throughput Optimization)
Utilizing the producer-consumer model, separating data acquisition, processing, and storage processes to ensure no packet loss under high throughput.
(1) Data Acquisition Thread
Each communication plugin runs in a separate thread, responsible for real-time data reading and placing it into a shared queue:
// UsbPluginWorkThread.h (USB plugin work thread)
#pragma once
#include <QThread>
#include <QQueue>
#include <QMutex>
#include <QWaitCondition>
class UsbPlugin;
class UsbPluginWorkThread : public QThread {
Q_OBJECT
public:
explicit UsbPluginWorkThread(UsbPlugin* plugin, QObject* parent = nullptr);
void stop() { m_stop = true; wait(); }
protected:
void run() override;
private:
UsbPlugin* m_plugin;
bool m_stop = false;
QQueue<QByteArray> m_rxQueue; // Receive queue (thread-safe)
QMutex m_queueMutex;
QWaitCondition m_queueCond;
};
(2) Asynchronous Data Storage
Using SQLite + asynchronous writing, to avoid blocking the main thread. Improve writing efficiency through <span>QSqlQuery</span> prepared statements:
// DataStorage.h
#pragma once
#include <QObject>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include <QVector>
class DataStorage : public QObject {
Q_OBJECT
public:
static DataStorage* getInstance() {
static DataStorage instance;
return &instance;
}
bool init(const QString& dbPath = "data.db") {
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName(dbPath);
if (!db.open()) {
qDebug() << "Database open failed:" << db.lastError().text();
return false;
}
// Create data table (timestamp, data length, raw data)
QSqlQuery query;
return query.exec("CREATE TABLE IF NOT EXISTS DataLog (" "id INTEGER PRIMARY KEY AUTOINCREMENT, " "timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, " "data_len INTEGER, " "raw_data BLOB)");
}
void asyncSave(const QByteArray& data) {
// Use QtConcurrent for asynchronous writing
QtConcurrent::run([data]() {
QSqlQuery query;
query.prepare("INSERT INTO DataLog (data_len, raw_data) VALUES (?, ?)");
query.addBindValue(data.size());
query.addBindValue(data);
if (!query.exec()) {
qDebug() << "Data storage failed:" << query.lastError().text();
}
});
}
private:
DataStorage() = default;
};
(3) Data Synchronization and Throttling
- •
Shared Queue: Use
<span>QQueue</span>+<span>QMutex</span>+<span>QWaitCondition</span>to implement a thread-safe circular buffer, avoiding memory overflow. - •
Flow Control: When the queue size exceeds a threshold (e.g., 10MB), discard old data or notify the acquisition end to slow down.
3. Data Waveform Visualization (Low Latency Rendering)
Using Qt custom controls + double buffering drawing, to achieve millisecond-level real-time waveform updates.
(1) Custom Waveform Control
Inherit from <span>QWidget</span>, override <span>paintEvent</span>, using <span>QImage</span> as a background buffer to reduce interface redraw times:
// WaveformWidget.h
#pragma once
#include <QWidget>
#include <QVector>
#include <QImage>
class WaveformWidget : public QWidget {
Q_OBJECT
public:
explicit WaveformWidget(QWidget* parent = nullptr);
void updateData(const QVector<quint16>& newData); // Update data
protected:
void paintEvent(QPaintEvent* event) override;
private:
QVector<quint16> m_data; // Current display data
QImage m_backBuffer; // Background buffer
int m_maxValue = 4096; // Maximum data value (adjust based on sensor)
int m_minValue = 0;
};
// WaveformWidget.cpp
void WaveformWidget::updateData(const QVector<quint16>& newData) {
m_data = newData;
update(); // Trigger redraw
}
void WaveformWidget::paintEvent(QPaintEvent* event) {
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
// Initialize background buffer (only on first or size change)
if (m_backBuffer.size() != size()) {
m_backBuffer = QImage(size(), QImage::Format_RGB32);
m_backBuffer.fill(Qt::black);
}
// Draw waveform (double buffering)
QPainter bufferPainter(&m_backBuffer);
bufferPainter.setPen(QPen(Qt::green, 1));
int width = this->width();
int height = this->height();
int midY = height / 2;
// Draw grid
bufferPainter.setPen(QPen(Qt::darkGreen, 0.5));
for (int x = 0; x < width; x += 20) bufferPainter.drawLine(x, 0, x, height);
for (int y = 0; y < height; y += 20) bufferPainter.drawLine(0, y, width, y);
// Draw waveform
bufferPainter.beginPath();
for (int i = 0; i < m_data.size(); ++i) {
int x = static_cast<int>(static_cast<double>(i) / m_data.size() * width);
int y = midY - static_cast<int>(static_cast<double>(m_data[i] - m_minValue) / (m_maxValue - m_minValue) * midY);
if (i == 0) bufferPainter.moveTo(x, y);
else bufferPainter.lineTo(x, y);
}
bufferPainter.strokePath();
// Draw the background buffer to the screen
painter.drawImage(0, 0, m_backBuffer);
}
(2) Real-Time Update Optimization
- •
Data Downsampling: When the sampling rate is high (e.g., 1MHz), average or sample the data to reduce the amount of data drawn.
- •
Timed Refresh: Use
<span>QTimer</span>to control the refresh frequency (e.g., 50ms), balancing real-time performance and CPU usage.// Start timer in MainWindow m_refreshTimer = new QTimer(this); m_refreshTimer->setInterval(50); // 20Hz refresh connect(m_refreshTimer, &QTimer::timeout, this, [this]() { if (m_currentPlugin) { auto data = m_currentPlugin->receive(); QVector<quint16> displayData = downsample(data, 100); // Downsample to 100 points ui->waveformWidget->updateData(displayData); } }); m_refreshTimer->start();
4. Low Latency Optimization Strategies
- •
Multithreaded Architecture: Separate communication, acquisition, storage, and rendering into different threads to avoid blocking the main thread.
- •
Zero-Copy Data Transfer: Use
<span>QSharedMemory</span>or<span>std::shared_ptr</span>to reduce data copying. - •
Memory Pool: Pre-allocate fixed-size memory blocks (e.g., circular buffer) to avoid dynamic memory allocation.