用QT做一个网络调试助手

文章目录

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

前言

完整代码:网络调试助手

一、TCP网络调试助手介绍

1. 项目概述

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

服务器:

客户端:

2. 开发流程

3. TCP服务器的关键流程

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

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

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

4. TCP客户端的关键流程

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

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

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

二、实现UI界面

1. 服务器界面

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

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

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

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

2. 客户端界面

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

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

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

三、实现代码框架

1. 服务器代码

实现服务器框架步骤:

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

1.1 初始化服务器地址

查看QT提供的帮助手册:

代码示例:

c++ 复制代码
#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());
        }
    }
}

程序运行结果:

1.2 开始监听

实现步骤:

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

代码示例:

c++ 复制代码
#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);
}

程序运行结果:

1.3 与客户端连接

代码示例:

c++ 复制代码
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");
    }
}

程序运行结果:

1.4 接收客户端信息

代码示例:

c++ 复制代码
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);
}

程序运行结果:

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

代码示例:

c++ 复制代码
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;
    }
}

程序运行结果:

1.6 停止监听或断开退出

代码示例:

c++ 复制代码
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();
}

程序运行结果:

1.7 显示连接多个客户端

新建 MyComboBox 类:

新建MyComboBox,使用组合框,显示多个客户端连接(记得提升)

代码示例:

myCombobox.h

c++ 复制代码
#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

c++ 复制代码
#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

c++ 复制代码
private slots:
    void mComboBox_refresh();

widget.cpp

c++ 复制代码
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");
}

程序运行结果:

1.8 给多个客户端发消息

代码示例:

c++ 复制代码
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;
}

程序运行结果:

2. 客户端代码

实现客户端框架步骤:

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

2.1 连接服务器

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

代码示例:

c++ 复制代码
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();
}

程序运行结果:

2.2 断开服务器

代码示例:

c++ 复制代码
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);
}

程序运行结果:

2.3 自定义文本颜色

代码示例:

c++ 复制代码
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 实现收发数据

代码示例:

c++ 复制代码
// 发送为红色字体
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());
}

程序运行结果:

相关推荐
BingoGo1 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack1 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
BingoGo2 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack2 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端
JaguarJack3 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo3 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
JaguarJack4 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel
郑州光合科技余经理5 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
DianSan_ERP5 天前
电商API接口全链路监控:构建坚不可摧的线上运维防线
大数据·运维·网络·人工智能·git·servlet
呉師傅5 天前
火狐浏览器报错配置文件缺失如何解决#操作技巧#
运维·网络·windows·电脑