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

开源 C++ QT QML 开发(十二)通讯--TCP客户端

           文章的目的为了记录使用QT QML开发学习的经历。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。

相关链接:

开源 C++ QT QML 开发(一)基本介绍

开源 C++ QT QML 开发(二)工程结构

开源 C++ QT QML 开发(三)常用控件

开源 C++ QT QML 开发(四)复杂控件--Listview

开源 C++ QT QML 开发(五)复杂控件--Gridview

开源 C++ QT QML 开发(六)自定义控件--波形图

开源 C++ QT QML 开发(七)自定义控件--仪表盘

开源 C++ QT QML 开发(八)自定义控件--圆环

开源 C++ QT QML 开发(九)文件--文本和二进制

开源 C++ QT QML 开发(十)通讯--串口

开源 C++ QT QML 开发(十一)通讯--TCP服务器端

开源 C++ QT QML 开发(十二)通讯--TCP客户端

推荐链接:

开源 C# 快速开发(一)基础知识

开源 C# 快速开发(二)基础控件

开源 C# 快速开发(三)复杂控件

开源 C# 快速开发(四)自定义控件--波形图

开源 C# 快速开发(五)自定义控件--仪表盘

开源 C# 快速开发(六)自定义控件--圆环

开源 C# 快速开发(七)通讯--串口

开源 C# 快速开发(八)通讯--Tcp服务器端

开源 C# 快速开发(九)通讯--Tcp客户端

开源 C# 快速开发(十)通讯--http客户端

开源 C# 快速开发(十一)线程

开源 C# 快速开发(十二)进程监控

开源 C# 快速开发(十三)进程--管道通讯

开源 C# 快速开发(十四)进程--内存映射

开源 C# 快速开发(十五)进程--windows消息

开源 C# 快速开发(十六)数据库--sqlserver增删改查

本章节主要内容是:TCP客户端调试代码,ip和port可设置,数据发送和接收。

1.代码分析

2.所有源码

3.效果演示

一、代码分析

一、头文件分析 (TcpClient.h)
1.1 类声明和属性系统
 

class TcpClient : public QObject
{Q_OBJECTQ_PROPERTY(bool isConnected READ isConnected NOTIFY connectionChanged)Q_PROPERTY(QString statusMessage READ statusMessage NOTIFY statusChanged)Q_PROPERTY(QString receivedData READ receivedData NOTIFY dataReceived)


详细分析:

Q_OBJECT:启用Qt的元对象系统,支持信号槽机制

Q_PROPERTY:将C++成员暴露给QML,实现数据绑定

isConnected:只读布尔属性,反映连接状态

statusMessage:只读字符串属性,显示状态信息

receivedData:只读字符串属性,存储接收到的数据


1.2 公共接口函数
 

public:explicit TcpClient(QObject *parent = nullptr);Q_INVOKABLE void connectToServer(const QString &ip, int port);Q_INVOKABLE void disconnectFromHost();Q_INVOKABLE void sendMessage(const QString &message);bool isConnected() const { return m_connected; }QString statusMessage() const { return m_statusMessage; }QString receivedData() const { return m_receivedData; }


函数作用:

Q_INVOKABLE:使C++函数可在QML中调用

构造函数:初始化父对象和成员变量

connectToServer:连接到指定IP和端口的服务器

disconnectFromHost:主动断开连接

sendMessage:向服务器发送消息

三个getter函数:返回对应属性的值

1.3 信号声明
 

signals:void connectionChanged();void statusChanged();void dataReceived();


信号作用:

connectionChanged:连接状态改变时发射

statusChanged:状态消息更新时发射

dataReceived:收到新数据时发射

1.4 私有槽函数
 

private slots:void onConnected();void onDisconnected();void onReadyRead();void onErrorOccurred(QAbstractSocket::SocketError error);


槽函数功能:

onConnected:处理连接成功事件

onDisconnected:处理连接断开事件

onReadyRead:处理数据可读事件

onErrorOccurred:处理错误事件


二、源文件分析 (TcpClient.cpp)
2.1 构造函数
 

TcpClient::TcpClient(QObject *parent): QObject(parent), m_connected(false)
{m_socket = new QTcpSocket(this);// 连接信号槽 - 使用Qt5.12兼容的方式connect(m_socket, &QTcpSocket::connected, this, &TcpClient::onConnected);connect(m_socket, &QTcpSocket::disconnected, this, &TcpClient::onDisconnected);connect(m_socket, &QTcpSocket::readyRead, this, &TcpClient::onReadyRead);// Qt5.12兼容的错误处理方式
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))connect(m_socket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::errorOccurred),this, &TcpClient::onErrorOccurred);
#elseconnect(m_socket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error),this, &TcpClient::onErrorOccurred);
#endifm_statusMessage = "未连接";
}


