1 TCP网络调试助手

1.1 项目概述

  • 网络相关的一些基础概念-面试用
  • 学习QTcpServer
  • 学习QTcpClient
  • 学习TextEdit特定位置输入文字颜色
  • 学习网络通信相关知识点
  • 复习巩固之前UI控件

程序运行如下图所示

在这里插入图片描述

1.2 开发流程

在这里插入图片描述

1.3 QTtcp服务器的关键流程

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

在这里插入图片描述

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

  1. 创建并初始化 QTcpServer 实例:

实例化 QTcpServer 。调用 listen 方法在特定端口监听传入的连接。

  1. 处理新连接:

为 newConnection 信号连接一个槽函数。在槽函数中,使用nextPendingConnection 获取 QTcpSocket 以与客户端通信。

  1. 读取和发送数据:

通过连接 QTcpSocket 的 readyRead 信号来读取来自客户端的数据。
使用 write 方法发送数据回客户端。

  1. 关闭连接:

在适当的时候关闭 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();
        // 处理数据
        // ...
    }
};

确保在使用 QTcpServer 和 QTcpSocket 时妥善处理网络错误和异常情况。

1.4 QTtcp客户端的关键流程

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

在这里插入图片描述

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

  1. 创建 QTcpSocket 实例:

实例化 QTcpSocket 。

  1. 连接到服务器:

使用 connectToHost 方法连接到服务器的IP地址和端口。

  1. 发送数据到服务器:

使用 write 方法发送数据。

  1. 接收来自服务器的数据:

为 readyRead 信号连接一个槽函数来接收数据。

  1. 关闭连接:

关闭 QTcpSocket 连接。

示例代码如下:

class MyClient : public QObject {
    Q_OBJECT
public:
    MyClient() {
        QTcpSocket *socket = new QTcpSocket(this);
        connect(socket, &QTcpSocket::readyRead, this, &MyClient::onReadyRead);
        socket->connectToHost("server_address", 1234);
    }
private slots:
    void onReadyRead() {
        QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
        QByteArray data = socket->readAll();
        // 处理接收到的数据
        // ...
    }
};

这个客户端尝试连接到指定的服务器地址和端口,然后等待和处理来自服务器的数据。记得根据需要管理和处理网络错误和异常情况。

1.5 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报文,该方最终关闭连接。

在这里插入图片描述

在这两个过程中,三次握手主要确保双方都准备好进行通信,而四次挥手则确保双方都已经完成通信并同意关闭连接。

1.6 Socket

Socket 不是一个协议,而是一种编程接口(API)或机制,用于在网络中实现通信。Socket 通常在应用层和传输层之间提供一个端点,使得应用程序可以通过网络发送和接收数据。它支持多种协议,主要是TCP 和 UDP。

以下是 Socket 的一些基本特点:

  1. 类型:有两种主要类型的 Sockets —— TCP Socket(面向连接,可靠)和 UDP Socket(无连接,不可靠)。
  2. 应用:在各种网络应用中广泛使用,如网页服务器、聊天应用、在线游戏等。编程语言支持:大多数现代编程语言如 Python, Java, C++, 等都提供 Socket 编程的支持。
  3. 功能:提供了创建网络连接、监听传入的连接、发送和接收数据等功能。QT: 在QT组件中,QTcpSocket用来管理和实现TCP Socket通信QUdpSocket用来管理和实现UDP Socket通信。

总之,Socket 是实现网络通信的基础工具之一,它抽象化了网络层的复杂性,为开发者提供了一种相对简单的方式来建立和管理网络连接。

2 UI设计

服务端界面

在这里插入图片描述

客户端界面

在这里插入图片描述

3 网络通信核心代码

QTcpServer 是 Qt 网络模块的一部分,用于构建 TCP 服务器。它提供了一种机制来异步监听来自客户端的连接。一旦接受了一个连接,服务器就可以与客户端进行数据交换。

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 服务器,而无需通过继承来扩展类。这种方式通常更简单,适用于不需要复杂处理的基本应用场景。

3.2 创建TCP客户端的核心代码

为了使客户端代码更具模块化和响应性,可以使用 Qt 的信号与槽机制。这种方法允许客户端以事件驱动的方式响应网络事件,如连接建立、数据接收等。下面是一个使用信号与槽的 TCP 客户端示例。

示例代码

