文章的目的为了记录使用QT QML开发学习的经历。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。
相关链接:
开源 C++ QT QML 开发(四)复杂控件--Listview
开源 C++ QT QML 开发(五)复杂控件--Gridview
开源 C++ QT QML 开发(十一)通讯--TCP服务器端
开源 C++ QT QML 开发(十二)通讯--TCP客户端
推荐链接:
开源 C# 快速开发(十六)数据库--sqlserver增删改查
本章节主要内容是:TCP客户端调试代码,ip和port可设置,数据发送和接收。
1.代码分析
2.所有源码
3.效果演示
一、代码分析
一、头文件分析 (TcpClient.h)
1.1 类声明和属性系统
class TcpClient : public QObject
{
Q_OBJECT
Q_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);
#else
connect(m_socket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error),
this, &TcpClient::onErrorOccurred);
#endif
m_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: window
width: 800
height: 600
title: "TCP客户端"
visible: true
TcpClient {
id: client
}
注册C++类型到QML,创建client实例
3.2 控制面板布局
连接控制部分:
TextField {
id: serverIPField
text: "127.0.0.1" // 默认本地回环地址
}
TextField {
id: portField
text: "8080" // 默认端口
validator: IntValidator { bottom: 1; top: 65535 } // 输入验证
}
动态按钮:
Button {
id: connectButton
text: 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: receivedTextArea
text: client.receivedData // 绑定到C++属性
readOnly: true
wrapMode: TextArea.Wrap
selectByMouse: true
font.family: "Courier New" // 等宽字体,便于对齐
}
}
四、程序入口分析
4.1 main函数
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
// 注册TCP客户端类型到QML
qmlRegisterType<TcpClient>("TcpClient", 1, 0, "TcpClient");
QQmlApplicationEngine engine;
engine.load(url);
return app.exec();
}
二、所有源码
.pro文件源码
QT += quick quickcontrols2 network
CONFIG += 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.0
SOURCES += \
TcpClient.cpp \
main.cpp
RESOURCES += 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 += target
HEADERS += \
TcpClient.h
TcpClient.h文件源码
#ifndef TCPCLIENT_H
#define TCPCLIENT_H
#include <QObject>
#include <QTcpSocket>
#include <QTimer>
#include <QDateTime>
class TcpClient : public QObject
{
Q_OBJECT
Q_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);
#else
connect(m_socket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error),
this, &TcpClient::onErrorOccurred);
#endif
m_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.0
ApplicationWindow {
id: window
width: 800
height: 600
title: "TCP客户端"
visible: true
TcpClient {
id: client
}
// TCP客户端控制区域
Rectangle {
id: controlPanel
width: parent.width
height: 180
color: "#f0f0f0"
border.color: "#cccccc"
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
// 连接设置行
RowLayout {
Layout.fillWidth: true
Label {
text: "服务器IP:"
}
TextField {
id: serverIPField
Layout.preferredWidth: 200
text: "127.0.0.1"
placeholderText: "输入服务器IP地址"
}
Label {
text: "端口号:"
}
TextField {
id: portField
Layout.preferredWidth: 100
text: "8080"
validator: IntValidator { bottom: 1; top: 65535 }
placeholderText: "输入端口号"
}
Button {
id: connectButton
text: 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.text
color: "white"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.bold: true
}
}
}
// 状态显示行
RowLayout {
Layout.fillWidth: true
Label {
id: statusLabel
text: client.statusMessage
Layout.fillWidth: true
color: client.isConnected ? "#27ae60" : "#e74c3c"
font.bold: true
wrapMode: Text.Wrap
}
Label {
text: "连接状态: " + (client.isConnected ? "已连接" : "未连接")
color: client.isConnected ? "#2980b9" : "#95a5a6"
font.bold: true
}
}
// 消息发送行
RowLayout {
Layout.fillWidth: true
TextField {
id: messageField
Layout.fillWidth: true
placeholderText: "输入要发送的消息..."
enabled: client.isConnected
onAccepted: sendButton.clicked()
}
Button {
id: sendButton
text: "发送消息"
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.text
color: "white"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
}
// 控制按钮行
RowLayout {
Layout.fillWidth: true
Button {
text: "清空日志"
onClicked: {
// 由于receivedData是只读的,需要在C++端添加清空方法
// 这里暂时通过重新创建对象来清空
client.sendMessage("") // 触发更新
}
background: Rectangle {
color: "#f39c12"
radius: 4
}
contentItem: Text {
text: "清空日志"
color: "white"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
Button {
text: "测试消息"
enabled: client.isConnected
onClicked: {
client.sendMessage("Hello from TCP Client!")
}
background: Rectangle {
color: "#3498db"
radius: 4
}
contentItem: Text {
text: "测试消息"
color: "white"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
Item {
Layout.fillWidth: true
}
Label {
text: "服务器: " + (client.isConnected ?
serverIPField.text + ":" + portField.text : "未连接")
color: "#8e44ad"
font.bold: true
}
}
}
}
// 消息显示区域
Rectangle {
anchors {
top: controlPanel.bottom
bottom: parent.bottom
left: parent.left
right: parent.right
}
color: "white"
border.color: "#dee2e6"
ScrollView {
anchors.fill: parent
anchors.margins: 5
TextArea {
id: receivedTextArea
text: client.receivedData
readOnly: true
wrapMode: TextArea.Wrap
selectByMouse: true
font.family: "Courier New"
font.pixelSize: 12
background: 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,测试发送和接收。