详细分析:

成员初始化:m_connected初始化为false,m_statusMessage设为"未连接"

Socket创建:new QTcpSocket(this),指定父对象实现自动内存管理

信号槽连接:

connected → onConnected:连接建立

disconnected → onDisconnected:连接断开

readyRead → onReadyRead:数据可读

版本兼容处理:针对Qt 5.15前后版本不同的错误信号名称


2.2 connectToServer 函数
 

void TcpClient::connectToServer(const QString &ip, int port)
{if (m_connected) {m_statusMessage = "已连接,请先断开当前连接";emit statusChanged();return;}m_statusMessage = QString("正在连接 %1:%2...").arg(ip).arg(port);emit statusChanged();m_socket->connectToHost(ip, port);
}


执行流程:

状态检查:如果已连接,设置错误状态并返回

更新状态:显示正在连接的信息

触发状态变更:发射statusChanged信号更新UI

发起连接:调用QTcpSocket::connectToHost异步连接

2.3 disconnectFromHost 函数
 

void TcpClient::disconnectFromHost()
{if (m_socket && m_connected) {m_socket->disconnectFromHost();}
}


特点:

安全性检查:确保socket存在且处于连接状态

优雅断开:disconnectFromHost()会等待数据发送完成


2.4 sendMessage 函数
 

