QT——TCP网络调试助手

一.项目概述

  • 学习QTcpServer
  • 学习QTcpClicent
  • 学习TextEdit特定位置输入的文字颜色
  • 学习网络通信相关知识点

二.开发流程

1.首先我们实现当用户选择不同协议类型时不同的UI组件如何切换

绑定信号**&QComboBox::currentIndexChanged** 与QComBobox 组件,当QComboBox组件的索引变化时,执行下面槽函数,实现不同的UI组件之间的切换

cpp 复制代码
// 当 QComboBox 组件索引发生改变时的槽函数
void Widget::on_CurrentIndexChanged()
{
    // 获取当前选择的索引
    currentIndex = ui->comboBox_1->currentIndex();

    // 删除垂直布局中的第六个组件(如果存在)
    if (ui->verticalLayout->count() >= 6) {
        QWidget* widget = ui->verticalLayout->itemAt(5)->widget(); // 获取第六个控件
        if (widget) {
            ui->verticalLayout->removeWidget(widget); // 从布局中移除
            widget->deleteLater(); // 删除组件
        }
    }

    // 判断是客户端还是服务端
    if (currentIndex == 1) { // 用户选择了客户端=================================
        ui->label_2->setText("(2)本地主机地址");
        ui->label_3->setText("(3)远程主机地址");

        // 获取本地主机地址
        QString localHostName = QHostInfo::localHostName();
        QHostInfo info = QHostInfo::fromName(localHostName);
        QList<QHostAddress> addresses = info.addresses();

        // 清空 comboBox_2 并添加 IPv4 地址
        ui->comboBox_2->clear();
        for (const auto& address : addresses) {
            if (address.protocol() == QAbstractSocket::IPv4Protocol) {
                qDebug() << "IPv4 Address:" << address.toString();
                ui->comboBox_2->addItem(address.toString());
            }
        }

        // 创建并设置新的 QComboBox
        QComboBox* box = new QComboBox(this);
        box->addItem("192.168.56.1 :8080");
        box->setEditable(true);  // 设置为组件中的内容可修改

        // 将组件添加到垂直布局中
        ui->verticalLayout->addWidget(box);
        box->show();  // 显示新创建的 QComboBox

    } else if (currentIndex == 2) { // 用户选择了服务端=================================
        ui->label_2->setText("(2)本地主机地址");
        ui->label_3->setText("(3)本地主机端口");

        // 创建并设置新的 QLineEdit
        QLineEdit* edt = new QLineEdit(this);
        edt->setText("8080");

        // 将组件添加到垂直布局中
        ui->verticalLayout->addWidget(edt);
        edt->show();  // 显示新创建的 QLineEdit
    }else{
        ui->label_2->setText("(2)本地主机地址");
        ui->label_3->setText("(3)本地主机端口");

        // 创建并设置新的 QLineEdit
        QLineEdit* edt = new QLineEdit(this);
        edt->setText("8080");

        // 将组件添加到垂直布局中
        ui->verticalLayout->addWidget(edt);
        edt->show();  // 显示新创建的 QLineEdit
    }
}

2.实现打开/关闭按键图片的切换

方式一:通过其父类所提供的void setIcon(const QIcon &icon)函数去实现

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"

const QString ICON_PATH_MM = ":/pictures/mm.png"; // 图标路径常量
const QString ICON_PATH_PP = ":/pictures/pp.png"; // 图标路径常量

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    ui->pushButton->setCheckable(true);

    // 设置按钮透明背景
    ui->pushButton->setStyleSheet("QPushButton {"
                                  "background-color: transparent;"
                                  "border: none;" // 可选:去掉按钮边框
                                  "}");

    // 设置图标和大小
    setButtonIconAndSize(ICON_PATH_MM);
}

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

void Widget::on_pushButton_clicked(bool checked)
{
    // 根据按钮的 checked 状态切换图标
    setButtonIconAndSize(checked ? ICON_PATH_PP : ICON_PATH_MM);
}

// 封装设置图标和大小的逻辑
void Widget::setButtonIconAndSize(const QString &iconPath)
{
    QSize buttonSize = ui->pushButton->size(); // 获取按钮的当前大小
    ui->pushButton->setIconSize(buttonSize);   // 设置图标大小
    ui->pushButton->setIcon(QIcon(iconPath));   // 设置图标
}

