开源 C++ QT Widget 开发(六)通讯--TCP调试

文章的目的为了记录使用C++ 进行QT Widget 开发学习的经历。临时学习,完成app的开发。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。

相关链接:

开源 C++ QT Widget 开发(一)工程文件结构-CSDN博客

开源 C++ QT Widget 开发(二)基本控件应用-CSDN博客

开源 C++ QT Widget 开发(三)图表--波形显示器-CSDN博客

开源 C++ QT Widget 开发(四)文件--二进制文件查看编辑-CSDN博客

开源 C++ QT Widget 开发(五)通讯--串口调试-CSDN博客

开源 C++ QT Widget 开发(六)通讯--TCP调试-CSDN博客

推荐链接:

开源 java android app 开发(一)开发环境的搭建-CSDN博客

开源 java android app 开发(二)工程文件结构-CSDN博客

开源 java android app 开发(三)GUI界面布局和常用组件-CSDN博客

开源 java android app 开发(四)GUI界面重要组件-CSDN博客

开源 java android app 开发(五)文件和数据库存储-CSDN博客

开源 java android app 开发(六)多媒体使用-CSDN博客

开源 java android app 开发(七)通讯之Tcp和Http-CSDN博客

开源 java android app 开发(八)通讯之Mqtt和Ble-CSDN博客

开源 java android app 开发(九)后台之线程和服务-CSDN博客

开源 java android app 开发(十)广播机制-CSDN博客

开源 java android app 开发(十一)调试、发布-CSDN博客

开源 java android app 开发(十二)封库.aar-CSDN博客

推荐链接:

开源C# .net mvc 开发(一)WEB搭建_c#部署web程序-CSDN博客

开源 C# .net mvc 开发(二)网站快速搭建_c#网站开发-CSDN博客

开源 C# .net mvc 开发(三)WEB内外网访问(VS发布、IIS配置网站、花生壳外网穿刺访问)_c# mvc 域名下不可訪問內網,內網下可以訪問域名-CSDN博客

开源 C# .net mvc 开发(四)工程结构、页面提交以及显示_c#工程结构-CSDN博客

开源 C# .net mvc 开发(五)常用代码快速开发_c# mvc开发-CSDN博客

本章主要内容:Tcp通讯调试工具,ip,端口等参数可设置。可以设为服务器端和客户端,同时数据可以发送文本或16进制。

一、源码分析

1.1 界面设计 (UI)

主窗口布局:采用垂直布局,包含TCP配置区、接收数据区和发送数据区

控件组织:

TCP配置区:模式选择、地址选择、端口输入、连接按钮、状态显示

接收区:文本显示框、16进制显示选项、清空按钮

发送区:文本输入框、16进制发送选项、清空和发送按钮

1.2 核心功能

网络模式支持:

Server模式:作为TCP服务器监听连接,支持多客户端连接

Client模式:作为TCP客户端连接远程服务器

1.3 数据处理:

16进制与文本模式:支持16进制和文本两种数据格式的发送和显示

实时数据收发:能够实时接收和显示网络数据

二、所有源码

2.1 mainwindow.h

复制代码
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTcpServer>
#include <QTcpSocket>
#include <QComboBox>
#include <QTextEdit>
#include <QCheckBox>
#include <QPushButton>
#include <QLabel>
#include <QGroupBox>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QMessageBox>
#include <QScrollBar>
#include <QLineEdit>
#include <QHostInfo>
#include <QNetworkInterface>

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

private slots:
    void onConnectButtonClicked();
    void onSendButtonClicked();
    void onClearReceiveButtonClicked();
    void onClearSendButtonClicked();
    void onNewConnection();
    void onClientConnected();
    void onClientDisconnected();
    void onReadyRead();
    void onTcpModeChanged(int index);

private:
    void createUI();
    void initNetwork();
    void refreshLocalAddresses();
    void connectSignals();
    void disconnectSignals();
    void updateConnectionStatus();
    QString getClientInfo(QTcpSocket *client) const;

    // TCP模式选择
    QComboBox *tcpModeComboBox;
    QComboBox *localAddressComboBox;
    QLineEdit *portEdit;
    QPushButton *connectButton;
    QLabel *statusLabel;

    // 客户端连接信息
    QLabel *clientsLabel;

    // 接收区域控件
    QTextEdit *receiveEdit;
    QCheckBox *hexDisplayCheckBox;
    QPushButton *clearReceiveButton;

    // 发送区域控件
    QTextEdit *sendEdit;
    QCheckBox *hexSendCheckBox;
    QPushButton *clearSendButton;
    QPushButton *sendButton;

    // 网络对象
    QTcpServer *tcpServer;
    QList<QTcpSocket*> clientSockets;
    QTcpSocket *tcpClient;

    // 布局和容器
    QWidget *centralWidget;
    QVBoxLayout *mainLayout;
};