void TcpClient::sendMessage(const QString &message)
{if (!m_connected || !m_socket) {m_statusMessage = "未连接,无法发送消息";emit statusChanged();return;}QByteArray data = message.toUtf8();qint64 bytesWritten = m_socket->write(data);if (bytesWritten == -1) {m_statusMessage = "发送失败: " + m_socket->errorString();emit statusChanged();} else {// 在接收数据显示区域也显示发送的消息QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");QString sendInfo = QString("[%1] 发送: %2").arg(timestamp).arg(message);appendToReceivedData(sendInfo);m_statusMessage = "消息发送成功";emit statusChanged();}
}


详细流程:

连接验证:检查socket和连接状态

编码转换:将QString转换为UTF-8字节数组

数据发送:调用write()方法发送数据

结果处理:

失败:记录错误信息

成功:添加时间戳并显示在接收区,更新状态


2.5 onConnected 槽函数
 

void TcpClient::onConnected()
{m_connected = true;m_statusMessage = "连接服务器成功";QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");QString connectInfo = QString("[%1] 已连接到服务器").arg(timestamp);appendToReceivedData(connectInfo);emit connectionChanged();emit statusChanged();
}


处理逻辑:

更新连接状态:m_connected = true

设置成功状态消息

记录连接时间戳到接收数据区

发射状态变更信号更新UI


2.6 onDisconnected 槽函数
 

void TcpClient::onDisconnected()
{m_connected = false;m_statusMessage = "已断开连接";QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");QString disconnectInfo = QString("[%1] 与服务器断开连接").arg(timestamp);appendToReceivedData(disconnectInfo);emit connectionChanged();emit statusChanged();
}


对称处理:与onConnected类似,但处理断开连接的情况


2.7 onReadyRead 槽函数
 

void TcpClient::onReadyRead()
{if (!m_socket) return;QByteArray data = m_socket->readAll();QString message = QString::fromUtf8(data);QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");QString receivedInfo = QString("[%1] 接收: %2").arg(timestamp).arg(message);appendToReceivedData(receivedInfo);m_statusMessage = "收到新消息";emit statusChanged();emit dataReceived();
}


数据接收流程:

安全性检查:确保socket有效

读取数据:readAll()读取所有可用数据

编码转换:UTF-8字节数组转QString

格式化显示:添加时间戳前缀

更新状态:显示收到新消息,发射数据接收信号


2.8 onErrorOccurred 槽函数
 

void TcpClient::onErrorOccurred(QAbstractSocket::SocketError error)
{Q_UNUSED(error)m_connected = false;m_statusMessage = "连接错误: " + m_socket->errorString();QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");QString errorInfo = QString("[%1] 错误: %2").arg(timestamp).arg(m_socket->errorString());appendToReceivedData(errorInfo);emit connectionChanged();emit statusChanged();
}


错误处理:

Q_UNUSED(error):避免未使用参数警告

重置连接状态

获取详细的错误描述信息

记录错误日志到接收区

通知UI更新状态


2.9 appendToReceivedData 辅助函数
 

void TcpClient::appendToReceivedData(const QString &message)
{m_receivedData.prepend(message + "\n");// 限制显示的行数,避免内存占用过大QStringList lines = m_receivedData.split('\n');if (lines.size() > 1000) { // 最多保留1000行lines = lines.mid(0, 1000);m_receivedData = lines.join('\n');}emit dataReceived();
}


内存管理策略:

前置添加:prepend()使最新消息显示在最前面

行数限制:最多保留1000行数据

内存优化:防止长时间运行导致内存无限增长

信号通知:发射dataReceived更新UI显示


三、QML界面分析
3.1 主窗口结构
 

ApplicationWindow {id: windowwidth: 800height: 600title: "TCP客户端"visible: trueTcpClient {id: client}


注册C++类型到QML,创建client实例

3.2 控制面板布局
连接控制部分:

TextField {id: serverIPFieldtext: "127.0.0.1"  // 默认本地回环地址
}
TextField {id: portField  text: "8080"      // 默认端口validator: IntValidator { bottom: 1; top: 65535 }  // 输入验证
}


动态按钮:

Button {id: connectButtontext: client.isConnected ? "断开连接" : "连接服务器"  // 状态相关文本onClicked: {if (client.isConnected) {client.disconnectFromHost()} else {var port = parseInt(portField.text)if (port > 0 && port <= 65535) {client.connectToServer(serverIPField.text, port)}}}
}


3.3 消息显示区域
 

ScrollView {TextArea {id: receivedTextAreatext: client.receivedData  // 绑定到C++属性readOnly: truewrapMode: TextArea.WrapselectByMouse: truefont.family: "Courier New"  // 等宽字体,便于对齐}
}


四、程序入口分析
4.1 main函数
 

int main(int argc, char *argv[])
{QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);QGuiApplication app(argc, argv);// 注册TCP客户端类型到QMLqmlRegisterType<TcpClient>("TcpClient", 1, 0, "TcpClient");QQmlApplicationEngine engine;engine.load(url);return app.exec();
}

二、所有源码

.pro文件源码

QT += quick quickcontrols2 networkCONFIG += c++11# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Refer to the documentation for the
# deprecated API to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0SOURCES += \TcpClient.cpp \main.cppRESOURCES += qml.qrc# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += targetHEADERS += \TcpClient.h

TcpClient.h文件源码

#ifndef TCPCLIENT_H
#define TCPCLIENT_H#include <QObject>
#include <QTcpSocket>
#include <QTimer>
#include <QDateTime>class TcpClient : public QObject
{Q_OBJECTQ_PROPERTY(bool isConnected READ isConnected NOTIFY connectionChanged)Q_PROPERTY(QString statusMessage READ statusMessage NOTIFY statusChanged)Q_PROPERTY(QString receivedData READ receivedData NOTIFY dataReceived)public:explicit TcpClient(QObject *parent = nullptr);Q_INVOKABLE void connectToServer(const QString &ip, int port);Q_INVOKABLE void disconnectFromHost();Q_INVOKABLE void sendMessage(const QString &message);bool isConnected() const { return m_connected; }QString statusMessage() const { return m_statusMessage; }QString receivedData() const { return m_receivedData; }signals:void connectionChanged();void statusChanged();void dataReceived();private slots:void onConnected();void onDisconnected();void onReadyRead();void onErrorOccurred(QAbstractSocket::SocketError error);private:void appendToReceivedData(const QString &message);QTcpSocket *m_socket;QString m_statusMessage;QString m_receivedData;bool m_connected;
};#endif // TCPCLIENT_H

TcpClient.cpp文件源码

#include "TcpClient.h"
#include <QHostAddress>TcpClient::TcpClient(QObject *parent): QObject(parent), m_connected(false)
{m_socket = new QTcpSocket(this);// 连接信号槽 - 使用Qt5.12兼容的方式connect(m_socket, &QTcpSocket::connected, this, &TcpClient::onConnected);connect(m_socket, &QTcpSocket::disconnected, this, &TcpClient::onDisconnected);connect(m_socket, &QTcpSocket::readyRead, this, &TcpClient::onReadyRead);// Qt5.12兼容的错误处理方式
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))connect(m_socket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::errorOccurred),this, &TcpClient::onErrorOccurred);
#elseconnect(m_socket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error),this, &TcpClient::onErrorOccurred);
#endifm_statusMessage = "未连接";
}void TcpClient::connectToServer(const QString &ip, int port)
{if (m_connected) {m_statusMessage = "已连接,请先断开当前连接";emit statusChanged();return;}m_statusMessage = QString("正在连接 %1:%2...").arg(ip).arg(port);emit statusChanged();m_socket->connectToHost(ip, port);
}void TcpClient::disconnectFromHost()
{if (m_socket && m_connected) {m_socket->disconnectFromHost();}
}void TcpClient::sendMessage(const QString &message)
{if (!m_connected || !m_socket) {m_statusMessage = "未连接,无法发送消息";emit statusChanged();return;}QByteArray data = message.toUtf8();qint64 bytesWritten = m_socket->write(data);if (bytesWritten == -1) {m_statusMessage = "发送失败: " + m_socket->errorString();emit statusChanged();} else {// 在接收数据显示区域也显示发送的消息QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");QString sendInfo = QString("[%1] 发送: %2").arg(timestamp).arg(message);appendToReceivedData(sendInfo);m_statusMessage = "消息发送成功";emit statusChanged();}
}void TcpClient::onConnected()
{m_connected = true;m_statusMessage = "连接服务器成功";QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");QString connectInfo = QString("[%1] 已连接到服务器").arg(timestamp);appendToReceivedData(connectInfo);emit connectionChanged();emit statusChanged();
}void TcpClient::onDisconnected()
{m_connected = false;m_statusMessage = "已断开连接";QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");QString disconnectInfo = QString("[%1] 与服务器断开连接").arg(timestamp);appendToReceivedData(disconnectInfo);emit connectionChanged();emit statusChanged();
}void TcpClient::onReadyRead()
{if (!m_socket) return;QByteArray data = m_socket->readAll();QString message = QString::fromUtf8(data);QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");QString receivedInfo = QString("[%1] 接收: %2").arg(timestamp).arg(message);appendToReceivedData(receivedInfo);m_statusMessage = "收到新消息";emit statusChanged();emit dataReceived();
}void TcpClient::onErrorOccurred(QAbstractSocket::SocketError error)
{Q_UNUSED(error)m_connected = false;m_statusMessage = "连接错误: " + m_socket->errorString();QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");QString errorInfo = QString("[%1] 错误: %2").arg(timestamp).arg(m_socket->errorString());appendToReceivedData(errorInfo);emit connectionChanged();emit statusChanged();
}void TcpClient::appendToReceivedData(const QString &message)
{m_receivedData.prepend(message + "\n");// 限制显示的行数,避免内存占用过大QStringList lines = m_receivedData.split('\n');if (lines.size() > 1000) { // 最多保留1000行lines = lines.mid(0, 1000);m_receivedData = lines.join('\n');}emit dataReceived();
}

main.qml文件源码

import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import TcpClient 1.0ApplicationWindow {id: windowwidth: 800height: 600title: "TCP客户端"visible: trueTcpClient {id: client}// TCP客户端控制区域Rectangle {id: controlPanelwidth: parent.widthheight: 180color: "#f0f0f0"border.color: "#cccccc"ColumnLayout {anchors.fill: parentanchors.margins: 10// 连接设置行RowLayout {Layout.fillWidth: trueLabel {text: "服务器IP:"}TextField {id: serverIPFieldLayout.preferredWidth: 200text: "127.0.0.1"placeholderText: "输入服务器IP地址"}Label {text: "端口号:"}TextField {id: portFieldLayout.preferredWidth: 100text: "8080"validator: IntValidator { bottom: 1; top: 65535 }placeholderText: "输入端口号"}Button {id: connectButtontext: client.isConnected ? "断开连接" : "连接服务器"onClicked: {if (client.isConnected) {client.disconnectFromHost()} else {var port = parseInt(portField.text)if (port > 0 && port <= 65535) {client.connectToServer(serverIPField.text, port)} else {statusLabel.text = "端口号无效"}}}background: Rectangle {color: client.isConnected ? "#e74c3c" : "#2ecc71"radius: 4}contentItem: Text {text: connectButton.textcolor: "white"horizontalAlignment: Text.AlignHCenterverticalAlignment: Text.AlignVCenterfont.bold: true}}}// 状态显示行RowLayout {Layout.fillWidth: trueLabel {id: statusLabeltext: client.statusMessageLayout.fillWidth: truecolor: client.isConnected ? "#27ae60" : "#e74c3c"font.bold: truewrapMode: Text.Wrap}Label {text: "连接状态: " + (client.isConnected ? "已连接" : "未连接")color: client.isConnected ? "#2980b9" : "#95a5a6"font.bold: true}}// 消息发送行RowLayout {Layout.fillWidth: trueTextField {id: messageFieldLayout.fillWidth: trueplaceholderText: "输入要发送的消息..."enabled: client.isConnectedonAccepted: sendButton.clicked()}Button {id: sendButtontext: "发送消息"enabled: client.isConnected && messageField.text.trim() !== ""onClicked: {if (messageField.text.trim() !== "") {client.sendMessage(messageField.text)messageField.clear()}}background: Rectangle {color: sendButton.enabled ? "#9b59b6" : "#bdc3c7"radius: 4}contentItem: Text {text: sendButton.textcolor: "white"horizontalAlignment: Text.AlignHCenterverticalAlignment: Text.AlignVCenter}}}// 控制按钮行RowLayout {Layout.fillWidth: trueButton {text: "清空日志"onClicked: {// 由于receivedData是只读的,需要在C++端添加清空方法// 这里暂时通过重新创建对象来清空client.sendMessage("") // 触发更新}background: Rectangle {color: "#f39c12"radius: 4}contentItem: Text {text: "清空日志"color: "white"horizontalAlignment: Text.AlignHCenterverticalAlignment: Text.AlignVCenter}}Button {text: "测试消息"enabled: client.isConnectedonClicked: {client.sendMessage("Hello from TCP Client!")}background: Rectangle {color: "#3498db"radius: 4}contentItem: Text {text: "测试消息"color: "white"horizontalAlignment: Text.AlignHCenterverticalAlignment: Text.AlignVCenter}}Item {Layout.fillWidth: true}Label {text: "服务器: " + (client.isConnected ?serverIPField.text + ":" + portField.text : "未连接")color: "#8e44ad"font.bold: true}}}}// 消息显示区域Rectangle {anchors {top: controlPanel.bottombottom: parent.bottomleft: parent.leftright: parent.right}color: "white"border.color: "#dee2e6"ScrollView {anchors.fill: parentanchors.margins: 5TextArea {id: receivedTextAreatext: client.receivedDatareadOnly: truewrapMode: TextArea.WrapselectByMouse: truefont.family: "Courier New"font.pixelSize: 12background: null}}}
}

main.cpp文件源码

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "TcpClient.h"int main(int argc, char *argv[])
{QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);QGuiApplication app(argc, argv);// 注册TCP客户端qmlRegisterType<TcpClient>("TcpClient", 1, 0, "TcpClient");QQmlApplicationEngine engine;// 设置应用程序信息app.setApplicationName("TCP客户端");app.setApplicationVersion("1.0");const QUrl url(QStringLiteral("qrc:/main.qml"));QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,&app, [url](QObject *obj, const QUrl &objUrl) {if (!obj && url == objUrl)QCoreApplication::exit(-1);}, Qt::QueuedConnection);engine.load(url);return app.exec();
}

