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

C++ Qt网络编程实战:跨平台TCP调试工具开发

作为一名 C++ 开发学习者,掌握 Qt 框架下的网络编程是提升综合开发能力的重要一环。

今天给大家带来 一个基于 TCP 协议的网络调试助手的设计与实现

如果你也在学习 Qt 或者准备做一个类似的网络通信小工具,希望这篇文章能为你提供清晰的开发思路和技术参考。欢迎留言交流你的实现经验!

Part1、TCP网络调试助手

本次设计的网络调试助手在简化界面UI的基础上,重点在于掌握网络通信的核心知识点,尤其是服务器与客户端的创建过程以及数据交互机制。同时,也借此机会复习和巩固了Qt UI控件的使用方法。

1.1、项目整体开发流程

图片

1.2、Qt TCP服务器的关键流程

图片

在创建基于QTcpServer的服务端程序时,需遵循以下关键步骤:

1)、创建并初始化 QTcpServer 实例

  1. 实例化一个 QTcpServer 对象;
  2. 调用 listen() 方法监听指定端口上的连接请求。

2)、处理新连接

  1. 将 newConnection 信号连接到对应的槽函数;
  2. 在槽函数中通过 nextPendingConnection() 获取 QTcpSocket,用于与客户端通信。

3)、读取和发送数据

  1. 使用 readyRead 信号绑定槽函数,以接收来自客户端的数据;
  2. 利用 write() 方法将响应数据发送回客户端。

4)、关闭连接

  1. 在适当的时候调用 close() 方法关闭 QTcpSocket 连接。
示例代码如下:
class MyServer : public QObject {    Q_OBJECTpublic:    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.3、Qt TCP客户端的关键流程

图片

创建基于 QTcpSocket 的客户端程序主要包括以下几个步骤:

  1. 创建 QTcpSocket 实例
  2. 连接到服务器
    • 使用 connectToHost() 方法连接目标服务器的IP地址和端口号。

  3. 发送数据到服务器
    • 使用 write() 方法发送请求或消息。

  4. 接收来自服务器的数据
    • 将 readyRead 信号绑定到对应的槽函数,用于处理服务器返回的数据。

  5. 关闭连接
    • 使用 close() 方法关闭当前连接。

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

该客户端尝试连接到指定的服务器地址和端口,并等待服务器返回数据。开发者应根据实际需求合理管理网络错误与异常。

1.4、UI界面的设计

本项目中设计了两个主要界面:

TCP服务端UI界面

包括IP地址选择框、端口输入框、监听按钮、客户端连接状态显示区、数据收发文本框等;

图片

TCP客户端UI界面

包括服务器IP与端口输入框、连接按钮、数据发送与接收框等。

图片

1.5、TCP协议理论知识

以下为TCP协议的基础理论知识,虽在实际编程中由 QTcpSocket 类封装底层细节,但理解其原理对于面试准备及深入学习仍具有重要意义。

TCP协议的基本特点:

特性

描述

面向连接

通信前必须建立连接(三次握手)

可靠传输

数据完整且无误地到达接收方

顺序控制

确保数据包按序重组

流量控制

使用滑动窗口机制避免过载

拥塞控制

动态调整传输速率防止网络拥塞

数据分段

大块数据自动分片传输

确认与重传

接收方确认接收,丢失则重传

终止连接

正常关闭连接(四次挥手)

TCP连接建立 —— 三次握手

图片

  1. 客户端发送SYN报文(同步);

  2. 服务器回复SYN-ACK(同步-确认);

  3. 客户端发送ACK报文,连接建立完成。

TCP连接终止 —— 四次挥手

图片

  1. 一方发送FIN报文(结束);

  2. 对方回复ACK确认;

  3. 对方发送FIN报文;

  4. 原方回复ACK,连接关闭。

Socket的主要类型:
  • TCP Socket

    :面向连接、可靠;

  • UDP Socket

    :无连接、不可靠。

Socket的主要功能:
  • 创建网络连接;

  • 监听客户端连接;

  • 发送与接收数据。

Qt中的Socket支持:
  • QTcpSocket

    :用于实现TCP通信;

  • QUdpSocket