#endif // MAINWINDOW_H

2.2 mainwindow.cpp文件

复制代码
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , tcpServer(nullptr)
    , tcpClient(nullptr)
{
    // 设置窗口大小和标题
    this->resize(1000, 800);
    this->setWindowTitle("QT TCP调试助手");

    // 创建界面
    createUI();

    // 初始化网络
    initNetwork();

    // 刷新本地地址
    refreshLocalAddresses();

    // 连接信号槽
    connectSignals();
}

MainWindow::~MainWindow()
{
    // 关闭服务器
    if (tcpServer && tcpServer->isListening()) {
        tcpServer->close();
    }

    // 关闭所有客户端连接
    for (QTcpSocket *client : clientSockets) {
        if (client && client->state() == QAbstractSocket::ConnectedState) {
            client->disconnectFromHost();
            if (client->state() != QAbstractSocket::UnconnectedState) {
                client->waitForDisconnected(1000);
            }
        }
        delete client;
    }
    clientSockets.clear();

    // 关闭客户端连接
    if (tcpClient && tcpClient->state() == QAbstractSocket::ConnectedState) {
        tcpClient->disconnectFromHost();
        if (tcpClient->state() != QAbstractSocket::UnconnectedState) {
            tcpClient->waitForDisconnected(1000);
        }
    }
    delete tcpClient;
    delete tcpServer;
}

void MainWindow::createUI()
{
    // 创建中央部件
    centralWidget = new QWidget(this);
    setCentralWidget(centralWidget);

    // 主布局
    mainLayout = new QVBoxLayout(centralWidget);
    mainLayout->setSpacing(10);
    mainLayout->setContentsMargins(10, 10, 10, 10);

    // 创建TCP配置区域
    QGroupBox *configGroup = new QGroupBox("TCP配置", centralWidget);
    QHBoxLayout *configLayout = new QHBoxLayout(configGroup);
    configLayout->setSpacing(5);

    // TCP模式选择
    QLabel *modeLabel = new QLabel("TCP模式:", configGroup);
    tcpModeComboBox = new QComboBox(configGroup);
    tcpModeComboBox->setMinimumWidth(100);
    tcpModeComboBox->addItems(QStringList() << "Server" << "Client");
    configLayout->addWidget(modeLabel);
    configLayout->addWidget(tcpModeComboBox);

    // 本地地址选择
    QLabel *addressLabel = new QLabel("本地地址:", configGroup);
    localAddressComboBox = new QComboBox(configGroup);
    localAddressComboBox->setMinimumWidth(150);
    configLayout->addWidget(addressLabel);
    configLayout->addWidget(localAddressComboBox);

    // 端口输入
    QLabel *portLabel = new QLabel("端口:", configGroup);
    portEdit = new QLineEdit(configGroup);
    portEdit->setMinimumWidth(80);
    portEdit->setText("8080");
    portEdit->setValidator(new QIntValidator(1, 65535, this));
    configLayout->addWidget(portLabel);
    configLayout->addWidget(portEdit);

    // 连接/监听按钮
    connectButton = new QPushButton("启动监听", configGroup);
    connectButton->setFixedWidth(100);
    configLayout->addWidget(connectButton);

    // 状态标签
    statusLabel = new QLabel("未连接", configGroup);
    configLayout->addWidget(statusLabel);

    // 客户端连接信息标签
    clientsLabel = new QLabel("客户端: 0", configGroup);
    configLayout->addWidget(clientsLabel);

    configLayout->addStretch();

    // 创建接收区域
    QGroupBox *receiveGroup = new QGroupBox("接收数据", centralWidget);
    QVBoxLayout *receiveLayout = new QVBoxLayout(receiveGroup);

    // 接收文本框
    receiveEdit = new QTextEdit(receiveGroup);
    receiveEdit->setReadOnly(true);
    receiveEdit->setMinimumHeight(300);
    receiveLayout->addWidget(receiveEdit);

    // 接收选项
    QHBoxLayout *receiveOptionsLayout = new QHBoxLayout();
    hexDisplayCheckBox = new QCheckBox("16进制显示", receiveGroup);
    clearReceiveButton = new QPushButton("清空接收", receiveGroup);
    clearReceiveButton->setFixedWidth(80);
    receiveOptionsLayout->addWidget(hexDisplayCheckBox);
    receiveOptionsLayout->addWidget(clearReceiveButton);
    receiveOptionsLayout->addStretch();

    receiveLayout->addLayout(receiveOptionsLayout);

    // 创建发送区域
    QGroupBox *sendGroup = new QGroupBox("发送数据", centralWidget);
    QVBoxLayout *sendLayout = new QVBoxLayout(sendGroup);

    // 发送文本框
    sendEdit = new QTextEdit(sendGroup);
    sendEdit->setMaximumHeight(150);
    sendLayout->addWidget(sendEdit);

    // 发送选项
    QHBoxLayout *sendOptionsLayout = new QHBoxLayout();
    hexSendCheckBox = new QCheckBox("16进制发送", sendGroup);
    clearSendButton = new QPushButton("清空发送", sendGroup);
    clearSendButton->setFixedWidth(80);
    sendButton = new QPushButton("发送", sendGroup);
    sendButton->setFixedWidth(80);
    sendOptionsLayout->addWidget(hexSendCheckBox);
    sendOptionsLayout->addWidget(clearSendButton);
    sendOptionsLayout->addWidget(sendButton);
    sendOptionsLayout->addStretch();

    sendLayout->addLayout(sendOptionsLayout);

    // 将各个区域添加到主布局
    mainLayout->addWidget(configGroup);
    mainLayout->addWidget(receiveGroup);
    mainLayout->addWidget(sendGroup);

    // 设置布局比例
    mainLayout->setStretch(0, 1);  // 配置区域
    mainLayout->setStretch(1, 4);  // 接收区域
    mainLayout->setStretch(2, 2);  // 发送区域
}