方式二:重写QPushButton的事件

cpp 复制代码
#include "mybtnopen.h"
#include <QPainter>
#include <QMouseEvent>

myBtnOpen::myBtnOpen(QWidget *parent):QPushButton(parent)
{
    // 加载初始图片
    pic.load(":/pictures/mm.png");
    // 设置按钮的固定大小为图片的大小
    //setFixedSize(pic.size());
    //setFixedSize(100,100);
    // 刷新界面,触发 paintEvent 进行绘制
    update();
}

void myBtnOpen::paintEvent(QPaintEvent *e)
{
    // 创建一个 QPainter 对象,负责绘制图片
    QPainter painter(this);
    // 使用 QPainter 在按钮区域内绘制当前加载的图片
    painter.drawPixmap(rect(), pic);
}

void myBtnOpen::mousePressEvent(QMouseEvent *e)
{
    if(e->button() == Qt::LeftButton){
        //打开
        if(t == true){

            pic.load(":/pictures/mm.png");
            update();
            t = false;
        }else{
            pic.load(":/pictures/pp.png");
            update();
            t = true;
        }
    }
    QPushButton::mousePressEvent(e);
}

3.打开/关闭按键具体实现逻辑

  • 首先在构造函数中执行以下代码获取本地主机ip地址并显示到QComboBox组件中
cpp 复制代码
void Widget::getLocalhostIpAddress()
{
    QString localHostName = QHostInfo::localHostName();          //获取本地主机名称
    QHostInfo info = QHostInfo::fromName(localHostName);         //根据本地主机名称获取本地主机信息
    QList<QHostAddress> addresses = info.addresses();            //将本地主机的所有ip地址赋值给容器中

    // 清空 comboBox_2 并添加 IPv4 地址
    ui->comboBox_2->clear();
    for (const auto& address : addresses) {
        if (address.protocol() == QAbstractSocket::IPv4Protocol) {
            qDebug() << "IPv4 Address:" << address.toString();
            ui->comboBox_2->addItem(address.toString());
        }
    }
}
  • 接着在构造函数中进行信号与槽的连接。
  • 1.服务端有新的连接
  • 2.客户端连接成功
  • 3.客户端接收到新数据
  • 4.客户端与服务端断开连接
cpp 复制代码
server = new QTcpServer(this);
connect(server, &QTcpServer::newConnection, this, &Widget::on_newConnection); // 服务端有新连接时槽函数

connect(socket, &QTcpSocket::connected, this, [=]() { // 客户端连接到服务端槽函数
    ui->btn_open->setIcon(QIcon(":/pictures/pp.png"));
    ui->comboBox_1->setEnabled(false);
});

connect(socket, &QTcpSocket::readyRead, this, [=]() { // 客户端接收到数据槽函数
    int sum = 0;
    QByteArray data = socket->readAll(); // 读取接收到的数据
    sum = data.size();
    revCnt += sum;
    qDebug() << "Received from server:" << data;
    if (ui->checkBox_2->isChecked()) {
        data.append("\r\n");
    }
    if (ui->checkBox_4->isChecked()) { // Hex显示是否勾选,此处为勾选
        QByteArray tmpHexString = data.toHex().toUpper();
        ui->textEdit->insertPlainText(tmpHexString);
    } else {
        ui->textEdit->insertPlainText(data);
    }
    ui->label_revCnt->setText("接受: " + QString::number(revCnt));
});

connect(socket, &QTcpSocket::disconnected, this, [=]() { // 客户端与服务端断开连接槽函数
    qDebug() << "Disconnected from server.";
    socket->deleteLater(); // 断开连接时删除 socket
});

打开/关闭按键槽函数分为三大部分(服务端、客户端、UDP)

