基于TCP/QT/C++的网络调试助手测试报告

目录

一、项目介绍

1、项目背景

2、项目功能

3、开发流程

二、UI设计

1、服务端

2、客户端

三、代码实现

1、QT中使用TCP的关键流程

2、服务器开发

3、客户端开发

四、结果测试

五、项目总结


一、项目介绍

1、项目背景

在网络应用开发和嵌入式系统调试过程中,开发者经常需要模拟 客户端-服务器 交互,以验证数据传输的正确性和稳定性。然而,传统的网络调试工具通常难以同时支持多个客户端的管理,或者缺乏灵活的调试能力。因此,本项目基于 Qt 框架实现了一款 功能强大、易于使用 的网络调试助手,旨在提高开发和测试效率。

2、项目功能

  • 支持TCP连接 :使用**Socket网络编程,**客户端和服务器的连接,是通过网络连接起来的,数据传输方式TCP,比较可靠。

  • 监听指定 IP 和端口,支持多个客户端连接

  • 管理多个客户端,支持客户端连接、断开及状态监控

  • 收发消息,支持单个或所有客户端的消息广播

  • 断开连接及停止监听,确保资源释放

本项目为网络开发人员提供了一个高效、直观的调试工具,能够大幅提升 网络通信协议的调试效率 ,同时也可用于 嵌入式设备调试、物联网设备通信测试 等场景。

3、开发流程

二、UI设计

1、服务端

使用的控件命名如下图所示:

2、客户端

使用的控件命名如下图所示:

在设计布局的时候,可以大体上从上到下分几块,每个小块里面先水平布局,每个小块布局好之后,在所有的小块进行垂直布局,在调整每个小块的占比,可以一点一点调整,上下左右再留一点空白,这样布局会看着比较舒服。

这样设计出来的窗口是固定大小,那么怎样可以在用户使用的时候,鼠标拉动窗口,控件可以跟着改变呢?

其实很简单,只需要一行代码就可以搞定,在Widget的构造方法中添加:

复制代码
this->setLayout(ui->verticalLayout);

三、代码实现

QTcpServer 是 Qt 框架中用于实现 TCP 服务器功能的类,属于 QtNetwork 模块。它是 Qt 网络编程的重要组成部分,主要用于创建和管理 TCP 服务器端。

主要功能

  • TCP 服务端监听 :使用 QTcpServer 可以轻松地创建一个 TCP 服务器,监听特定的 IP 地址和端口。

  • 连接管理 :当客户端尝试连接到服务器时,QTcpServer 提供信号(如 newConnection())通知应用程序有新的连接请求。你可以通过重写虚函数或使用信号与槽来处理这些事件。

  • 数据通信 :连接建立后,可以通过派生自 QTcpSocket 的套接字类来进行客户端和服务器之间的数据收发。

  • 多线程支持:Qt 的网络模块默认是异步的,这意味着服务器可以在不阻塞主线程的情况下处理多个连接。

1、QT中使用TCP的关键流程

首先需要在创建项目后,在.pro文件中加入network网络权限

2、服务器开发

复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QNetworkInterface>
#include<QTcpSocket>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    //初始化 UI 界面的代码,它的作用是加载 Qt Designer 设计的 UI 布局,并将界面元素绑定到 ui 指针中
    ui->setupUi(this);
    //布局自适应窗口
    this->setLayout(ui->verticalLayout);
    
    //实例化一个QTcpServer对象
    server = new QTcpServer(this);

    //绑定信号与槽
    connect(ui->comboBoxChildren,&MyComboBox::on_comboBox_clicked,this,&Widget::mComboBox_refresh);
    connect(server,SIGNAL(newConnection()),this,SLOT(on_newClient_connect()));
    //一些细节的修改,在没有客户端连接的时候,停止监听、断开连接以及发送按钮不可用
    ui->btnLineout->setEnabled(false);
    ui->btnStopListen->setEnabled(false);
    ui->btnSend->setEnabled(false);

    //获取本机的所有网络接口地址,并将 IPv4 地址添加到 comboBoxAddr 下拉框中,供用户选择服务器监听的地址。
    QList<QHostAddress> addresss = QNetworkInterface::allAddresses();
    for(QHostAddress tmp :addresss){
        if(tmp.protocol()==QAbstractSocket::IPv4Protocol){
            ui->comboBoxAddr->addItem(tmp.toString());
        }
    }

}

