QT TCP 源码结构框架

串口 + TCP + 协议解析都一起纳进来.


一、整体架构概览

先看概念上的分层,不碰代码:

text 复制代码
        ┌─────────────────────┐
        │      MainWindow     │  ← UI 层:界面、按钮、输入框、日志显示
        └────────┬────────────┘
                 │
          ITransport*  (抽象传输接口)
           /       \
          /         \
┌────────────────┐ ┌─────────────────┐
│  SerialService │ │    TcpService   │  ← 传输层:串口 / TCP,负责收发字节
└────────────────┘ └─────────────────┘
                 │
                 ▼
        ┌─────────────────────┐
        │   ProtocolParser    │  ← 协议层:粘包处理、校验、拆出 CMD + Payload
        └────────┬────────────┘
                 │
                 ▼
        ┌─────────────────────┐
        │  CommandDispatcher  │  ← 命令层:按 CMD 分发到不同处理逻辑
        └─────────────────────┘

核心思想只有一句话:

"UI 只管交互,传输只管收发字节,协议只管怎么拆,命令只管怎么处理。"


二、工程目录结构

你可以直接照这个建工程(qmake / CMake 都行,下面用 qmake 示例):

text 复制代码
SerialTool/
  SerialTool.pro
  main.cpp

  MainWindow.h
  MainWindow.cpp
  MainWindow.ui

  ITransport.h          // 抽象传输接口(串口/TCP 统一面向这个)
  SerialService.h
  SerialService.cpp
  TcpService.h
  TcpService.cpp

  ProtocolParser.h
  ProtocolParser.cpp

  CommandDispatcher.h
  CommandDispatcher.cpp

  Utils.h               // 工具函数:时间戳等
  Utils.cpp

SerialTool.pro 示例

pro 复制代码
QT       += core gui widgets serialport network

TARGET = SerialTool
TEMPLATE = app

SOURCES += \
    main.cpp \
    MainWindow.cpp \
    SerialService.cpp \
    TcpService.cpp \
    ProtocolParser.cpp \
    CommandDispatcher.cpp \
    Utils.cpp

HEADERS += \
    MainWindow.h \
    ITransport.h \
    SerialService.h \
    TcpService.h \
    ProtocolParser.h \
    CommandDispatcher.h \
    Utils.h

FORMS += \
    MainWindow.ui

三、公共工具层 Utils(时间戳)

cpp 复制代码
// Utils.h
#pragma once
#include <QString>

QString withTimestamp(const QString &msg);
cpp 复制代码
// Utils.cpp
#include "Utils.h"
#include <QDateTime>

QString withTimestamp(const QString &msg)
{
    return QDateTime::currentDateTime()
           .toString("[yyyy-MM-dd HH:mm:ss.zzz] ") + msg;
}

评价:

这类小工具集中到 Utils,避免到处复制粘贴时间戳代码,后续想加别的通用函数也有家可归。


四、传输接口 ITransport(关键点)

1. 接口设计

cpp 复制代码
// ITransport.h
#pragma once
#include <QObject>

class ITransport : public QObject
{
    Q_OBJECT
public:
    explicit ITransport(QObject *parent = nullptr)
        : QObject(parent) {}

    virtual ~ITransport() = default;

    virtual bool open() = 0;
    virtual void close() = 0;
    virtual bool isOpen() const = 0;
    virtual void send(const QByteArray &data) = 0;

signals:
    void received(const QByteArray &data);
    void log(const QString &msg);
    void opened();
    void closed();
    void sendCountUpdated(quint64 bytes);
    void recvCountUpdated(quint64 bytes);
};

设计评价:

  • 把"传输"的抽象统一成:open / close / isOpen / send
  • 上层完全不需要知道"下面是串口还是 TCP":
    只要有个 ITransport* transport_ 就能用。
  • 信号统一:receivedlog、统计信号都在接口里,方便 UI 一次性绑定。

这一层是整个"支持串口 + TCP 的关键"。


五、串口实现 SerialService(实现 ITransport)

只说结构和关键点,细节你可以用前面那版代码:

cpp 复制代码
// SerialService.h
#pragma once
#include "ITransport.h"
#include <QSerialPort>
#include <QTimer>

class SerialService : public ITransport
{
    Q_OBJECT
public:
    explicit SerialService(QObject *parent = nullptr);

    void setConfig(const QString &portName, int baudRate);