#include <QTcpSocket>
#include <QCoreApplication>
#include <QDebug>
class TcpClient : public QObject {
    Q_OBJECT
public:
    TcpClient(const QString &host, quint16 port) {
        connect(&socket, &QTcpSocket::connected, this, &TcpClient::onConnected);
        connect(&socket, &QTcpSocket::readyRead, this, &TcpClient::onReadyRead);
        socket.connectToHost(host, port);
    }
private slots:
    void onConnected() {
        qDebug() << "Connected to server!";
        socket.write("Hello, server!");
    }
    void onReadyRead() {
        QByteArray data = socket.readAll();
        qDebug() << "Server said:" << data;
        socket.disconnectFromHost();
    }
private:
    QTcpSocket socket;
};

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);
    TcpClient client("localhost", 12345);
    
    return a.exec();
}

代码解释

  1. 创建 TcpClient 类:这个类继承自 QObject ,允许使用信号与槽机制。
  2. 连接信号和槽:在构造函数中,将 QTcpSocket 的 connected 和 readyRead 信号分别连接到
    onConnected 和 onReadyRead 槽。
  3. 连接到服务器:使用 connectToHost() 方法开始连接过程。
  4. 处理连接建立:一旦连接建立, onConnected 槽被触发,客户端向服务器发送一条消息。
  5. 接收数据:当数据可读时, onReadyRead 槽被触发,客户端读取并打印来自服务器的数据。
  6. 断开连接:在接收数据后,客户端断开与服务器的连接。

这个客户端示例展示了如何使用 Qt 的信号与槽机制来处理 TCP 连接。这种方式使得代码更加清晰,易于维护,并且能更好地处理异步事件。

4 TCP服务端项目开发

main.cpp

#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QMessageBox>
#include <QNetworkInterface>
#include <QTcpSocket>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //关联窗口和子组件同步变大变小
    this->setLayout(ui->verticalLayout);

    //实例化一个服务器对象
    server = new QTcpServer(this);
    //绑定新客户端连接信号与槽
    connect(server,SIGNAL(newConnection()),this,SLOT(on_newClient_conect()));
    //为MyComoBox连接信号与槽
    connect(ui->comboBoxChildren,&MyComboBox::on_ComboBox_clicked,this,&Widget::myComboBox_refresh);
    //设置启动时按钮状态
    ui->btnLineOut->setEnabled(false);
    ui->btnStopListen->setEnabled(false);
    ui->btnSend->setEnabled(false);
    //自动获取主机IP地址并刷新到控件上
    //QList<QHostAddress> QNetworkInterface::allAddresses()
    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_conect()
{
    //判断是否有新加入的客户端
    if(server->hasPendingConnections()){
        //实例化一个Socket对象,生成对话通道
        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(stateChanged(QAbstractSocket::SocketState)),
                this,SLOT(mstateChanged(QAbstractSocket::SocketState)));
        //往comboBox中增加条目
        ui->comboBoxChildren->addItem(QString::number(connection->peerPort()));
        ui->comboBoxChildren->setCurrentText(QString::number(connection->peerPort()));
        childIndex = ui->comboBoxChildren->currentIndex();

        if(!ui->btnSend->isEnabled()){
            ui->btnSend->setEnabled(true);
        }
    }
}

//开始监听按钮槽函数
void Widget::on_btnListen_clicked()
{
    //监听客户端
    //QHostAddress addr("10.102.13.233");
    //自动检测IP地址:QHostAddress::Any
    int port = ui->lineEditPort->text().toInt();
    if(!server->listen(QHostAddress(ui->comboBoxAddr->currentText()),port)){
        qDebug() << "listenError";
        QMessageBox msgBox;
        msgBox.setWindowTitle("监听失败");
        msgBox.setText("端口号被占用\n");
        msgBox.exec();
    }else{
        //设置按下开始监听按钮后其他按钮状态
        ui->btnListen->setEnabled(false);
        ui->btnLineOut->setEnabled(true);
        ui->btnStopListen->setEnabled(true);
    }
}

//数据接收槽函数
void Widget::on_readyRead_handler()
{
    //通过信号发送者找到发送者对象
    QTcpSocket *tmpSock = qobject_cast<QTcpSocket *>(sender());
    QByteArray revData = tmpSock->readAll();
    ui->textEditRev->moveCursor(QTextCursor::End);
    ui->textEditRev->ensureCursorVisible();
    ui->textEditRev->insertPlainText("客户端:" + revData + "\n");
}

//连接断开槽函数
void Widget::mdisconnected()
{
    //通过信号发送者找到发送者对象
    QTcpSocket *tmpSock = qobject_cast<QTcpSocket *>(sender());
    qDebug() << "client out!";
    ui->textEditRev->insertPlainText("客户端断开\n");
    //网络资源回收,这个必须在判断客户端对象是否为空前面
    tmpSock->deleteLater();
    qDebug() << ui->comboBoxChildren->count();
    //判断myComboBox是否为空,若为空则将发送按钮设置为不可用
    if(ui->comboBoxChildren->count() == 0){
        ui->btnSend->setEnabled(false);
    }
}