    :用于实现UDP通信。

Socket抽象了网络通信的复杂性,是实现网络通信的重要基础工具之一。

Part2、网络通信核心代码

QTcpServer 是 Qt 网络模块的重要组成部分,用于构建TCP服务器。它可以异步监听客户端连接,并在连接建立后进行数据交换。


2.1、TCP服务端连接的核心代码

在类定义中声明服务器对象:

QTcpServer *server;

构造函数中实例化:

server = new QTcpServer(this);

点击监听按钮时启动监听并绑定信号:

void Widget::on_btnListen_clicked() {    QHostAddress addr("192.168.1.106");    quint16 port = 8888;    bool ret = server->listen(addr, port);    if (!ret) return;    connect(server, SIGNAL(newConnection()), this, SLOT(on_newClient_connect()));}

当有客户端接入时获取连接并绑定接收数据信号:​​​​​​​

void Widget::on_newClient_connect() {    if (server->hasPendingConnections()) {        QTcpSocket *tcpSocket = server->nextPendingConnection();        qDebug() << "client addr: " << tcpSocket->peerAddress().toString();        qDebug() << "client port: " << tcpSocket->peerPort();        ui->textEdit_Rev->append("addr: " + tcpSocket->peerAddress().toString());        ui->textEdit_Rev->append("port: " + QString::number(tcpSocket->peerPort()));        connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(on_readyRead_handler()));
        ui->comboBox_child->addItem(QString::number(tcpSocket->peerPort()));        ui->comboBox_child->setCurrentText(QString::number(tcpSocket->peerPort()));    }}

2.2、TCP服务端的数据通信核心代码

当客户端发送数据时触发接收槽函数:​​​​​​​

void Widget::on_readyRead_handler() {    QTcpSocket *tcpSocket = qobject_cast<QTcpSocket *>(sender());    QByteArray revData = tcpSocket->readAll();    ui->textEdit_Rev->append("client: " + revData);}

发送按钮槽函数,支持向所有或指定客户端发送数据:​​​​​​​

void Widget::on_btnSend_clicked() {    QList<QTcpSocket *> clients = server->findChildren<QTcpSocket*>();    for (QTcpSocket *temp : clients) {        temp->write(ui->textEdit_Send->toPlainText().toStdString().c_str());    }}

2.3、TCP客户端的核心代码

客户端对象定义与实例化:​​​​​​​

QTcpSocket *client;client = new QTcpSocket(this);

连接按钮槽函数:​​​​​​​

void Widget::on_btnConnect_clicked() {    QString addr(ui->lineEdit_addr->text());    quint16 port = ui->lineEdit_port->text().toInt();    client->connectToHost(addr, port);    connect(client, SIGNAL(readyRead()), this, SLOT(on_readyRead_handler()));}

数据接收槽函数:​​​​​​​

void Widget::on_readyRead_handler() {    QByteArray revData = client->readAll();    ui->textEdit_rev->append("server: " + revData);}

发送按钮槽函数:​​​​​​​

void Widget::on_btnSend_clicked() {    QByteArray sendData = ui->textEdit_send->toPlainText().toUtf8();    client->write(sendData);}

Part3TCP服务端项目功能优化


3.1、自动刷新IP地址

为了方便用户选择本地IP地址,可以在程序启动时自动扫描系统中可用的IPv4地址并填充至下拉框中:​​​​​​​

QList<QHostAddress> addrList = QNetworkInterface::allAddresses();for (QHostAddress addr : addrList) {    if (addr.protocol() == QAbstractSocket::IPv4Protocol) {        ui->comboBox_Addr->addItem(addr.toString());    }}

3.2、服务器向不同客户端发数据

为实现向不同客户端单独发送数据的功能,可自定义一个继承于 QComboBox 的 myComboBox 类,并重写鼠标事件以触发自定义信号:

自定义类实现:​​​​​​​
class myComboBox : public QComboBox {    Q_OBJECTprotected:    void mousePressEvent(QMouseEvent *e) override;signals:    void on_ComboBox_clicked();};void myComboBox::mousePressEvent(QMouseEvent *e) {    if (e->button() == Qt::LeftButton)        emit on_ComboBox_clicked();    QComboBox::mousePressEvent(e);}
主界面中绑定信号与槽:
connect(ui->comboBox_child, &myComboBox::on_ComboBox_clicked, this, &Widget::on_refresh_comboBox);
刷新选项框内容:​​​​​​​
void Widget::on_refresh_comboBox() {    ui->comboBox_child->clear();    QList<QTcpSocket*> clients = server->findChildren<QTcpSocket*>();    for (auto client : clients) {        ui->comboBox_child->addItem(QString::number(client->peerPort()));    }    ui->comboBox_child->addItem("all");}
数据发送优化逻辑:​​​​​​​
void Widget::on_btnSend_clicked() {    QList<QTcpSocket *> clients = server->findChildren<QTcpSocket*>();    if (clients.isEmpty()) return;    QString target = ui->comboBox_child->currentText();    if (target != "all") {        for (auto client : clients) {            if (QString::number(client->peerPort()) == target) {                client->write(ui->textEdit_Send->toPlainText().toStdString().c_str());            }        }    } else {        for (auto client : clients) {            client->write(ui->textEdit_Send->toPlainText().toStdString().c_str());        }    }    ui->textEdit_Rev->moveCursor(QTextCursor::End);    ui->textEdit_Rev->ensureCursorVisible();}

