当前位置: 首页 > article >正文

用QT做一个网络调试助手

文章目录

  • 前言
  • 一、TCP网络调试助手介绍
    • 1. 项目概述
    • 2. 开发流程
    • 3. TCP服务器的关键流程
    • 4. TCP客户端的关键流程
  • 二、实现UI界面
    • 1. 服务器界面
    • 2. 客户端界面
  • 三、实现代码框架
    • 1. 服务器代码
      • 1.1 初始化服务器地址
      • 1.2 开始监听
      • 1.3 与客户端连接
      • 1.4 接收客户端信息
      • 1.5 判断客户端状态(连接或断开)
      • 1.6 停止监听或断开退出
      • 1.7 显示连接多个客户端
      • 1.8 给多个客户端发消息
    • 2. 客户端代码
      • 2.1 连接服务器
      • 2.2 断开服务器
      • 2.3 自定义文本颜色
      • 2.4 实现收发数据

前言

完整代码:网络调试助手

一、TCP网络调试助手介绍

1. 项目概述

用 QT 模拟网络调试助手的服务器和客户端,可以进行数据收发。

服务器:

image.png

客户端:

image.png

2. 开发流程

image.png

3. TCP服务器的关键流程

工程建立,需要在.pro加入网络权限:

image.png

创建一个基于 QTcpServer 的服务端涉及以下关键步骤:

  1. 创建并初始化 QTcpServer 实例:
    • 实例化 QTcpServer 。
    • 调用 listen 方法在特定端口监听传入的连接。
  2. 处理新连接:
    • 为 newConnection 信号连接一个槽函数。
    • 在槽函数中,使用 nextPendingConnection 获取 QTcpSocket 以与客户端通信。
  3. 读取和发送数据:
    • 通过连接 QTcpSocket 的 readyRead 信号来读取来自客户端的数据。
    • 使用 write 方法发送数据回客户端。
  4. 关闭连接:
    • 在适当的时候关闭 QTcpSocket 。

4. TCP客户端的关键流程

工程建立,需要在.pro加入网络权限:

image.png

创建一个基于 QTcpSocket 的Qt客户端涉及以下步骤:

  1. 创建 QTcpSocket 实例:
    • 实例化 QTcpSocket 。
  2. 连接到服务器:
    • 使用 connectToHost 方法连接到服务器的IP地址和端口。
  3. 发送数据到服务器:
    • 使用 write 方法发送数据。
  4. 接收来自服务器的数据:
    • 为 readyRead 信号连接一个槽函数来接收数据。
  5. 关闭连接:
    • 关闭 QTcpSocket 连接。

二、实现UI界面

1. 服务器界面

首先我们先放入两个文本编辑框 Text Edit,一个用来发送数据,一个用来接收数据,并放入一个发送按钮与下面的文本编辑框水平对齐:

image.png

接着我们再放入三个按钮分别为监听,停止监听,断开,设置水平布局:

image.png

然后再放入标签 Label,组合框 Combo Box,行编辑框 Line Edit,设置水平布局:

image.png

最后再放入一个组合框 Combo Box,整体垂直布局,修改间距,设置标题:

image.png

2. 客户端界面

首先我们先放入两个文本编辑框 Text Edit,一个用来发送数据,一个用来接收数据,并放入一个发送按钮与下面的文本编辑框水平对齐:

image.png

接着我们再放入两个按钮分别为连接和断开,并放入两个标签 Label 跟两个行编辑框 Line Edit设置水平布局:

image.png

最后再总体进行垂直布局,调整间距大小,设置标题:

image.png

三、实现代码框架

1. 服务器代码

实现服务器框架步骤:

  1. 初始化服务端地址
  2. 开始监听
  3. 与客户端连接
  4. 接收客户端信息
  5. 判断客户端状态(连接或断开)
  6. 停止监听或断开退出
  7. 新建MyComboBox,使用组合框,显示多个客户端连接(记得提升)
  8. 实现发送消息给客户端,通过索引发送多个

1.1 初始化服务器地址

查看QT提供的帮助手册:
image.png

代码示例:

#include <QTcpServer>
#include <QNetworkInterface>

public:
    QTcpServer* server;

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

    server = new QTcpServer(this);
    
    // 初始化服务器地址
    QList<QHostAddress> address = QNetworkInterface::allAddresses();
    for (QHostAddress tmp : address)
    {
        if (tmp.protocol() == QAbstractSocket::IPv4Protocol)
        {
            ui->comboBoxIP->addItem(tmp.toString());
        }
    }
}

程序运行结果:

image.png

1.2 开始监听

实现步骤:

  1. 设置端口号
  2. 开始监听

代码示例:

#include <QMessageBox>

void Widget::on_btnListen_clicked()
{
    // 1. 设置端口号
    int port = ui->lineEditPort->text().toInt();

    // 2. 开始监听
    if (!server->listen(QHostAddress(ui->comboBoxIP->currentText()), port))
    {
        QMessageBox msgBox;
        msgBox.setWindowTitle("监听失败");
        msgBox.setText("端口号被占用");
        msgBox.exec();
        return;
    }

    ui->btnListen->setEnabled(false);
    ui->btnStopListen->setEnabled(true);
    ui->btnDisconnect->setEnabled(true);
}

程序运行结果:

image.png

1.3 与客户端连接

代码示例:

private slots:
    void on_newClient_connect();

#include <QTcpSocket>

void Widget::on_newClient_connect()
{
    if (server->hasPendingConnections())
    {
        // 从TcpSocket中获得客户端地址和端口号
        QTcpSocket *connection = server->nextPendingConnection();
        ui->textEditRecv->insertPlainText("客户端地址:" + connection->peerAddress().toString()
                                          + "客户端端口号:" + QString::number(connection->peerPort()) + "\n");
    }
}

程序运行结果:

image.png

1.4 接收客户端信息

代码示例:

private slots:
    void on_readyRead_handler();
    
void Widget::on_newClient_connect()
{
    if (server->hasPendingConnections())
    {
        // 从TcpSocket中获得客户端地址和端口号
        QTcpSocket *connection = server->nextPendingConnection();
        ui->textEditRecv->insertPlainText("客户端地址:" + connection->peerAddress().toString()
                                          + "客户端端口号:" + QString::number(connection->peerPort()) + "\n");

        // 接收客户端信息
        connect(connection, SIGNAL(readyRead()),this, SLOT(on_readyRead_handler()));

    }
}

void Widget::on_readyRead_handler()
{
    QTcpSocket *tmpSock = qobject_cast<QTcpSocket *>(sender());

    QByteArray revData = tmpSock->readAll();
    ui->textEditRecv->insertPlainText("客户端: " + revData);
}

程序运行结果:

image.png

1.5 判断客户端状态(连接或断开)

代码示例:

private slots:
    void mstateChange(QAbstractSocket::SocketState);
    
void Widget::on_newClient_connect()
{
    if (server->hasPendingConnections())
    {
        // 从TcpSocket中获得客户端地址和端口号
        QTcpSocket *connection = server->nextPendingConnection();
        ui->textEditRecv->insertPlainText("客户端地址:" + connection->peerAddress().toString()
                                          + "客户端端口号:" + QString::number(connection->peerPort()) + "\n");

        // 接收客户端信息
        connect(connection, SIGNAL(readyRead()),this, SLOT(on_readyRead_handler()));

        // 判断客户端状态
        connect(connection, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(mstateChange(QAbstractSocket::SocketState)));
    }
}

void Widget::mstateChange(QAbstractSocket::SocketState socketState)
{
    QTcpSocket *tmpSock = qobject_cast<QTcpSocket *>(sender());
        qDebug() << "client out In state:" << socketState;
    switch (socketState) {
    case QAbstractSocket::UnconnectedState:
            ui->textEditRecv->insertPlainText("客户端断开");
            tmpSock->deleteLater();
        break;
    case QAbstractSocket::ConnectedState:
    case QAbstractSocket::ConnectingState:
            ui->textEditRecv->insertPlainText("客户端接入");
        break;
    }
}

程序运行结果:

image.png

1.6 停止监听或断开退出

代码示例:

void Widget::on_btnStopListen_clicked()
{
    // 停止监听所有客户端
    QList<QTcpSocket*> tcpSocketClients = server->findChildren<QTcpSocket*>();
    for (QTcpSocket* tmp : tcpSocketClients)
    {
        tmp->close();
    }

    server->close();
    ui->btnListen->setEnabled(true);
    ui->btnStopListen->setEnabled(false);
    ui->btnDisconnect->setEnabled(false);
}

void Widget::on_btnDisconnect_clicked()
{
    on_btnStopListen_clicked();
    delete server;
    this->close();
}

程序运行结果:

image.png

1.7 显示连接多个客户端

新建 MyComboBox 类:

image.png

新建MyComboBox,使用组合框,显示多个客户端连接(记得提升)
代码示例:
myCombobox.h

#ifndef MYCOMBOBOX_H
#define MYCOMBOBOX_H