void MainWindow::initNetwork()
{
    tcpServer = new QTcpServer(this);
    tcpClient = new QTcpSocket(this);
}

void MainWindow::refreshLocalAddresses()
{
    localAddressComboBox->clear();

    // 添加本地地址选项
    localAddressComboBox->addItem("127.0.0.1");
    localAddressComboBox->addItem("0.0.0.0");

    // 获取本机所有IP地址
    QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
    for (const QHostAddress &address : addresses) {
        if (address.protocol() == QAbstractSocket::IPv4Protocol &&
            address != QHostAddress::LocalHost) {
            localAddressComboBox->addItem(address.toString());
        }
    }

    localAddressComboBox->setCurrentIndex(0);
}

void MainWindow::connectSignals()
{
    connect(connectButton, &QPushButton::clicked, this, &MainWindow::onConnectButtonClicked);
    connect(sendButton, &QPushButton::clicked, this, &MainWindow::onSendButtonClicked);
    connect(clearReceiveButton, &QPushButton::clicked, this, &MainWindow::onClearReceiveButtonClicked);
    connect(clearSendButton, &QPushButton::clicked, this, &MainWindow::onClearSendButtonClicked);
    connect(tcpModeComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
            this, &MainWindow::onTcpModeChanged);

    connect(tcpServer, &QTcpServer::newConnection, this, &MainWindow::onNewConnection);
    connect(tcpClient, &QTcpSocket::connected, this, &MainWindow::onClientConnected);
    connect(tcpClient, &QTcpSocket::disconnected, this, &MainWindow::onClientDisconnected);
    connect(tcpClient, &QTcpSocket::readyRead, this, &MainWindow::onReadyRead);
}

void MainWindow::disconnectSignals()
{
    disconnect(tcpServer, &QTcpServer::newConnection, this, &MainWindow::onNewConnection);
    disconnect(tcpClient, &QTcpSocket::connected, this, &MainWindow::onClientConnected);
    disconnect(tcpClient, &QTcpSocket::disconnected, this, &MainWindow::onClientDisconnected);
    disconnect(tcpClient, &QTcpSocket::readyRead, this, &MainWindow::onReadyRead);

    for (QTcpSocket *client : clientSockets) {
        disconnect(client, &QTcpSocket::readyRead, this, &MainWindow::onReadyRead);
        disconnect(client, &QTcpSocket::disconnected, this, &MainWindow::onClientDisconnected);
    }
}

void MainWindow::onTcpModeChanged(int index)
{
    if (index == 0) { // Server模式
        connectButton->setText("启动监听");
        localAddressComboBox->setEnabled(true);
    } else { // Client模式
        connectButton->setText("连接服务器");
        localAddressComboBox->setEnabled(true);
    }

    // 断开当前连接
    if (tcpServer->isListening()) {
        tcpServer->close();
    }
    if (tcpClient->state() == QAbstractSocket::ConnectedState) {
        tcpClient->disconnectFromHost();
    }

    statusLabel->setText("未连接");
    clientsLabel->setText("客户端: 0");
}

