开源 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,测试发送和接收。