Widget::~Widget()
{
    //析构
    delete ui;
}

void Widget::on_newClient_connect()
{
    qDebug()<<"newClient In!";
    //如果有新连接进来
    if(server->hasPendingConnections()){
        //获得客户端连接信息
        QTcpSocket *connection = server->nextPendingConnection();
        qDebug()<<"client Addr:"<< connection->peerAddress().toString() <<"port:"<<connection->peerPort();
        ui->textEditRev->insertPlainText("客户端地址:"+connection->peerAddress().toString()+
                                         "\n客户端端口号:"+QString::number(connection->peerPort())+"\n");
        connect(connection,SIGNAL(readyRead()),this,SLOT(on_readyRead_handler()));
        connect(connection,SIGNAL(disconnected()),this,SLOT(mdisconnected()));
        connect(connection,SIGNAL(stateChange(QAbstractSocket::SocketState)),
                this,SLOT(mststeChanged(QAbstractSocket::SocketState socketState)));
        //有新连接进来的时候在comboBoxChildren添加新条目,并显示当前最新进入的连接
        ui->comboBoxChildren->addItem(QString::number(connection->peerPort()));
        ui->comboBoxChildren->setCurrentText(QString::number(connection->peerPort()));
        
        if(!ui->btnSend->isEnabled()){
            ui->btnSend->setEnabled(true);
        }
    }
}

void Widget::on_readyRead_handler()
{
    //获取当前连接对象,sender() 返回当前触发信号的对象,但它是一个 QObject* 类型。转换为 QTcpSocket*。
    QTcpSocket *tmpSock = qobject_cast<QTcpSocket*>(sender());
    QByteArray revData = tmpSock->readAll();
    //光标移到末尾
    ui->textEditRev->moveCursor(QTextCursor::End);
    ui->textEditRev->ensureCursorVisible();
    //将数据显示到textEditRev
    ui->textEditRev->insertPlainText("客户端:"+revData);
}

void Widget::mdisconnected()
{
    //当客户端断开连接时,删除相应的 QTcpSocket 对象,并在 UI 上显示 客户端断开!
    QTcpSocket *tmpSock = qobject_cast<QTcpSocket *>(sender());
    ui->textEditRev->insertPlainText("客户端断开!");
    tmpSock->deleteLater();
}

void Widget::mststeChanged(QAbstractSocket::SocketState socketState)
{
    int tmpIndex;
    QTcpSocket *tmpSock = qobject_cast<QTcpSocket *>(sender());
    switch (socketState) {
    //如果客户端断开,通过端口号查找comboBoxChildren的索引,并删除条目,最后删除相应的 QTcpSocket 对象
    case QAbstractSocket::UnconnectedState:
        //    case QAbstractSocket::ClosingState:
        ui->textEditRev->insertPlainText("客户端断开!");
        tmpIndex = ui->comboBoxChildren->findText(QString::number(tmpSock->peerPort()));
        ui->comboBoxChildren->removeItem(tmpIndex);
        tmpSock->deleteLater();
        //如果所有客户端都断开连接后,发送键不可用
        if(ui->comboBoxChildren->count()==0)
            ui->btnSend->setEnabled(false);
        break;
    case QAbstractSocket::ConnectedState:
    case QAbstractSocket::ConnectingState:
        ui->textEditRev->insertPlainText("客户端接入!");
        break;

    }
}

void Widget::mComboBox_refresh()
{
    ui->comboBoxChildren->clear();
    //接收所有连接的客户端,将端口号添加下拉框条目中
    QList<QTcpSocket*> tcpSocketClients = server->findChildren<QTcpSocket*>();
    for(QTcpSocket* tmp:tcpSocketClients){
        if(tmp!=nullptr)
            if(tmp->peerPort()!=0)
                ui->comboBoxChildren->addItem(QString::number(tmp->peerPort()));
    }
    //all支持向所有客户端通信
    ui->comboBoxChildren->addItem("all");
}


