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 协议的一些基本特点:
  5. 面向连接 :在数据传输之前, TCP 需要在发送方和接收方之间建立一个连接。这包括三次握手过程,确保两端都准备好进行数据传输。
  6. 可靠传输 : TCP 提供可靠的数据传输服务,这意味着它保证数据包准确无误地到达目的地。如果发生数据丢失或错误,TCP 会重新发送数据包。
  7. 顺序控制 : TCP 保证数据包的传输顺序。即使数据包在网络中的传输顺序被打乱,接收方也能按照正确的顺序重组这些数据。
  8. 流量控制 : TCP 使用窗口机制来控制发送方的数据传输速率,以防止网络过载。这有助于防止接收方被发送方发送的数据所淹没。
  9. 拥塞控制 : TCP 还包括拥塞控制机制,用来检测并防止网络拥塞。当网络拥塞发生时, TCP 会减少其数据传输速率。
  10. 数据分段 :大块的数据在发送前会被分割成更小的段,以便于传输。这些段会被独立发送并在接收端重新组装。
  11. 确认和重传 :接收方对成功接收的数据包发送确认( ACK )信号。如果发送方没有收到确认,它会重传丢失的数据包。
  12. 终止连接 :数据传输完成后, TCP 连接需要被正常关闭,这通常涉及到四次挥手过程。
    TCP 适用于需要高可靠性的应用,如网页浏览、文件传输、电子邮件等。然而,由于它的这些特性, TCP
    在处理速度上可能不如其他协议(如 UDP )那么快速。
    TCP 协议中的三次握手和四次挥手是建立和终止连接的重要过程。下面是它们的简要描述:
    三次握手(建立连接)
    三次握手的主要目的是在两台设备之间建立一个可靠的连接。它包括以下步骤:
  13. SYN :客户端向服务器发送一个 SYN (同步序列编号)报文来开始一个新的连接。此时,客户端进入SYN-SENT 状态。
  14. SYN-ACK :服务器接收到 SYN 报文后,回复一个 SYN-ACK (同步和确认)报文。此时服务器进入SYN-RECEIVED状态。
  15. ACK :客户端接收到 SYN-ACK 后,发送一个 ACK (确认)报文作为回应,并进入 ESTABLISHED (已建立)状态。服务器在收到这个ACK 报文后,也进入 ESTABLISHED 状态。这标志着连接已经建立。

    四次挥手(断开连接)
    四次挥手的目的是终止已经建立的连接。这个过程包括以下步骤:
  16. FIN :当通信的一方完成数据发送任务后,它会发送一个 FIN (结束)报文来关闭连接。发送完 FIN报文后,该方进入FIN-WAIT-1 状态。
  17. ACK :另一方接收到 FIN 报文后,发送一个 ACK 报文作为回应,并进入 CLOSE-WAIT 状态。发送 FIN报文的一方在收到ACK 后,进入 FIN-WAIT-2 状态。
  18. FIN :在等待一段时间并完成所有数据的发送后, CLOSE-WAIT 状态的一方也发送一个 FIN 报文来请求关闭连接。
  19. 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 服务端的核心代码
    主要步骤如下:
  20. 创建 QTcpServer 实例 :启动服务器并开始监听指定端口。
  21. 监听连接请求 :调用 listen() 方法使服务器监听特定的 IP 地址和端口。
  22. 接受连接 :当客户端尝试连接时, QTcpServer 产生一个信号。你需要实现一个槽( slot )来响应这个信号,并接受连接。
  23. 处理客户端连接 :每个连接的客户端都关联一个 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 ();
    }
    代码解释
  24. 创建 QTcpServer 对象 :在主函数中,直接创建了一个 QTcpServer 对象。
  25. 监听端口 :使用 listen() 方法监听所有接口上的 12345 端口。
  26. 处理新连接 :通过连接 newConnection 信号,当有新客户端连接时,会调用相应的槽函数。
  27. 读取数据 :为每个连接的客户端创建 QTcpSocket 对象,并连接 readyRead 信号以接收数据。
  28. 发送数据 :向客户端发送响应消息。
  29. 客户端断开连接时的处理 :使用 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();
复制代码
}
复制代码
相关推荐
liujing1023292913 分钟前
Day13_C语言基础&项目实战
c语言·开发语言
周振超的17 分钟前
c++编译第三方项目报错# pragma warning( disable: 4273)
开发语言·c++
JH30731 小时前
Java Stream API 在企业开发中的实战心得:高效、优雅的数据处理
java·开发语言·oracle
呆呆的小草4 小时前
Cesium距离测量、角度测量、面积测量
开发语言·前端·javascript
uyeonashi4 小时前
【QT系统相关】QT文件
开发语言·c++·qt·学习
冬天vs不冷5 小时前
Java分层开发必知:PO、BO、DTO、VO、POJO概念详解
java·开发语言
sunny-ll5 小时前
【C++】详解vector二维数组的全部操作(超细图例解析!!!)
c语言·开发语言·c++·算法·面试
猎人everest6 小时前
Django的HelloWorld程序
开发语言·python·django
嵌入式@秋刀鱼6 小时前
《第四章-筋骨淬炼》 C++修炼生涯笔记(基础篇)数组与函数
开发语言·数据结构·c++·笔记·算法·链表·visual studio code
嵌入式@秋刀鱼6 小时前
《第五章-心法进阶》 C++修炼生涯笔记(基础篇)指针与结构体⭐⭐⭐⭐⭐
c语言·开发语言·数据结构·c++·笔记·算法·visual studio code