//myComboBox槽函数
void Widget::myComboBox_refresh()
{
    //找到所有server对象的子对象QTcpSocketClients并遍历
    ui->comboBoxChildren->clear();
    QList<QTcpSocket*> tcpSocketClients = server->findChildren<QTcpSocket*>();
    for(QTcpSocket* tmp:tcpSocketClients){
        //添加comboBox子项
        ui->comboBoxChildren->addItem(QString::number(tmp->peerPort()));
    }
    ui->comboBoxChildren->addItem("all");
}


//客户端状态槽函数
void Widget::mstateChanged(QAbstractSocket::SocketState socketState)
{
    //myComboBox选项序号
    int tmpIndex;
    //通过信号发送者找到发送者对象
    QTcpSocket *tmpSock = qobject_cast<QTcpSocket *>(sender());
    //qDebug() << "client out In state:" << socketState;
    switch(socketState){
        //case QAbstractSocket::UnconnectedState:
        case QAbstractSocket::ClosingState:
            //ui->textEditRev->insertPlainText("客户端断开\n");
            tmpIndex = ui->comboBoxChildren->findText(QString::number(tmpSock->peerPort()));
            ui->comboBoxChildren->removeItem(tmpIndex);
            //释放网络资源,防止myComboBox中遗留选项
            tmpSock->deleteLater();
            break;
        //case QAbstractSocket::ConnectingState:
        case QAbstractSocket::ConnectedState:
            //ui->textEditRev->insertPlainText("客户端接入\n");
            break;
    }
}


//发送按钮槽函数
void Widget::on_btnSend_clicked()
{
    //找到所有server对象的子对象QTcpSocketClients并遍历
    QList<QTcpSocket*> tcpSocketClients = server->findChildren<QTcpSocket*>();
    //判断客户端对象是否为空,避免因为myComobox刷新不及时导致客户端访问越界
    if(tcpSocketClients.isEmpty()){
        QMessageBox msgBox;
        msgBox.setWindowTitle("发送错误");
        msgBox.setText("无已连接的客户端");
        msgBox.exec();
        ui->btnSend->setEnabled(false);
        return;
    }
    //当用户不选择all,向所有客户端发送数据的时候
    if(ui->comboBoxChildren->currentText() != "all"){
        //根据用户选择,找到指定客户端进行通信,通过currentName进行查找,若peerPort与当前选择的currentName相同,则向该客户端发送
        QString currentName = ui->comboBoxChildren->currentText();
        for(QTcpSocket* tmp : tcpSocketClients){
            if(QString::number(tmp->peerPort()) == currentName){
                tmp->write(ui->textEditSend->toPlainText().toStdString().c_str());
            }
        }
    }else{
        //遍历所有子客户端,并一一调用write函数,向所有客户端发送消息
        for(QTcpSocket* tmp:tcpSocketClients){
            tmp->write(ui->textEditSend->toPlainText().toStdString().c_str());
        }
    }

}
//comboBox变化槽函数
void Widget::on_comboBoxChildren_activated(int index)
{
    childIndex = index;
    qDebug() << index;
}
//停止监听槽函数
void Widget::on_btnStopListen_clicked()
{
    //找到所有server对象的子对象QTcpSocketClients并遍历
    QList<QTcpSocket*> tcpSocketClients = server->findChildren<QTcpSocket*>();
    for(QTcpSocket* tmp:tcpSocketClients){
        //关闭客户端连接通道
        tmp->close();
    }
    //关闭server
    server->close();
    //恢复按钮状态
    ui->btnListen->setEnabled(true);
    ui->btnLineOut->setEnabled(false);
    ui->btnStopListen->setEnabled(false);
    ui->btnSend->setEnabled(false);
}

//断开连接槽函数
void Widget::on_btnLineOut_clicked()
{
    on_btnStopListen_clicked();
    delete server;
    this->close();
}

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 *server;

public slots:
    void on_newClient_conect();
    //数据接收槽函数
    void on_readyRead_handler();
    //连接断开槽函数
    void mdisconnected();
    //客户端状态槽函数
    void mstateChanged(QAbstractSocket::SocketState);
    //myComboBox槽函数
    void myComboBox_refresh();
private slots:
    //开始监听按钮槽函数
    void on_btnListen_clicked();
    //发送槽函数
    void on_btnSend_clicked();
    //myComboBox点击槽函数
    void on_comboBoxChildren_activated(int index);

    void on_btnStopListen_clicked();

    void on_btnLineOut_clicked();