void Widget::on_btnListen_clicked()
{
    //服务器开始监听指定的 IP 地址和端口,等待客户端连接
    //    QHostAddress addr("");
    int port = ui->lineEditPort->text().toInt();
    //    if(!server->listen(QHostAddress::Any,port))
    if(!server->listen(QHostAddress(ui->comboBoxAddr->currentText()),port)){
        qDebug()<< "listenError";
        QMessageBox msgBox;
        msgBox.setWindowTitle("监听失败");
        msgBox.setText("端口号被占用");
        msgBox.exec();
        return;
    }
    
    //监听键不可用,停止监听、断开连接可用
    ui->btnListen->setEnabled(false);
    ui->btnLineout->setEnabled(true);
    ui->btnStopListen->setEnabled(true);

}

void Widget::on_btnSend_clicked()
{
    QList<QTcpSocket*> tcpSocketClients = server->findChildren<QTcpSocket*>();
    //如果当前无连接,提示发送失败
    if(tcpSocketClients.isEmpty()){
        QMessageBox msgBox;
        msgBox.setWindowTitle("发送错误!");
        msgBox.setText("当前无连接!");
        msgBox.exec();
        ui->btnSend->setEnabled(false);
        return;
    }
    if(ui->comboBoxChildren->currentText() != "all"){
        //根据用户选择,找到指定客户端进行通信,通过childIndex来查找,该变量在用户选择条目后触发的on_comboBoxChildren_activated去赋值
        QString currentName = ui->comboBoxChildren->currentText();
        for(QTcpSocket* tmp:tcpSocketClients){
            if(QString::number(tmp->peerPort())==currentName){
                tmp->write(ui->textEditSend->toPlainText().toStdString().c_str());
            }
        }
//        tcpSocketClients[childIndex]->write(ui->textEditSend->toPlainText().toStdString().c_str());
    }else{
        //遍历所有子客户端,并一一调用write函数,向所有客户端发送消息
        for(QTcpSocket* tmp:tcpSocketClients){
            tmp->write(ui->textEditSend->toPlainText().toStdString().c_str());
        }
    }

}


void Widget::on_btnStopListen_clicked()
{
    //关闭所有的客户端连接
    QList<QTcpSocket*> tcpSocketClients = server->findChildren<QTcpSocket *>();
    for(QTcpSocket* tmp:tcpSocketClients){
        tmp->close();
    }
    //关闭服务器,停止监听
    server->close();
    ui->btnListen->setEnabled(true);
    ui->btnLineout->setEnabled(false);
    ui->btnStopListen->setEnabled(false);
}

void Widget::on_btnLineout_clicked()
{
    //停止监听
    on_btnListen_clicked();
    //释放服务器对象 server,避免内存泄漏
    delete server;
    //关闭窗口
    this->close();
}

给代码添加了详细的注释,有问题欢迎一起讨论。】

3、客户端开发

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

#include <QTimer>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    //初始化ui界面
    ui->setupUi(this);
    //布局自适应
    this->setLayout(ui->verticalLayout);
    //设置按钮状态
    ui->btndiscon->setEnabled(false);
    ui->btnSend->setEnabled(false);
    //实例化一个QTcpSocket对象
    client = new QTcpSocket(this);
    connect(client,SIGNAL(readyRead()),this,SLOT(mRead_Data_From_Server()));
}

Widget::~Widget()
{
    //析构
    delete ui;
}


void Widget::on_btnConnect_clicked()
{
    //通过ip端口号创建连接
    client->connectToHost(ui->lineEditIPAddr->text(),ui->lineEditPort->text().toInt());
    //实例化一个计时器,五秒没连接成功给提示
    timer = new QTimer(this);
    timer->setInterval(5000);
    connect(timer,SIGNAL(timeout()),this,SLOT(onTimeOut()));
    connect(client,SIGNAL(connected()),this,SLOT(onConnected()));
    connect(client,SIGNAL(QAbstractSocket::SocketError),this,SLOT(onError(QAbstractSocket::SocketError)));
    //连接按钮按下的时候全窗口不可用,等成功连接时设置可用
    this->setEnabled(false);
    //启动定时器
    timer->start();
}