cpp 复制代码
//打开按键槽函数
void Widget::on_btn_open_clicked(bool checked)
{
    if(checked){
        //获取本地主机ip地址
        QString localIpAddress = ui->comboBox_2->currentText();
        QHostAddress ipaddress = (QHostAddress)localIpAddress;
        if(ui->comboBox_1->currentIndex() == 2){      //当前所选的是服务端===================================================
            QString honts = edt->text();
            qDebug() << "localIpAddress: " << localIpAddress;
            qDebug() << "honts: " << honts;
            quint16 port = honts.toUInt();
            server->listen(ipaddress, port); //开始监听
            ui->btn_open->setIcon(QIcon(":/pictures/pp.png"));
            ui->comboBox_1->setEnabled(false);
            t = 2;

        }else if(ui->comboBox_1->currentIndex() == 1){ //当前所选的是客户端===================================================
            QHostAddress remIpaddress;                 //远程主机ip地址
            quint16 remPort;                           //远程主机端口号
            //获取用户输入的远程主机ip地址与端口号
            QString data = box->currentText();
            // 使用 ':' 字符分割字符串
            QStringList parts = data.split(":");
            if (parts.size() == 2) {
                QString ipAddress = parts[0];
                QString port = parts[1];
                remIpaddress = (QHostAddress)ipAddress;   //远程主机ip地址
                remPort = port.toUInt();                  //远程主机端口号
            } else {
                qDebug() << "Invalid address format!";
            }
            socket = new QTcpSocket;
            socket->connectToHost(remIpaddress, remPort); //尝试连接服务端
            t = 1;

        }else{                                         //当前所选的是UDP===================================================
            QString honts = edt->text();
            qDebug() << "localIpAddress: " << localIpAddress;
            qDebug() << "honts: " << honts;
            t = 0;
        }
    }else{
        ui->btn_open->setIcon(QIcon(":/pictures/mm.png")); // 设置按钮图标
        ui->comboBox_1->setEnabled(true); // 启用下拉框

        if (t == 1) { // 断开客户端连接
            if (socket) {
                socket->disconnectFromHost(); // 断开与服务器的连接
                socket->waitForDisconnected(); // 等待断开完成
                socket->deleteLater(); // 删除 socket
                qDebug() << "Client socket disconnected.";
            }
        } else if (t == 2) { // 断开服务端连接
            if (server->isListening()) {
                server->close(); // 关闭服务器
                qDebug() << "Server stopped listening.";
            }
        }
    }

}

服务端有新的连接的槽函数如下

cpp 复制代码
void Widget::on_newConnection()
{
    // 获取新连接
    clientSocket = server->nextPendingConnection();

    if (clientSocket) {
        qDebug() << "New connection from:" << clientSocket->peerAddress().toString() << ":" << clientSocket->peerPort();

        // 连接信号和槽
        connect(clientSocket, &QTcpSocket::readyRead, this, [=]() {
            int sum = 0;
            QByteArray data = clientSocket->readAll();
            qDebug() << "Received data:" << data;

            sum = data.size();
            revCnt += sum;
            ui->label_revCnt->setText("接受: "+ QString::number(revCnt));


            if(ui->checkBox_4->isChecked()){             //Hex显示是否勾选,此处为勾选
                QByteArray tmpHexString = data.toHex().toUpper();
                if(ui->checkBox_2->isChecked()) tmpHexString.append("\r\n");
                ui->textEdit->append(tmpHexString);
            }else{
                if(ui->checkBox_2->isChecked()) data.append("\r\n");
                ui->textEdit->append(data);
            }


        });

        connect(clientSocket, &QTcpSocket::disconnected, this, [=]() {
            qDebug() << "Client disconnected:" << clientSocket->peerAddress().toString() << ":" << clientSocket->peerPort();
            clientSocket->deleteLater(); // 断开连接时删除 socket
        });
    } else {
        qDebug() << "Failed to accept new connection.";
    }
}

4.发送数据具体实现逻辑

4.1首先实现正常发送

