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

P5 QT项目----会学网络调试助手服务端(5.1)

5.1 TCP 网络调试助手
5.1.1 项目概述
网络相关的一些基础概念 - 面试用
学习 QTcpServer
学习 QTcpClient
学习 TextEdit 特定位置输入文字颜色
学习网络通信相关知识点
复习巩固之前 UI 控件
程序运行如下图所示

5.1.2 开发流程
5.1.3 QTtcp 服务器的关键流程
工程建立,需要在 .pro 加入网络权限
创建一个基于 QTcpServer 的服务端涉及以下关键步骤:
1. 创建并初始化 QTcpServer 实例
实例化 QTcpServer
调用 listen 方法在特定端口监听传入的连接。
2. 处理新连接
newConnection 信号连接一个槽函数。
在槽函数中,使用 nextPendingConnection 获取 QTcpSocket 以与客户端通信。
3. 读取和发送数据
通过连接 QTcpSocket readyRead 信号来读取来自客户端的数据。
使用 write 方法发送数据回客户端。
4. 关闭连接 在适当的时候关闭 QTcpSocket
示例代码可能如下
class MyServer : public QObject {
        Q_OBJECT
public :
        MyServer () {
                QTcpServer * server = new QTcpServer ( this );
                connect ( server , & QTcpServer::newConnection , this , & MyServer::onNewConnection );
        server -> listen ( QHostAddress::Any , 1234 );
}
private slots :
        void onNewConnection () {
                QTcpSocket * clientSocket = server -> nextPendingConnection ();
                connect ( clientSocket , & QTcpSocket::readyRead , this , & MyServer::onReadyRead );
}
void onReadyRead () {
        QTcpSocket * clientSocket = qobject_cast < QTcpSocket *> ( sender ());
        // 读取数据
        QByteArray data = clientSocket -> readAll ();
        // 处理数据
        // ...
        }
};
5.1.4 TCP 协议
以下内容自省阅读和消化,主要在面试之前类似八股文问答,实际编程我们不需要关系这么多,
QTcpSocket 类底下的 API 已经做好所有的封装。
TCP (传输控制协议)是一种广泛使用的网络通信协议,设计用于在网络中的计算机之间可靠地传输数据。它是互联网协议套件的核心部分,通常与IP (互联网协议)一起使用,合称为 TCP/IP 。以下是 TCP 协议的一些基本特点:
1. 面向连接 :在数据传输之前, TCP 需要在发送方和接收方之间建立一个连接。这包括三次握手过程,确保两端都准备好进行数据传输。
2. 可靠传输 TCP 提供可靠的数据传输服务,这意味着它保证数据包准确无误地到达目的地。如果发生数据丢失或错误,TCP 会重新发送数据包。
3. 顺序控制 TCP 保证数据包的传输顺序。即使数据包在网络中的传输顺序被打乱,接收方也能按照正确的顺序重组这些数据。
4. 流量控制 TCP 使用窗口机制来控制发送方的数据传输速率,以防止网络过载。这有助于防止接收方被发送方发送的数据所淹没。
5. 拥塞控制 TCP 还包括拥塞控制机制,用来检测并防止网络拥塞。当网络拥塞发生时, TCP 会减少其数据传输速率。
6. 数据分段 :大块的数据在发送前会被分割成更小的段,以便于传输。这些段会被独立发送并在接收端重新组装。
7. 确认和重传 :接收方对成功接收的数据包发送确认( ACK )信号。如果发送方没有收到确认,它会重传丢失的数据包。
8. 终止连接 :数据传输完成后, TCP 连接需要被正常关闭,这通常涉及到四次挥手过程。
TCP 适用于需要高可靠性的应用,如网页浏览、文件传输、电子邮件等。然而,由于它的这些特性, TCP
在处理速度上可能不如其他协议(如 UDP )那么快速。
TCP 协议中的三次握手和四次挥手是建立和终止连接的重要过程。下面是它们的简要描述:
三次握手(建立连接)
三次握手的主要目的是在两台设备之间建立一个可靠的连接。它包括以下步骤:
1. SYN :客户端向服务器发送一个 SYN (同步序列编号)报文来开始一个新的连接。此时,客户端进入SYN-SENT 状态。
2. SYN-ACK :服务器接收到 SYN 报文后,回复一个 SYN-ACK (同步和确认)报文。此时服务器进入SYN-RECEIVED状态。
3. ACK :客户端接收到 SYN-ACK 后,发送一个 ACK (确认)报文作为回应,并进入 ESTABLISHED (已建立)状态。服务器在收到这个ACK 报文后,也进入 ESTABLISHED 状态。这标志着连接已经建立。
四次挥手(断开连接)
四次挥手的目的是终止已经建立的连接。这个过程包括以下步骤:
1. FIN :当通信的一方完成数据发送任务后,它会发送一个 FIN (结束)报文来关闭连接。发送完 FIN报文后,该方进入FIN-WAIT-1 状态。
2. ACK :另一方接收到 FIN 报文后,发送一个 ACK 报文作为回应,并进入 CLOSE-WAIT 状态。发送 FIN报文的一方在收到ACK 后,进入 FIN-WAIT-2 状态。
3. FIN :在等待一段时间并完成所有数据的发送后, CLOSE-WAIT 状态的一方也发送一个 FIN 报文来请求关闭连接。
4. ACK :最初发送 FIN 报文的一方在收到这个 FIN 报文后,发送一个 ACK 报文作为最后的确认,并进入TIME-WAIT状态。经过一段时间后,确保对方接收到了最后的 ACK 报文,该方最终关闭连接。
在这两个过程中,三次握手主要确保双方都准备好进行通信,而四次挥手则确保双方都已经完成通信并同意关闭连接。
5.1.5 Socket
Socket 不是一个协议,而是一种编程接口( API )或机制,用于在网络中实现通信。 Socket 通常在应用层和传输层之间提供一个端点,使得应用程序可以通过网络发送和接收数据。它支持多种协议,主要是TCP 和 UDP
以下是 Socket 的一些基本特点:
·类型 :有两种主要类型的 Sockets —— TCP Socket (面向连接,可靠)和 UDP Socket (无连接,不可靠)。
·应用 :在各种网络应用中广泛使用,如网页服务器、聊天应用、在线游戏等。
·编程语言支持 :大多数现代编程语言如 Python, Java, C++, 等都提供 Socket 编程的支持。
·功能 :提供了创建网络连接、监听传入的连接、发送和接收数据等功能。
·QT: QT 组件中, QTcpSocket 用来管理和实现 TCP Socket 通信, QUdpSocket 用来管理和实现
UDP Socket 通信
总之,Socket 是实现网络通信的基础工具之一,它抽象化了网络层的复杂性,为开发者提供了一种相对简单的方式来建立和管理网络连接。
5.2 UI 设计
5.3 网络通信核心代码
QTcpServer Qt 网络模块的一部分,用于构建 TCP 服务器。它提供了一种机制来异步监听来自客户端的连接。一旦接受了一个连接,服务器就可以与客户端进行数据交换。
5.3.1 创建 TCP 服务端的核心代码
主要步骤如下:
1. 创建 QTcpServer 实例 :启动服务器并开始监听指定端口。
2. 监听连接请求 :调用 listen() 方法使服务器监听特定的 IP 地址和端口。
3. 接受连接 :当客户端尝试连接时, QTcpServer 产生一个信号。你需要实现一个槽( slot )来响应这个信号,并接受连接。
4. 处理客户端连接 :每个连接的客户端都关联一个 QTcpSocket 对象,用于数据交换。
示例代码
#include <QTcpServer>
#include <QTcpSocket>
#include <QCoreApplication>
#include <QDebug>
int main ( int argc , char * argv []) {
        QCoreApplication a ( argc , argv );
        QTcpServer server ;
        // 监听端口
        if ( ! server . listen ( QHostAddress::Any , 12345 )) {
                qDebug () << "Server could not start" ;
                return - 1 ;
        }
        qDebug () << "Server started!" ;
        // 当有新连接时,执行相应的操作
        QObject::connect ( & server , & QTcpServer::newConnection , [ & ]() {
        QTcpSocket * client = server . nextPendingConnection ();
        QObject::connect ( client , & QTcpSocket::readyRead , [ client ]() {
                QByteArray data = client -> readAll ();
                qDebug () << "Received data:" << data ;
                client -> write ( "Hello, client!" );
        });
        QObject::connect ( client , & QTcpSocket::disconnected , client , & QTcpSocket::deleteLater ); 
        return a . exec ();
}
代码解释
1. 创建 QTcpServer 对象 :在主函数中,直接创建了一个 QTcpServer 对象。
2. 监听端口 :使用 listen() 方法监听所有接口上的 12345 端口。
3. 处理新连接 :通过连接 newConnection 信号,当有新客户端连接时,会调用相应的槽函数。
4. 读取数据 :为每个连接的客户端创建 QTcpSocket 对象,并连接 readyRead 信号以接收数据。
5. 发送数据 :向客户端发送响应消息。
6. 客户端断开连接时的处理 :使用 disconnected 信号确保客户端在断开连接时被适当地清理。
这个代码示例展示了如何使用 QTcpServer 创建一个基本的 TCP 服务器,而无需通过继承来扩展类。这种方式通常更简单,适用于不需要复杂处理的基本应用场景
5.4 TCP 服务端项目开发
核心代码
1.widget.cpp
#include "widget.h"
#include "ui_widget.h"
 
#include <QHostAddress>
#include <QDebug>
#include <QTcpSocket>
#include <QNetworkInterface>
#include <QMessageBox>
 
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->setLayout(ui->verticalLayout);
 
    //建立服务端
    tcpSever = new QTcpServer(this);
    connect(tcpSever,SIGNAL(newConnection()),this,SLOT(on_newClient_connect()));        /* 服务端建立连接 */
 
    /*鼠标左键刷新comboBox_children,将客户端的端口号标在comboBox_children */
    connect(ui->comboBox_children,&MyComboBox::myComboBox_clicked,this,&Widget::on_myComboBox_Refresh);
 
    //界面初始化
    ui->btnStopListen->setEnabled(false);
    ui->btnBreak->setEnabled(false);
 
    //QList<QHostAddress> QNetworkInterface::allAddresses()
    QList<QHostAddress> addresss = QNetworkInterface::allAddresses();                   /* 获取所有地址信息 */
    for(QHostAddress tmpAdd : addresss){                                                /* 遍历地址 */
        if(tmpAdd.protocol() == QAbstractSocket::IPv4Protocol){                         /* 选择IPv4地址 */
            ui->comboBox_Addr->addItem(tmpAdd.toString());                              /* 将选择的IPv4地址传递到comboBox上 */
        }
    }
 
}
 
Widget::~Widget()
{
    delete ui;
}
 
/* 有客户端接入函数 */
void Widget::on_newClient_connect()
{
    qDebug() << "newClient connect In";
 
    //当有客户端接入时,接收到newconnection信号
    if(tcpSever->hasPendingConnections()){
        //QTcpSocket *QTcpServer::nextPendingConnection()
        QTcpSocket *connection = tcpSever->nextPendingConnection();                             /* 通过这个函数找到服务端的地址和端口号 */
 
        //方便调试
        qDebug() << "Client Addr:" << connection->peerAddress().toString()
                 << "Client Port:" << connection->peerPort();
 
        //打印在textEdit_Rev上显示
        ui->textEdit_Rev->insertPlainText("\r\n服务端地址:"+connection->peerAddress().toString()
                                          +"\r\n服务端端口:"+QString::number(connection->peerPort()));
 
        //光标位置
        ui->textEdit_Rev->moveCursor(QTextCursor::End);
        ui->textEdit_Rev->ensureCursorVisible();
 
        //建立服务端接收客户端信息
        connect(connection,SIGNAL(readyRead()),this,SLOT(on_readyRead_handler()));
 
        //检测客户端是否断开,QAbstractSocket::disconnected()
        connect(connection,SIGNAL(disconnected()),this,SLOT(mdisConnect_Client()));
    }
}
 
/* 接收客户端信息处理函数 */
void Widget::on_readyRead_handler()
{
    qDebug() << "revData ";
 
    //由于QTcpSocket *connection是局部变量,无法接收到发送的信号
    QTcpSocket * tmpSocket = qobject_cast<QTcpSocket *>(sender());                          /* 接收发送信号函数 */
 
    QByteArray revData = tmpSocket->readAll();                                              /* readAll()该函数的返回值是QByteArray */
    ui->textEdit_Rev->insertPlainText("\n客户端: " + revData);                              /* 将接收的数据打印在textEdit_Rev */
 
    //光标位置
    ui->textEdit_Rev->moveCursor(QTextCursor::End);
    ui->textEdit_Rev->ensureCursorVisible();
}
 
/* 服务端检测客户端断开状态 */
void Widget::mdisConnect_Client()
{
    //由于QTcpSocket *connection是局部变量,无法接收到发送的信号
    QTcpSocket * tmpSocket = qobject_cast<QTcpSocket *>(sender());
    tmpSocket->deleteLater();                                      /* 清除tcpSever->findChildren遗留下的端口号0  */
 
    ui->textEdit_Rev->insertPlainText("\n客户端断开!");
 
    //光标位置
    ui->textEdit_Rev->moveCursor(QTextCursor::End);
    ui->textEdit_Rev->ensureCursorVisible();
}
 
 
void Widget::on_btnStartListen_clicked()
{
    //bool QTcpServer::listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0)
    //QHostAddress(const QString &address)
    //QHostAddress addr("192.168.43.83");
 
    int port = ui->lineEdit->text().toUInt();                                         /* 手动输入端口号 */
 
    //if(!tcpSever->listen(QHostAddress::Any,port)){
    if(!tcpSever->listen(QHostAddress(ui->comboBox_Addr->currentText()),port)){       /* 如果没有监听到有客户端接入,跳出该函数
                                                                                        有的话,就将客户端和客户端端口号发送给tcpSever的信号,
                                                                                        响应槽函数on_newClient_connect()*/
        qDebug() << "Listen Error";
        QMessageBox msgBox;
        msgBox.setWindowTitle("监听失败");
        msgBox.setText("端口号被占用");
        msgBox.exec();
        return;
    }
 
    //按键界面优化
    ui->btnStartListen->setEnabled(false);
    ui->btnStopListen->setEnabled(true);
    ui->btnBreak->setEnabled(true);
}
 
/* 服务端发送按键 */
void Widget::on_btn_Send_clicked()
{
    //找到对应的客户端来接收QList<T> QObject::findChildren(const QString &name = QString()
    QList<QTcpSocket *> tmpSocketClients = tcpSever->findChildren<QTcpSocket *>();
 
    //当用户不选择all,向特定的客户端进行发送
    if(ui->comboBox_children->currentText() != "all"){
 
        //根据用户选择客户端,通过ChildrenINdex来查找,该变量在用户选中条目后触发的on_comboBox_children_activated()区赋值
        tmpSocketClients[ChildrenINdex]->write(ui->textEdit_Send->toPlainText().toStdString().c_str());
    }
    else{
        //遍历所有客户端,并一一调用write含糊向客户端发送信息
        for(QTcpSocket *tmpSocket : tmpSocketClients){
            tmpSocket->write( ui->textEdit_Send->toPlainText().toStdString().c_str());         /* write(const char *data)将QString转为const char* */
        }
    }
}
 
/* 服务端可选择特定客户端发送信息 */
void Widget::on_myComboBox_Refresh()
{
    ui->comboBox_children->clear();
 
    QList<QTcpSocket *> tmpSocketClients = tcpSever->findChildren<QTcpSocket *>();
 
    //遍历所有客户端
    for(QTcpSocket *tmpSocket : tmpSocketClients){
 
        if(tmpSocket != nullptr)                        /* 没有空指针 */
            if(tmpSocket->peerPort() != 0)              /* 端口号不为0 */
                 ui->comboBox_children->addItem(QString::number( tmpSocket->peerPort()));
    }
    ui->comboBox_children->addItem("all");
}
 
/* 表示tmpSocketClients的下标 */
void Widget::on_comboBox_children_activated(int index)
{
    ChildrenINdex = index;
}
 
/* 停止监听 */
void Widget::on_btnStopListen_clicked()
{
    QList<QTcpSocket *> tmpSocketClients = tcpSever->findChildren<QTcpSocket *>();
    //遍历所有客户端
    for(QTcpSocket *tmpSocket : tmpSocketClients){
        tmpSocket->close();
  }
    tcpSever->close();
 
    ui->btnStartListen->setEnabled(true);
    ui->btnStopListen->setEnabled(false);
    ui->btnBreak->setEnabled(false);
}
 
/* 断开 */
void Widget::on_btnBreak_clicked()
{
    on_btnStopListen_clicked();
    delete tcpSever;
    this->close();
}
 

2.widget.h

#ifndef WIDGET_H
#define WIDGET_H
 
#include <QWidget>
#include <QTcpServer>
#include "mycombobox.h"
 
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
 
class Widget : public QWidget
{
    Q_OBJECT
 
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
 
    QTcpServer *tcpSever;
 
public slots:
    /* 有客户端接入函数 */
    void on_newClient_connect();
    /* 接收客户端信息处理函数 */
    void on_readyRead_handler();
    /* 服务端检测客户端断开状态 */
    void mdisConnect_Client();
 
    void on_myComboBox_Refresh();
 
private slots:
    /* 监听函数 */
    void on_btnStartListen_clicked();
    /* 服务端发送按键 */
    void on_btn_Send_clicked();
 
    void on_comboBox_children_activated(int index);
 
    void on_btnStopListen_clicked();
 
    void on_btnBreak_clicked();
 
private:
    Ui::Widget *ui;
 
    int ChildrenINdex = 0;
};
#endif // WIDGET_H
 

3.MyComboBox.cpp

#include "mycombobox.h"
 
#include <QMouseEvent>
 
MyComboBox::MyComboBox(QWidget *parent) : QComboBox(parent)
{
 
}
 
void MyComboBox::mousePressEvent(QMouseEvent *e)
{
    if(e->button() == Qt::LeftButton){
        emit myComboBox_clicked();
    }
    QComboBox::mousePressEvent(e);
}
 

4.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 myComboBox_clicked();
};
 