3.3、TextEdit 设置特定位置文字颜色

为了在 QTextEdit 控件中设置特定位置的文字颜色,需要通过光标级别的操作来实现。Qt 提供了 textCursor() 方法获取当前光标对象,并结合 setCharFormat() 实现字符格式的定制。

函数原型与嵌套关系如下:
QTextCursor:		QTextEdit::textCursor() constQTextCursor:      	void QTextCursor::setCharFormat(const QTextCharFormat &format)  //方法QTextCharFormat:	void setForeground(const QBrush &brush)                         //方法QBrush:           	QBrush(const QColor &color, const QPixmap &pixmap)              //构造函数QColor:           	QColor(const QColor &color)   									//构造函数
将该功能封装为一个函数,参数分别为字体颜色和待显示文本:​​​​​​​
void Widget::setInsertColor(Qt::GlobalColor color, QString str){    // 获取当前光标位置    QTextCursor cursor = ui->textEdit_rev->textCursor();    QTextCharFormat format;    // 设置字符前景色    format.setForeground(QColor(color));    cursor.setCharFormat(format);    // 插入带颜色的文本并换行    cursor.insertText(str + "\n");}

3.4、客户端断开检测

当客户端主动断开连接时,服务器会接收到 disconnected() 信号。通过绑定该信号与槽函数,可以及时检测到客户端的断开行为。

绑定 disconnected() 信号:
connect(tcpSocket, SIGNAL(disconnected()), this, SLOT(on_disconnected()));
客户端断开连接槽函数实现:

在槽函数中完成以下操作:

  • 在文本框中提示客户端已退出;

  • 从下拉框中移除对应客户端的端口号;

  • 删除客户端对象;

  • 判断是否仍有连接中的客户端,决定是否禁用发送按钮。

void Widget::on_disconnected(){    QTcpSocket *tcpSocket = qobject_cast<QTcpSocket *>(sender());    ui->textEdit_Rev->append("client quit!");    // 查找并移除选项框中对应的端口号    int tempIdx = ui->comboBox_child->findText(QString::number(tcpSocket->peerPort()));    if (tempIdx != -1) {        ui->comboBox_child->removeItem(tempIdx);    }    // 删除客户端对象    tcpSocket->deleteLater();    // 若无其他客户端,禁用发送按钮    if (server->findChildren<QTcpSocket*>().isEmpty()) {        ui->btnSend->setEnabled(false);    }}

3.5、停止监听的实现

点击“停止监听”按钮后,需关闭所有已连接的客户端,并关闭服务器本身。

槽函数实现如下:​​​​​​​
void Widget::on_btnStopListen_clicked(){    // 获取所有已连接的客户端    QList<QTcpSocket *> clients = server->findChildren<QTcpSocket*>();    for (QTcpSocket *temp : clients) {        temp->close(); // 关闭连接    }    server->close(); // 关闭服务器}

Part4、TCP客户端项目开发及优化

4.1、检测连接状态

客户端成功连接服务器时会发出 connected() 信号,若连接失败则会发出 error() 信号。但由于错误信号响应存在延迟,因此可使用定时器机制判断是否超时。

构造函数中初始化定时器:​​​​​​​
// 初始化定时器timer = new QTimer(this);timer->setSingleShot(true); // 单次触发timer->setInterval(3000);   // 超时时间为3秒// 启动定时器timer->start();
绑定信号与槽函数:​​​​​​​
connect(client, SIGNAL(connected()), this, SLOT(on_connected()));connect(timer, SIGNAL(timeout()), this, SLOT(on_timer_out()));
连接成功槽函数实现:
void Widget::on_connected(){    timer->stop(); // 停止定时器    ui->textEdit_rev->append("连接成功");    // 更新控件状态    ui->btnDisconnect->setEnabled(true);    ui->btnSend->setEnabled(true);    ui->lineEdit_addr->setEnabled(false);    ui->lineEdit_port->setEnabled(false);    ui->btnConnect->setEnabled(false);    this->setEnabled(true);    // 光标定位至末尾    ui->textEdit_rev->moveCursor(QTextCursor::End);    ui->textEdit_rev->ensureCursorVisible();}
连接超时槽函数实现:
void Widget::on_timer_out(){    ui->textEdit_rev->append("连接超时");    client->abort(); // 中止连接    this->setEnabled(true);    on_btnDisconnect_clicked(); // 手动调用断开连接}

4.2、其他细节功能

文本框特定颜色分区

与服务器端相同,使用自定义函数实现带颜色的文本插入:

void Widget::setInsertColor(Qt::GlobalColor color, QString str){    QTextCursor cursor = ui->textEdit_rev->textCursor();    QTextCharFormat format;    format.setForeground(QColor(color));    cursor.setCharFormat(format);    cursor.insertText(str + "\n");}
控件使能与失能控制

通过 setEnabled() 方法控制控件的可用状态:​​​​​​​

ui->btnDisconnect->setEnabled(false);ui->btnSend->setEnabled(false);
文本框自动滚动到底部

确保每次插入新内容后,光标自动定位至最后一行:

ui->textEdit_rev->moveCursor(QTextCursor::End);ui->textEdit_rev->ensureCursorVisible();

Part5、总结

主要知识点总结如下:

TCPServer 类相关 API 与常用信号

功能

API

创建服务器

new QTcpServer(this)

监听端口

server->listen(addr, port)

获取客户端

nextPendingConnection()

关闭连接

close()

常用信号

触发条件

newConnection()

有新客户端连接时触发

QTcpSocket 类常用 API 与信号

功能

API

连接服务器

connectToHost(addr, port)

发送数据

write(data)

接收数据

readAll()

断开连接

disconnectFromHost() 或 abort()

常用信号

触发条件

readyRead()

收到数据时触发

connected()

成功连接服务器时触发

disconnected()

客户端断开连接时触发

error()

连接或通信过程中发生错误时触发

QTextEdit 内容读取与写入方法

操作

方法

读取全部内容

toPlainText()

插入带格式文本

insertText() + setCharFormat()

移动光标到底部

moveCursor(QTextCursor::End)

 点击下方关注【Linux教程】,获取编程学习路线、项目教程、简历模板、大厂面试题pdf文档、大厂面经、编程交流圈子等等。

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

相关文章:

  • 容器与虚拟机的本质差异:从资源隔离到网络存储机制
  • 2020 年 NOI 最后一题题解
  • Apple基础(Xcode②-Flutter结构解析)
  • 【硬件-笔试面试题】硬件/电子工程师,笔试面试题-49,(知识点:OSI模型,物理层、数据链路层、网络层)
  • 2025年湖北中级注册安全工程师报考那些事
  • 网络安全学习第16集(cdn知识点)
  • 知识速查大全:python面向对象基础
  • C++从入门到起飞之——智能指针!
  • 电子电气架构 --- 区域架构让未来汽车成为现实
  • 深入理解PostgreSQL的MVCC机制
  • SpringBoot之多环境配置全解析
  • Linux 系统日志管理与时钟同步实用指南
  • Tlias 案例-整体布局(前端)
  • cpp实现音频重采样8k->16k及16k->8k
  • 推扫式和凝视型高光谱相机分别采用哪些分光方式?
  • Web前端实战:Vue工程化+ElementPlus
  • 二叉树算法之【二叉树的层序遍历】
  • 专题:2025机器人产业技术图谱与商业化指南|附130+份报告PDF、数据汇总下载
  • Python爬虫05_Requests肯德基餐厅位置爬取
  • 小架构step系列30:多个校验注解
  • 《Spring Security源码深度剖析:Filter链与权限控制模型》
  • 文件权限值的表示方法
  • 怎样在 Vue 中定义全局方法?
  • 【C语言】深度剖析指针(二):指针与数组,字符,函数的深度关联
  • AWS VPC NAT 网关可观测最佳实践
  • 15、点云<—>深度图转换原理
  • 数据集:机器学习的基石
  • RPA软件推荐:提升企业自动化效率
  • 北京理工大学医工交叉教学实践分享(1)|如何以实践破解数据挖掘教学痛点
  • 在 Elasticsearch 8.19 和 9.1 中引入更强大、更具弹性和可观测性的 ES|QL