#include <QComboBox>
#include <QWidget>

class MyComboBox : public QComboBox
{
    Q_OBJECT

public:
    MyComboBox(QWidget *parent);

protected:
    void mousePressEvent(QMouseEvent *e) override;

signals:
    void on_ComboBox_clicked();
};

#endif // MYCOMBOBOX_H

myCombobox.cpp

#include "mycombobox.h"

#include <QMouseEvent>

MyComboBox::MyComboBox(QWidget *parent) : QComboBox(parent)
{

}

void MyComboBox::mousePressEvent(QMouseEvent *e)
{
    if(e->button() == Qt::LeftButton){
        emit on_ComboBox_clicked();
    }
    QComboBox::mousePressEvent(e);
}

widget.h

private slots:
    void mComboBox_refresh();

widget.cpp

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

    server = new QTcpServer(this);

    // 初始化服务器地址
    QList<QHostAddress> address = QNetworkInterface::allAddresses();
    for (QHostAddress tmp : address)
    {
        if (tmp.protocol() == QAbstractSocket::IPv4Protocol)
        {
            ui->comboBoxIP->addItem(tmp.toString());
        }
    }

    // 与客户端连接
    connect(server, SIGNAL(newConnection()), this, SLOT(on_newClient_connect()));
    ui->btnStopListen->setEnabled(false);
    ui->btnDisconnect->setEnabled(false);
    ui->btnSend->setEnabled(false);

    // 刷新 combox
    connect(ui->comboBoxChildren,&MyComboBox::on_ComboBox_clicked,this,&Widget::mComboBox_refresh);
}

void Widget::mComboBox_refresh()
{
    ui->comboBoxChildren->clear();
    QList<QTcpSocket*> tcpSocketClients = server->findChildren<QTcpSocket*>();

    for(QTcpSocket* tmp:tcpSocketClients){
        if(tmp!=nullptr)
            ui->comboBoxChildren->addItem(QString::number(tmp->peerPort()));
    }
    ui->comboBoxChildren->addItem("all");
}

程序运行结果:

image.png

1.8 给多个客户端发消息

image.png

代码示例:

private:
    int childIndex;
    
void Widget::on_btnSend_clicked()
{
    QList<QTcpSocket*> tcpSocketClients =  server->findChildren<QTcpSocket*>();
    // 当用户不选择向all,所有客户端进行发送的时候
    qDebug() << childIndex;
    if(ui->comboBoxChildren->currentText() != "all")
    {
        // 根据用户选择,找到指定客户端进行数据通信,通过childInde来查找,该变量在用户选择条目后触发的on_comboBoxChildren_activated去赋值
        tcpSocketClients[childIndex]->write(ui->textEditSend->toPlainText().toStdString().c_str());
    }
    else
    {
        // 遍历所有子客户端,并一一调用write函数,向所有客户端发送
        for(QTcpSocket* tmp:tcpSocketClients)
        {
           QByteArray sendData = ui->textEditSend->toPlainText().toLocal8Bit();
           tmp->write(sendData);
        }
    }
}

void Widget::on_comboBoxChildren_activated(int index)
{
    childIndex = index;
}

程序运行结果:

image.png

2. 客户端代码

实现客户端框架步骤:

  1. 设置定时器判断连接是否成功(成功,超时,错误)
  2. 设置断开
  3. 自定义文本颜色
  4. 实现发送和接收数据

2.1 连接服务器

  1. 初始化client
  2. 设置定时器判断是否与服务器连接

代码示例:

private slots:
    void on_btnConnect_clicked();

    void onConnected();

    void onError(QAbstractSocket::SocketError);

    void onTimeOUt();
    
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);


    ui->btnDisconnect->setEnabled(false);
    ui->btnSend->setEnabled(false);
    
    // 初始化client
    client = new QTcpSocket(this);
    connect(client,SIGNAL(readyRead()),this,SLOT(mRead_Data_From_Server()));
}

void Widget::on_btnConnect_clicked()
{
    client->connectToHost(ui->lineEdit_IP->text(),ui->lineEdit_Port->text().toInt());

    // 设置定时器判断是否与服务器连接
    timer = new QTimer(this);
    timer->setSingleShot(true);
    // timer->setInterval(5000);

    connect(timer, SIGNAL(timeout()),this,SLOT(onTimeOUt()));
    connect(client,SIGNAL(connected()),this,SLOT(onConnected()));
    connect(client,SIGNAL(error(QAbstractSocket::SocketError)),
                          this,SLOT(onError(QAbstractSocket::SocketError)));

    this->setEnabled(false);
    timer->start(1000);
}