void MainWindow::onConnectButtonClicked()
{
    QString portText = portEdit->text().trimmed();
    if (portText.isEmpty()) {
        QMessageBox::warning(this, "警告", "请输入端口号");
        return;
    }

    quint16 port = portText.toUShort();
    if (port == 0) {
        QMessageBox::warning(this, "警告", "端口号无效");
        return;
    }

    if (tcpModeComboBox->currentIndex() == 0) { // Server模式
        if (tcpServer->isListening()) {
            // 停止监听
            tcpServer->close();
            for (QTcpSocket *client : clientSockets) {
                client->disconnectFromHost();
                delete client;
            }
            clientSockets.clear();
            connectButton->setText("启动监听");
            statusLabel->setText("已停止监听");
            clientsLabel->setText("客户端: 0");
        } else {
            // 启动监听
            QHostAddress address(localAddressComboBox->currentText());
            if (tcpServer->listen(address, port)) {
                connectButton->setText("停止监听");
                statusLabel->setText(QString("监听中: %1:%2").arg(address.toString()).arg(port));
                receiveEdit->append(QString("[系统] 开始监听 %1:%2").arg(address.toString()).arg(port));
            } else {
                QMessageBox::critical(this, "错误", "监听失败: " + tcpServer->errorString());
            }
        }
    } else { // Client模式
        if (tcpClient->state() == QAbstractSocket::ConnectedState) {
            // 断开连接
            tcpClient->disconnectFromHost();
            connectButton->setText("连接服务器");
            statusLabel->setText("已断开连接");
        } else {
            // 连接服务器
            QHostAddress serverAddress(localAddressComboBox->currentText());
            tcpClient->connectToHost(serverAddress, port);
            if (tcpClient->waitForConnected(3000)) {
                connectButton->setText("断开连接");
                statusLabel->setText(QString("已连接到: %1:%2").arg(serverAddress.toString()).arg(port));
                receiveEdit->append(QString("[系统] 连接到服务器 %1:%2").arg(serverAddress.toString()).arg(port));
            } else {
                QMessageBox::critical(this, "错误", "连接失败: " + tcpClient->errorString());
            }
        }
    }
}

void MainWindow::onSendButtonClicked()
{
    QString sendText = sendEdit->toPlainText();
    if (sendText.isEmpty()) {
        QMessageBox::warning(this, "警告", "发送内容不能为空");
        return;
    }

    QByteArray sendData;
    if (hexSendCheckBox->isChecked()) {
        // 16进制发送
        sendText = sendText.trimmed();
        sendText.replace(" ", "");
        sendText.replace("\n", "");
        sendText.replace("\t", "");
        sendText.replace("\r", "");

        if (sendText.isEmpty()) {
            QMessageBox::warning(this, "警告", "16进制数据不能为空");
            return;
        }

        if (sendText.length() % 2 != 0) {
            QMessageBox::warning(this, "警告", "16进制数据长度必须为偶数");
            return;
        }

        bool ok;
        for (int i = 0; i < sendText.length(); i += 2) {
            QString byteStr = sendText.mid(i, 2);
            uint8_t byte = byteStr.toUShort(&ok, 16);
            if (!ok) {
                QMessageBox::warning(this, "警告", "包含非法的16进制字符: " + byteStr);
                return;
            }
            sendData.append(byte);
        }
    } else {
        // 文本发送
        sendData = sendText.toUtf8();
    }

    // 发送数据
    if (tcpModeComboBox->currentIndex() == 0) { // Server模式
        // 向所有客户端发送数据
        for (QTcpSocket *client : clientSockets) {
            if (client->state() == QAbstractSocket::ConnectedState) {
                client->write(sendData);
            }
        }
        if (clientSockets.isEmpty()) {
            QMessageBox::warning(this, "警告", "没有客户端连接");
        }
    } else { // Client模式
        if (tcpClient->state() == QAbstractSocket::ConnectedState) {
            tcpClient->write(sendData);
        } else {
            QMessageBox::warning(this, "警告", "未连接到服务器");
        }
    }
}