    bool open() override;
    void close() override;
    bool isOpen() const override { return port_.isOpen(); }
    void send(const QByteArray &data) override;

private slots:
    void onReadyRead();
    void onPortError(QSerialPort::SerialPortError error);
    void tryReconnect();

private:
    QSerialPort port_;
    QTimer reconnectTimer_;

    QString portName_;
    int baudRate_ = 0;

    quint64 sentBytes_ = 0;
    quint64 recvBytes_ = 0;
};

关键机制:

  • readyRead 信号异步接收,不会阻塞 UI。
  • errorOccurred 捕获串口拔掉等情况,配合 QTimer 做自动重连。
  • 内部维护 sentBytes_ / recvBytes_,通过接口的 sendCountUpdated / recvCountUpdated 发给 UI。

评价:

  • 职责非常单一:只管"串口"本身,不碰协议、不碰 UI。
  • 因为实现了 ITransport,上层可以随时把它换成别的实现(TCP/Udp 等)。

六、TCP 实现 TcpService(实现 ITransport)

cpp 复制代码
// TcpService.h
#pragma once
#include "ITransport.h"
#include <QTcpSocket>
#include <QTimer>

class TcpService : public ITransport
{
    Q_OBJECT
public:
    explicit TcpService(QObject *parent = nullptr);

    void setConfig(const QString &host, quint16 port);

    bool open() override;
    void close() override;
    bool isOpen() const override {
        return socket_.state() == QAbstractSocket::ConnectedState;
    }
    void send(const QByteArray &data) override;

private slots:
    void onConnected();
    void onDisconnected();
    void onReadyRead();
    void onErrorOccurred(QAbstractSocket::SocketError error);
    void tryReconnect();

private:
    QTcpSocket socket_;
    QTimer reconnectTimer_;

    QString host_;
    quint16 port_ = 0;

    quint64 sentBytes_ = 0;
    quint64 recvBytes_ = 0;
};

关键点:

  • 和串口一样,用 readyRead 异步接收。
  • 连接状态通过 connected / disconnected / errorOccurred 回调。
  • 同样用 QTimer 做自动重连(可选)。

评价:

  • 从上层视角看,它就是另一个实现了 ITransport 的"通道",API 一样,信号一样。
  • 你只要在 UI 里根据选择"Serial/TCP"切换 transport_ 的指向,就完成"支持 TCP"。

七、协议解析层 ProtocolParser(粘包 + 校验)

假设协议格式:

text 复制代码
55 AA LEN CMD PAYLOAD... CHK
LEN = payload 长度
CHK = CMD + payload 全部 XOR
cpp 复制代码
// ProtocolParser.h
#pragma once
#include <QObject>

class ProtocolParser : public QObject
{
    Q_OBJECT
public:
    explicit ProtocolParser(QObject *parent = nullptr);

public slots:
    void feed(const QByteArray &data);  // 传输层喂原始字节

signals:
    void frameParsed(quint8 cmd, const QByteArray &payload);
    void log(const QString &msg);

private:
    QByteArray buffer_;
    void process();
};

核心点:

  • 内部 buffer_ 负责处理"半包 / 粘包"。
  • 每次 feed 把新数据 append 进 buffer,然后循环解析。
  • 成功解析完整帧后,发 frameParsed(cmd, payload),上层不用管粘包问题。

评价:

  • 只做"字节 → 帧"的工作,不亲自处理业务,不知道串口/TCP。
  • 将来你要改协议(比如 JSON、其他头格式),只要动这个类,不影响 UI 和传输层。

八、命令分发层 CommandDispatcher(按 CMD 派发)

cpp 复制代码
// CommandDispatcher.h
#pragma once
#include <QObject>
#include <QMap>
#include <functional>

class CommandDispatcher : public QObject
{
    Q_OBJECT
public:
    explicit CommandDispatcher(QObject *parent = nullptr);

    void registerHandler(quint8 cmd,
                         std::function<void(const QByteArray&)> handler);

public slots:
    void dispatch(quint8 cmd, const QByteArray &payload);

signals:
    void log(const QString &msg);

private:
    QMap<quint8, std::function<void(const QByteArray&)>> handlers_;
};

使用方式:

在 MainWindow 里:

cpp 复制代码
dispatcher_->registerHandler(0x01, [this](const QByteArray &p){
    // 例如 JSON 文本
    QString s = QString::fromUtf8(p);
    ui->textEditRecv->append(withTimestamp("CMD 0x01(JSON): " + s));
});