void Widget::mRead_Data_From_Server()
{
    //读取数据
    ui->textEditRev->moveCursor(QTextCursor::End);
    ui->textEditRev->ensureCursorVisible();
    //服务端数据接收之后用黑色字体显示
    mInsertTextByColor(Qt::black,client->readAll());

}

void Widget::on_btnSend_clicked()
{
    //发送数据
    QByteArray sendData = ui->textEditSend->toPlainText().toUtf8();
    client->write(sendData);
    //客户端发送数据之后用红色字体显示
    mInsertTextByColor(Qt::red,sendData);
}

void Widget::on_btndiscon_clicked()
{
    //关闭客户端
    client->close();
    ui->textEditRev->append("终止连接!");
    //设置按钮状态
    ui->btnConnect->setEnabled(true);
    ui->lineEditIPAddr->setEnabled(true);
    ui->lineEditPort->setEnabled(true);
    ui->btndiscon->setEnabled(false);
    ui->btnSend->setEnabled(false);
}

void Widget::onConnected()
{
    //连接成功之后关闭计时器,设置按钮状态
    timer->stop();
    this->setEnabled(true);
    ui->textEditRev->append("连接成功!");
    ui->btnConnect->setEnabled(false);
    ui->lineEditIPAddr->setEnabled(false);
    ui->lineEditPort->setEnabled(false);
    ui->btndiscon->setEnabled(true);
    ui->btnSend->setEnabled(true);
}

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

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

void Widget::mInsertTextByColor(Qt::GlobalColor color,QString str)
{
    QTextCursor cursor = ui->textEditRev->textCursor();
    QTextCharFormat format;
    format.setForeground(QBrush(QColor(color)));
    cursor.setCharFormat(format);
    cursor.insertText(str);
}

四、结果测试

连接成功

服务端客户端进行通信

服务器停止监听断开连接

客户端断开连接

五、项目总结

|--------------|--------|---------------------------------------------------------|
| 类别 | 功能 | API/方法 |
| TcpServer | 监听 | bool listen(const QHostAddress& address, quint16 port) |
| TcpServer | 连接 | QTcpSocket*nextPendingConnection() |
| TcpServer | 关闭 | void close() |
| TcpServer 信号 | 新连接 | newConnection() |
| TcpScoket | 连接 | void connectToHost(const QString &host, quint16 port) |
| TcpScoket | 发送 | qint64 write(const QByteArray& data) |
| TcpScoket | 接收 | QByteArray readAll() |
| TcpScoket | 断开 | void disconnectFromHost() |
| TcpScoket 信号 | 连接 成功 | connected() |
| TcpScoket 信号 | 断开 连接 | disconnected() |
| TcpScoket 信号 | 有可读数据 | readyRead() |
| TcpScoket 信号 | 成功写入数据 | bytesWritten(qint64 bytes) |
| EditText | 读数据 | String getText() |
| EditText | 写数据 | void setText(String text) |
| EditText | 改变颜色 | void changeTextColor(int start, int end, int color) |

相关推荐
神马行空2 分钟前
一文解读DeepSeek的安全风险、挑战与应对策略
网络·人工智能·安全·大模型·deepseek
wanhengidc4 分钟前
视频网站服务器网络连接不稳定该如何解决?
运维·服务器·网络·小程序
为你写首诗ge11 分钟前
【Unity网络编程知识】使用Socket实现简单UDP通讯
网络·unity·udp
5:0021 分钟前
Linux:(模拟HTTP协议,GET和POST方法,Http的状态码)
网络协议·http·https
十五年专注C++开发35 分钟前
双指针技巧在C++中的应用:从基础到进阶
开发语言·数据结构·c++
ssr——ssss36 分钟前
网络华为HCIA+HCIP 动态路由协议
网络·华为·智能路由器
黑客笔记1 小时前
第4章 IP网络扫描(网络安全评估)
网络·tcp/ip·web安全
GCKJ_08241 小时前
观成科技:海莲花利用MST投递远控木马
网络·安全·web安全
网安秘谈1 小时前
深入解析SSL/TLS证书:构建现代网络安全的密码学基石1.1 TLS 1.3握手流程优化
网络协议·http·https