void MainWindow::onNewConnection()
{
    while (tcpServer->hasPendingConnections()) {
        QTcpSocket *client = tcpServer->nextPendingConnection();
        clientSockets.append(client);

        connect(client, &QTcpSocket::readyRead, this, &MainWindow::onReadyRead);
        connect(client, &QTcpSocket::disconnected, this, [this, client]() {
            receiveEdit->append(QString("[系统] 客户端断开: %1").arg(getClientInfo(client)));
            clientSockets.removeOne(client);
            client->deleteLater();
            clientsLabel->setText(QString("客户端: %1").arg(clientSockets.size()));
        });

        receiveEdit->append(QString("[系统] 新客户端连接: %1").arg(getClientInfo(client)));
        clientsLabel->setText(QString("客户端: %1").arg(clientSockets.size()));
    }
}

void MainWindow::onClientConnected()
{
    receiveEdit->append("[系统] 已连接到服务器");
}

void MainWindow::onClientDisconnected()
{
    receiveEdit->append("[系统] 与服务器断开连接");
    connectButton->setText("连接服务器");
    statusLabel->setText("连接已断开");
}

void MainWindow::onReadyRead()
{
    QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
    if (!socket) return;

    QByteArray data = socket->readAll();
    if (data.isEmpty()) return;

    QString prefix;
    if (tcpModeComboBox->currentIndex() == 0) { // Server模式
        prefix = QString("[客户端 %1] ").arg(getClientInfo(socket));
    } else { // Client模式
        prefix = "[服务器] ";
    }

    if (hexDisplayCheckBox->isChecked()) {
        // 16进制显示
        QString hexData;
        for (int i = 0; i < data.size(); i++) {
            hexData += QString("%1 ").arg((uint8_t)data[i], 2, 16, QLatin1Char('0')).toUpper();
        }
        receiveEdit->append(prefix + hexData.trimmed());
    } else {
        // 文本显示
        QString text = QString::fromUtf8(data);
        receiveEdit->append(prefix + text);
    }

    // 自动滚动到底部
    QScrollBar *scrollbar = receiveEdit->verticalScrollBar();
    scrollbar->setValue(scrollbar->maximum());
}

void MainWindow::onClearReceiveButtonClicked()
{
    receiveEdit->clear();
}

void MainWindow::onClearSendButtonClicked()
{
    sendEdit->clear();
}

QString MainWindow::getClientInfo(QTcpSocket *client) const
{
    if (!client) return "未知";
    return QString("%1:%2").arg(client->peerAddress().toString()).arg(client->peerPort());
}

void MainWindow::updateConnectionStatus()
{
    if (tcpModeComboBox->currentIndex() == 0) { // Server模式
        if (tcpServer->isListening()) {
            statusLabel->setText("监听中");
        } else {
            statusLabel->setText("未监听");
        }
    } else { // Client模式
        if (tcpClient->state() == QAbstractSocket::ConnectedState) {
            statusLabel->setText("已连接");
        } else {
            statusLabel->setText("未连接");
        }
    }
}

2.3 .pro工程文件

复制代码
QT       += core gui network widgets

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++11

# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    main.cpp \
    mainwindow.cpp

HEADERS += \
    mainwindow.h

FORMS += \
    mainwindow.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

三、显示效果

相关推荐
特立独行的猫a34 分钟前
C/C++三方库移植到HarmonyOS平台详细教程
c语言·c++·harmonyos·napi·三方库·aki
SelectDB41 分钟前
湖仓一体:小米集团基于 Apache Doris + Apache Paimon 实现 6 倍性能飞跃
数据库·开源·github
听吉米讲故事1 小时前
开源AI编程工具Kilo Code的深度分析:与Cline和Roo Code的全面对比
开源·ai编程·cline·roo code·kilo code
谱写秋天1 小时前
VSCode+Qt+CMake详细地讲解
c++·ide·vscode·qt·编辑器
A7bert7772 小时前
【YOLOv5部署至RK3588】模型训练→转换RKNN→开发板部署
c++·人工智能·python·深度学习·yolo·目标检测·机器学习
oioihoii2 小时前
现代C++工具链实战:CMake + Conan + vcpkg依赖管理
开发语言·c++
黑客影儿2 小时前
使用UE5开发2.5D开放世界战略养成类游戏的硬件配置指南
开发语言·c++·人工智能·游戏·智能手机·ue5·游戏引擎
九离十3 小时前
STL——vector的使用(快速入门详细)
开发语言·c++·stl
君鼎3 小时前
More Effective C++ 条款08:理解各种不同意义的new和delete
c++
算家计算4 小时前
360智脑开源优化排序模型——360Zhinao-1.8B-Reranking本地部署教程,提升检索质量,减少大模型“幻觉”现象
人工智能·开源·360