private:
    Ui::Widget *ui;
    int childIndex;
};
#endif // WIDGET_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);
}

mycombobox.h

#ifndef MYCOMBOBOX_H
#define MYCOMBOBOX_H

#include <QComboBox>
#include <QWidget>

//自定义ComboBox控件实现点击刷新
class MyComboBox : public QComboBox
{
    Q_OBJECT

public:
    MyComboBox(QWidget *parent);
protected:
    void mousePressEvent(QMouseEvent *e) override;
signals:
    void on_ComboBox_clicked();
};

#endif // MYCOMBOBOX_H

5 TCP客户端项目开发

main.c

#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

widget.cpp

#include "widget.h"
#include "ui_widget.h"

#include <QString>
#include <QTimer>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //关联窗口和子组件同步变大变小
    this->setLayout(ui->verticalLayout);
    //创建客户端套接字
    client = new QTcpSocket(this);
    //数据到达信号与槽的连接
    connect(client,SIGNAL(readyRead()),this,SLOT(mRead_data_from_server()));
    //设置按钮状态
    ui->btnDisconnect->setEnabled(false);
    ui->btnSend->setEnabled(false);
}

Widget::~Widget()
{
    delete ui;
}

//连接按键槽函数
void Widget::on_btnConect_clicked()
{
    //超时定时器
    timer = new QTimer(this);
    //超时触发
    timer->setSingleShot(true);
    //超时时间5s
    timer->setInterval(5000);

    //客户端连接主机
    client->connectToHost(ui->lineEditIpAddr->text(),ui->lineEditPort->text().toInt());
    //客户端连接连接信号与槽实现
    connect(client,SIGNAL(connected()),this,SLOT(onConnected()));
    //客户端连接错误信号与槽实现
    connect(client,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(onerror(QAbstractSocket::SocketError)));
    //定时器超时信号与槽实现
    connect(timer,SIGNAL(timeout()),this,SLOT(onTimeout()));
    //整个窗口不可用
    this->setEnabled(false);
    timer->start();



}
//发送按键槽函数
void Widget::on_btnSend_clicked()
{
    QByteArray sendData = ui->textEditSend->toPlainText().toUtf8();
    //发送数据
    client->write(sendData);
    mInsertTextByColor(Qt::red,sendData + "\n");
}
//数据到达槽函数实现
void Widget::mRead_data_from_server()
{
    //光标定位到尾部
    ui->textEditRev->moveCursor(QTextCursor::End);
    ui->textEditRev->ensureCursorVisible();
    QByteArray revData = client->readAll();
    mInsertTextByColor(Qt::black,revData + "\n");
}
//断开连接槽函数
void Widget::on_btnDisconnect_clicked()
{
    client->close();
    ui->textEditRev->append("终止连接");
    //设置各个按钮状态
    ui->btnConect->setEnabled(true);
    ui->lineEditIpAddr->setEnabled(true);
    ui->lineEditPort->setEnabled(true);
    ui->btnDisconnect->setEnabled(false);
}

//新客户端接入槽函数实现
void Widget::onConnected()
{
    //超时定时器停止
    timer->stop();
    //整个窗口可用
    this->setEnabled(true);
    //设置各个按钮状态
    ui->textEditRev->append("连接成功");
    ui->btnConect->setEnabled(false);
    ui->btnDisconnect->setEnabled(true);
    ui->lineEditIpAddr->setEnabled(false);
    ui->lineEditPort->setEnabled(false);
    ui->btnSend->setEnabled(true);
}

void Widget::onerror(QAbstractSocket::SocketError error)
{
    qDebug() << "连接错误:" << error;
    ui->textEditRev->insertPlainText("连接出问题了:" + client->errorString());
    this->setEnabled(true);
    on_btnDisconnect_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);
}

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QTcpSocket>
#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    //连接按钮槽函数实现
    void on_btnConect_clicked();
    //发送按钮槽函数实现
    void on_btnSend_clicked();
    //接收数据槽函数实现
    void mRead_data_from_server();
    //断开连接槽函数实现
    void on_btnDisconnect_clicked();
    //新客户端接入槽函数实现
    void onConnected();
    //客户端连接错误槽函数实现
    void onerror(QAbstractSocket::SocketError);
    //连接超时定时器槽函数实现
    void onTimeout();

private:
    Ui::Widget *ui;
    //客户端套接字定义
    QTcpSocket *client;
    //连接超时定时器定义
    QTimer *timer;
    //修改字体颜色并显示
    void mInsertTextByColor(Qt::GlobalColor color,QString str);
};
#endif // WIDGET_H

项目到此结束

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