void Widget::onTimeOUt()
{
    ui->textEdit_Recv->insertPlainText("连接超时");
    client->abort();
    this->setEnabled(true);
}

void Widget::onConnected()
{
    timer->stop();
    this->setEnabled(true);
    ui->textEdit_Recv->append("连接成功!");
    ui->btnConnect->setEnabled(false);
    ui->lineEdit_Port->setEnabled(false);
    ui->lineEdit_IP->setEnabled(false);
    ui->btnDisconnect->setEnabled(true);
    ui->btnSend->setEnabled(true);
}

void Widget::onError(QAbstractSocket::SocketError error)
{
    qDebug() << "连接错误:" << error;
    ui->textEdit_Recv->insertPlainText("连接出问题啦:"+client->errorString());
    this->setEnabled(true);
    on_btnConnect_clicked();
}

程序运行结果:

image.png

2.2 断开服务器

代码示例:

void Widget::on_btnDisconnect_clicked()
{
    client->disconnectFromHost();
    client->close();
    ui->textEdit_Recv->append("断开连接");
    ui->btnConnect->setEnabled(true);
    ui->btnDisconnect->setEnabled(false);
    ui->lineEdit_Port->setEnabled(true);
    ui->lineEdit_IP->setEnabled(true);
    ui->btnSend->setEnabled(true);
}

程序运行结果:

image.png

2.3 自定义文本颜色

代码示例:

private:
    void mInserTextByColor(Qt::GlobalColor color,QString str);
    
void Widget::mInserTextByColor(Qt::GlobalColor color,QString str)
{
    // 获取文本编辑器的光标位置,并将其存储在cursor变量中。
    QTextCursor cursor =  ui->textEdit_Recv->textCursor();
    
    // 创建一个QTextCharFormat对象,用于设置文本格式。
    QTextCharFormat format;
    
    // 使用setForeground方法设置文本的前景色为传入的颜色。
    format.setForeground(QBrush(QColor(color)));
    
    // 将格式化后的字符格式应用到光标上。
    cursor.setCharFormat(format);
    
    // 使用insertText方法在光标位置插入传入的字符串str。
    cursor.insertText(str);
}

2.4 实现收发数据

代码示例:

// 发送为红色字体
void Widget::on_btnSend_clicked()
{
    QByteArray sendData = ui->textEdit_Send->toPlainText().toUtf8();
    client->write(sendData);

    // 客户端发送数据,并以红色字体插入到文本编辑器中
    mInserTextByColor(Qt::red, sendData);
}


// 接收为黑色字体
void Widget::mRead_Data_From_Server()
{
    // 将文本编辑器的光标移动到文本末尾
    ui->textEdit_Recv->moveCursor(QTextCursor::End);

    // 确保光标可见
    ui->textEdit_Recv->ensureCursorVisible();

    // 客户端读取所有数据,并以黑色字体插入到文本编辑器中
    mInserTextByColor(Qt::black, client->readAll());
}

程序运行结果:

image.png


http://www.kler.cn/a/527849.html

相关文章:

  • 动态规划DP 最长上升子序列模型 合唱队形(题目分析+C++完整代码)
  • 计算机网络一点事(21)
  • 01.01、判定字符是否唯一
  • [STM32 - 野火] - - - 固件库学习笔记 - - -十二.基本定时器
  • 线段树 算法
  • 7.抽象工厂(Abstract Factory)
  • 芯片AI深度实战:让verilog不再是 AI 的小众语言
  • 开发过程中如何减少属性注释?
  • Cursor 背后的技术栈:从 VS Code 到 AI 集成
  • 数据结构 树1
  • LeetCode题练习与总结:不含连续1的非负整数--600
  • level-icmp(ping)详细过程_6
  • 输入一行字符,分别统计出其中英文字母,空格,数字和其他字符的个数。
  • 团体程序设计天梯赛-练习集——L1-028 判断素数
  • 课程设计|结构力学
  • 蓝桥杯真题k倍区间
  • C# Winform enter键怎么去关联button
  • 分层多维度应急管理系统的设计
  • 疯狂拆单词01
  • 文件上传功能(一)
  • 抽象类与抽象方法详解
  • Matrials studio 软件安装步骤(百度网盘链接)
  • 【RocketMQ 存储】- broker 端存储批量消息的逻辑
  • CE-PBFT:大规模联盟区块链的高可用一致性算法
  • Unet 改进:在encoder和decoder间加入TransformerBlock
  • 【leetcode强化练习·二叉树】同时运用两种思维解题