#endif // MYCOMBOBOX_H
 

5.main.cpp

#include "widget.h"
 
#include <QApplication>
 
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

http://www.xdnf.cn/news/1024363.html

相关文章:

  • 一文读懂:晶振不同等级的差异及对应最佳应用场景
  • 关于 WASM: WASM + JS 混合逆向流程
  • ffmpeg rtmp推流源码分析
  • Java的学习心得
  • 大型螺旋桨三维扫描尺寸检测逆向建模-中科米堆
  • 为什么传统 Bug 追踪系统正在被抛弃?
  • 一个完整的LSTM风光发电预测与并网优化方案,包含数据处理、模型构建、训练优化、预测应用及系统集成实现细节
  • frida对qt5(32位)实现简单HOOK
  • java中的类与对象
  • 文件系统1(Linux中)
  • 纪念2024.10-2025.6飞牛os的6次系统崩溃
  • 大矩阵可以分解为低秩矩阵的乘积
  • 什么是音频?
  • Git 分支管理规范
  • 【Python训练营打卡】day52 @浙大疏锦行
  • 《并查集》题集
  • AndroidManifest里面的lable标签
  • Flutter:加减乘除,科学计数法转换
  • 《第二章-内功筑基》 C++修炼生涯笔记(基础篇)数据类型与运算符
  • 前端给一行文字不设置宽度 ,不拆分 ,又能让某几个字在视觉下方居中显示
  • LeetCode 2529.正整数和负整数的最大计数
  • Appium + Java 测试全流程
  • Spring boot 的 maven 打包过程
  • Fiori 初学记录----怎么调用后端系统odata 服务实现简单的CURD
  • 使用特征线法求解一阶线性齐次偏微分方程组
  • 多模态大语言模型arxiv论文略读(121)
  • html+css+js趣味小游戏~(附源码)
  • 数据库分库分表情况下数据统计的相关问题详解(面试问答)
  • C++面试(9)-----反转链表
  • 2025年渗透测试面试题总结-字节跳动[实习]安全研发员(题目+回答)