cpp 复制代码
//发送按键槽函数
void Widget::on_pushButton_send_clicked()
{
    //(1)先获取用户输入的要发送的内容
    QString data = ui->textEdit_sendData->toPlainText();
    QByteArray arrayData = data.toLocal8Bit();
    int cnt = 0;
    //(2)判断Hex发送是否勾选,此处为勾选
    if (ui->checkBox_9->isChecked()) {
        // a.检查字节数是否是偶数
        if (0 != arrayData.size() % 2) {
            ui->label_state->setText("input error!");
            return;
        }
        // b.检查是否符合Hex表达
        for (char c : arrayData) {
            if (!isxdigit(c)) {
                ui->label_state->setText("input error!");
                return;
            }
        }
        // c.转化为16进制
        arrayData = QByteArray::fromHex(arrayData);
    }
    //(3).开始发送
    if (t == 1) {        // 客户端发送数据
        cnt = socket->write(arrayData);
    } else if (t == 2) { // 服务端发送数据
        cnt = clientSocket->write(arrayData);
    }

    //(4)判断是否发送成功
    if (cnt == -1) {
        ui->label_state->setText("send error!");
    } else {
        if (ui->checkBox_8->isChecked()) {
            ui->textEdit_sendData->clear();
        }
        sendCnt += cnt;
        ui->label_state->setText("send OK!");
        ui->label_sendCnt->setText("发送: " + QString::number(sendCnt));
    }
}

4.2定时发送

通过定时器实现,当用户勾选定时发送组件时,初始化定时器,设置定时器定时时间,启动定时器。在构造函数中绑定定时器的超时信号与槽函数,通过Lambda表达式调用发送函数既可以

cpp 复制代码
//定时发送槽函数
void Widget::on_checkBox_10_clicked(bool checked)
{
    if(checked){
        //设置定时器定时时间
        timer->setInterval(ui->lineEdit_msData->text().toInt());
        //启动定时器
        timer->start();
    }else{
        //停止定时器
        timer->stop();
    }
}
cpp 复制代码
//发送按键槽函数
void Widget::on_pushButton_send_clicked()
{
    //(1)先获取用户输入的要发送的内容
    QString data = ui->textEdit_sendData->toPlainText();
    QByteArray arrayData = data.toLocal8Bit();
    int cnt = 0;
    //(2)判断Hex发送是否勾选,此处为勾选
    if (ui->checkBox_9->isChecked()) {
        // a.检查字节数是否是偶数
        if (0 != arrayData.size() % 2) {
            ui->label_state->setText("input error!");
            return;
        }
        // b.检查是否符合Hex表达
        for (char c : arrayData) {
            if (!isxdigit(c)) {
                ui->label_state->setText("input error!");
                return;
            }
        }
        // c.转化为16进制
        arrayData = QByteArray::fromHex(arrayData);
    }
    //(3).开始发送
    if (t == 1) {        // 客户端发送数据
        cnt = socket->write(arrayData);
    } else if (t == 2) { // 服务端发送数据
        cnt = clientSocket->write(arrayData);
    }

    //(4)判断是否发送成功
    if (cnt == -1) {
        ui->label_state->setText("send error!");
    } else {
        if (ui->checkBox_8->isChecked()) {
            ui->textEdit_sendData->clear();
        }
        sendCnt += cnt;
        ui->label_state->setText("send OK!");
        ui->label_sendCnt->setText("发送: " + QString::number(sendCnt));
    }
}
相关推荐
阑梦清川几秒前
JavaEE初阶---网络原理之TCP篇(二)
服务器·网络·tcp/ip
canyuemanyue3 分钟前
C++单例模式
开发语言·c++·单例模式
秋恬意18 分钟前
Java 反射机制详解
java·开发语言
黑不溜秋的21 分钟前
C++ 模板专题 - 标签分派(Tag Dispatching)
开发语言·c++·算法
skywind31 分钟前
为什么 C 语言数组是从 0 开始计数的?
c语言·开发语言·网络·c++
尘浮生1 小时前
Java项目实战II基于Spring Boot的火锅店管理系统设计与实现(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·后端·微信小程序·旅游
wrx繁星点点1 小时前
桥接模式:解耦抽象与实现的利器
android·java·开发语言·jvm·spring cloud·intellij-idea·桥接模式
千里马-horse1 小时前
在OpenCL 中输出CLinfo信息
开发语言·c++·算法·opencl·1024程序员节
试行1 小时前
C#实现ClientWebSocket请求服务端,接收完所有返回值,解决接收数据不全
开发语言·c#