三、效果演示

打开网络调试助手进行测试,设置ip和port,测试发送和接收。

http://www.dtcms.com/a/456844.html

相关文章:

  • 【密码学实战】openHiTLS pkeyutl命令行:公钥实用工具(加解密、密钥交换)
  • 做标书有什么好的网站吗网站改版不收录
  • JDK17和JDK8的 G1
  • win10安装conda环境
  • TDengine 浮点数新编码 BSS 用户手册
  • mybatis call存储过程,out的参数怎么返回
  • 今日八股——JVM篇
  • 【论文阅读】REACT: SYNERGIZING REASONING AND ACTING IN LANGUAGE MODELS
  • 沈阳做网站比较好的公司做网站需要会的软件
  • ubuntu22.04安装gvm管理go
  • 基于单片机的智能点滴输液速度与液位控制系统设计
  • 嵌入式开发学习日志38——stm32之看门狗
  • golang面经——内存相关模块
  • 成都政务网站建设怎样做视频网站
  • 架构设计常画哪些图
  • 自然语言处理分享系列-词向量空间中的高效表示估计(一)
  • RNN的注意力机制:原理与实现(代码示例)
  • Flutter bottomNavigationBar 底部导航栏
  • 做男装去哪个网站好的网站开发工具有哪些
  • 【Spring 3】深入剖析 Spring 的 Prototype Scope:何时以及如何使用非单例 Bean
  • asp.net+mvc+网站开发wordpress 手机端页面
  • 【开题答辩全过程】以 爱篮球app为例,包含答辩的问题和答案
  • 深入理解跨域问题与解决方案
  • 从零搭建 RAG 智能问答系统1:基于 LlamaIndex 与 Chainlit实现最简单的聊天助手
  • Redis核心通用命令解析
  • 后端(JavaWeb)学习笔记(CLASS 1):maven
  • 后端_Redis 分布式锁实现指南
  • K8s学习笔记(十六) 探针(Probe)
  • 企业个人网站口碑营销策略
  • c语言网站三星网上商城分期