dispatcher_->registerHandler(0x02, [this](const QByteArray &p){
    ui->textEditRecv->append(withTimestamp(
        "CMD 0x02(BIN): " + p.toHex(' ')
    ));
});

解析器连接分发器:

cpp 复制代码
connect(parser_, &ProtocolParser::frameParsed,
        dispatcher_, &CommandDispatcher::dispatch);

评价:

  • 把"不同 CMD 的业务逻辑"从解析器里拿出来,放在可以自由扩展的地方。
  • 使用 std::function + lambda,不用为了每个命令再建 N 个子类,写起来很轻松。
  • Dispatcher 不依赖 UI,只对 cmd + payload 负责,也方便写单元测试。

九、MainWindow(UI 层 + 总装配)

1. 成员大致长这样

cpp 复制代码
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;

    ITransport *transport_ = nullptr;
    SerialService *serial_ = nullptr;
    TcpService *tcp_ = nullptr;

    ProtocolParser *parser_ = nullptr;
    CommandDispatcher *dispatcher_ = nullptr;
    QTimer autoSendTimer_;

    void initUi();
    void initSignals();
    void initDispatcher();

private slots:
    void onOpenClicked();
    void onCloseClicked();
    void onSendClicked();
    void onClearRecv();
    void onClearLog();
};

2. 核心职责

MainWindow 只做这些事:

  1. 初始化 UI(串口下拉、TCP 地址/端口、计数标签等)
  2. 选择当前通道(Serial/TCP),给通道设置参数
  3. received 接到 ProtocolParser,把 frameParsed 接到 CommandDispatcher
  4. 绑定一堆 log 信号到 textEditLog
  5. 自动发送用 QTimeronSendClicked
  6. 用户输入解析(文本 / 十六进制),然后 transport_->send(data)

评价:

  • MainWindow 不直接 touch QSerialPort/QTcpSocket,只面向 ITransport,这点非常关键。
  • 协议、命令、传输这些复杂逻辑都在专门的类里面,MainWindow 文件不会长到看不下去。

十、整体设计总结(优缺点)

优点

  1. 解耦干净

    • UI ↔ 传输 ↔ 协议 ↔ 命令,每层都有清晰边界。
    • 想改协议,只动 ProtocolParser/Dispatcher,不动 UI/传输。
    • 想加 UdpService,只要再继承 ITransport 就行。
  2. 扩展性强

    • 支持串口、TCP 只是换一个 ITransport 实现。

    • 后面可以很轻松加:

      • JSON 协议解析(在某个 CMD 里用 QJsonDocument 解析)
      • 多协议并存(不同 CMD 对应不同解析方式)
      • 更多通信方式(UDP、WebSocket 等)
  3. 可维护性好

    • 每个类职责很单一,文件长度可控。
    • 逻辑问题可以很快定位属于哪一层。
  4. 便于测试

    • 可以写一个 MockTransport 实现 ITransport,不连真实设备就能调试协议和 UI。
    • ProtocolParser、CommandDispatcher 都不依赖 Qt UI,可以单元测试。

潜在缺点 / 注意点

  1. 文件数量多一点

    对比"所有东西全写在 MainWindow",文件多了一些,但这是换来的可维护性,值。

  2. 初学者入门理解成本稍高

    需要理解 Qt 信号槽 + 抽象接口这套东西。不过一旦搞懂,后面开发效率是翻倍的。

  3. 自动重连逻辑要按实际需求微调

    比如:连不上是否一直重试、是否加最大次数、错误提示策略等。


相关推荐
Java Fans2 小时前
Qt Designer 和 PyQt 开发教程
开发语言·qt·pyqt
开始了码2 小时前
深入理解回调函数:从概念到 Qt 实战
开发语言·qt
世转神风-7 小时前
qt-pro文件名词解释
开发语言·qt
kupeThinkPoem8 小时前
Qt中addSpacing参数为0的作用
qt
꧁坚持很酷꧂10 小时前
QCustomPlot绘制曲线
qt
火山灿火山13 小时前
Qt常用控件(五) - 多元素控件
开发语言·qt
Ivy_belief14 小时前
Linux:设置和获取单片机gpio引脚和key值
linux·arm开发·qt·gpio·event事件
神仙别闹15 小时前
基于QT(C++)实现B树可视化
c++·b树·qt
feiyangqingyun15 小时前
记一次Qt视频监控系统的优化/双击打开分组可能崩溃的BUG/排对打开通道过程中关闭通道可能崩溃的BUG
qt·音视频·qt监控系统·qt视频轮询