文章的目的为了记录使用QT QML开发学习的经历。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。
相关链接:
开源 C++ QT QML 开发(四)复杂控件--Listview
开源 C++ QT QML 开发(五)复杂控件--Gridview
开源 C++ QT QML 开发(十一)通讯--TCP服务器端
开源 C++ QT QML 开发(十二)通讯--TCP客户端
推荐链接:
开源 C# 快速开发(十六)数据库--sqlserver增删改查
本章节主要内容是:介绍qml通讯中的tcp连接服务器端例子,实现ip和port参数可设,数据发送和接收。
1.代码分析
2.所有源码
3.效果演示
一、代码分析
一、C++后端详细分析 (tcp_server.h/cpp)
1.1 头文件分析 (tcp_server.h)
类声明和属性定义
class TcpServer : public QObject
{
Q_OBJECT
// QML属性声明
Q_PROPERTY(bool isListening READ isListening NOTIFY isListeningChanged)
Q_PROPERTY(QString statusMessage READ statusMessage NOTIFY statusMessageChanged)
// ... 其他属性
作用:声明6个QML可绑定的属性,每个属性包含READ方法和对应的信号核心方法声明
public:
explicit TcpServer(QObject *parent = nullptr);
~TcpServer();
// QML可调用方法
Q_INVOKABLE bool startServer(const QString &ip, quint16 port);
Q_INVOKABLE void stopServer();
Q_INVOKABLE void sendToAllClients(const QString &message);
// ... 其他方法
设计特点:
使用Q_INVOKABLE使C++方法在QML中可用
明确的资源管理(析构函数中调用stopServer)
1.2 实现文件分析 (tcp_server.cpp)
构造函数
TcpServer::TcpServer(QObject *parent)
: QObject(parent)
, m_tcpServer(new QTcpServer(this))
, m_isListening(false)
, m_clientCount(0)
, m_currentPort(0)
{
connect(m_tcpServer, &QTcpServer::newConnection,
this, &TcpServer::onNewConnection);
updateStatusMessage("服务器未启动");
}
关键点:
父子对象关系管理(使用this作为parent)
信号槽连接:新连接→onNewConnection
初始状态设置
IP地址获取函数
QStringList TcpServer::getAvailableIPs()
{
QStringList ipAddresses;
ipAddresses << "0.0.0.0 (所有网络接口)";
ipAddresses << "127.0.0.1 (本地回环)";
// 获取所有IPv4地址并过滤
QList<QHostAddress> allAddresses = QNetworkInterface::allAddresses();
for (const QHostAddress &address : allAddresses) {
if (address.protocol() == QAbstractSocket::IPv4Protocol &&
address != QHostAddress::LocalHost) {
// 复杂的网卡过滤逻辑
if (!interfaceName.contains("Virtual", Qt::CaseInsensitive) &&
!interfaceName.contains("VPN", Qt::CaseInsensitive)) {
ipAddresses.append(QString("%1 (%2)").arg(ip).arg(interfaceName));
}
}
}
return ipAddresses;
}
算法分析:
添加特殊地址选项
遍历所有网络接口
过滤IPv4和非回环地址
排除虚拟网卡和VPN
格式化为"IP (网卡名)"的友好格式服务器启动函数
bool TcpServer::startServer(const QString &ip, quint16 port)
{
if (m_tcpServer->isListening()) {
m_tcpServer->close(); // 先关闭已有服务
}
QHostAddress hostAddress;
// IP地址解析逻辑
if (ip.startsWith("0.0.0.0")) {
hostAddress = QHostAddress::Any;
m_currentIP = "0.0.0.0";
} else if (ip.contains("127.0.0.1")) {
// ... 处理回环地址
} else {
// 提取纯IP地址(去除描述文本)
QString cleanIP = ip;
int spaceIndex = ip.indexOf(' ');
if (spaceIndex > 0) {
cleanIP = ip.left(spaceIndex);
}
// IP格式验证
if (!hostAddress.setAddress(cleanIP)) {
updateStatusMessage("IP地址格式错误: " + cleanIP);
return false;
}
m_currentIP = cleanIP;
}
m_currentPort = port;
// 启动监听
if (!m_tcpServer->listen(hostAddress, port)) {
updateStatusMessage("启动服务器失败: " + m_tcpServer->errorString());
return false;
}
// 更新状态并发射信号
m_isListening = true;
updateStatusMessage(QString("服务器已启动 - IP: %1 端口: %2")
.arg(m_currentIP).arg(m_currentPort));
emit isListeningChanged();
emit currentIPChanged();
emit currentPortChanged();
return true;
}
关键处理:
IP地址智能解析(支持带描述的格式)
错误处理和用户反馈
状态同步和信号发射新连接处理
void TcpServer::onNewConnection()
{
QTcpSocket *clientSocket = m_tcpServer->nextPendingConnection();
// 连接客户端信号
connect(clientSocket, &QTcpSocket::disconnected,
this, &TcpServer::onClientDisconnected);
connect(clientSocket, &QTcpSocket::readyRead,
this, &TcpServer::onClientReadyRead);
// 更新客户端列表
m_clients.append(clientSocket);
m_clientCount = m_clients.size();
// 日志记录
QString clientInfo = QString("%1:%2")
.arg(clientSocket->peerAddress().toString())
.arg(clientSocket->peerPort());
appendReceivedData(QString("[新客户端连接]: %1").arg(clientInfo));
updateStatusMessage(QString("服务器运行中 - IP: %1 端口: %2 - 客户端数量: %3")
.arg(m_currentIP).arg(m_currentPort).arg(m_clientCount));
emit clientCountChanged();
}
连接管理:
自动信号槽连接(断开、数据可读)
客户端信息记录(IP:Port格式)
实时状态更新数据接收处理
void TcpServer::onClientReadyRead()
{
QTcpSocket *client = qobject_cast<QTcpSocket*>(sender());
if (!client) return;
// 读取所有可用数据,不依赖换行符
QByteArray data = client->readAll();
if (!data.isEmpty()) {
QString message = QString::fromUtf8(data);
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
QString clientInfo = QString("%1:%2")
.arg(client->peerAddress().toString())
.arg(client->peerPort());
// 移除可能的多余换行符
message = message.trimmed();
appendReceivedData(QString("[%1] %2: %3")
.arg(timestamp).arg(clientInfo).arg(message));
}
}
数据处理特点:
使用qobject_cast安全转换sender
按行读取数据(readLine())
添加时间戳和客户端信息
支持UTF-8编码
二、QML前端详细分析
2.1 主窗口结构 (ApplicationWindow)
整体布局
ApplicationWindow {
id: window
width: 800
height: 600
// 三个主要区域
Rectangle { id: controlPanel } // 顶部控制面板
Rectangle { id: clientPanel } // 左侧客户端列表
Rectangle { /* 消息显示区域 */ } // 右侧消息显示
}
2.2 控制面板详细分析
IP地址选择组件
ComboBox {
id: ipComboBox
Layout.preferredWidth: 250
editable: true
model: server.getAvailableIPs() // 绑定C++方法
onAccepted: {
if (find(editText) === -1) {
model = model.concat(editText) // 动态添加自定义IP
}
}
}
特性:
可编辑下拉框
动态模型更新
支持自定义IP输入
服务器控制按钮
Button {
id: startButton
text: server.isListening ? "停止服务器" : "启动服务器"
onClicked: {
if (server.isListening) {
server.stopServer()
} else {
var port = parseInt(portField.text)
if (port > 0 && port <= 65535) {
server.startServer(ipComboBox.currentText, port)
}
}
}
background: Rectangle {
color: server.isListening ? "#e74c3c" : "#2ecc71" // 状态颜色
}
}
状态管理:
文本动态变化
颜色状态指示(绿→启动,红→停止)
端口验证
2.3 客户端列表组件
列表视图和委托
ListView {
id: clientListView
model: server.clientCount // 直接绑定客户端数量
delegate: Rectangle {
RowLayout {
Label { text: "客户端 " + (index + 1) }
Button { // 发送按钮
onClicked: {
clientMessageDialog.clientIndex = index
clientMessageDialog.open()
}
}
Button { // 断开按钮
onClicked: server.disconnectClient(index)
}
}
}
}
设计特点:
简单模型(直接使用clientCount)
每个客户端项包含管理和操作按钮
索引传递用于客户端识别
2.4 消息发送对话框
Dialog {
id: clientMessageDialog
property int clientIndex: -1 // 自定义属性
title: "发送消息给客户端 " + (clientIndex + 1)
onAccepted: {
if (clientMessageField.text.trim() !== "") {
server.sendToClient(clientIndex, clientMessageField.text)
clientMessageField.clear()
}
}
}
交互设计:
模态对话框
属性传递客户端索引
输入验证和自动清理
三、前后端交互机制
3.1 注册和暴露接口
int main(int argc, char *argv[])
{
TcpServer tcpServer;
QQmlApplicationEngine engine;
// 关键:将C++对象暴露给QML
engine.rootContext()->setContextProperty("server", &tcpServer);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
3.2 数据绑定流程
属性绑定示例:
// QML中直接绑定C++属性
Label { text: server.statusMessage }
Label { text: "客户端数量: " + server.clientCount }
TextArea { text: server.receivedData }
方法调用示例:
Button {
onClicked: server.startServer(ipComboBox.currentText, port)
}
3.3 信号传播机制
C++端信号发射 → QML属性更新 → 界面自动刷新
二、所有源码
TcpServer.h文件源码
#ifndef TCPSERVER_H
#define TCPSERVER_H
#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>
#include <QList>
#include <QDateTime>
class TcpServer : public QObject
{
Q_OBJECT
Q_PROPERTY(bool isListening READ isListening NOTIFY isListeningChanged)
Q_PROPERTY(QString statusMessage READ statusMessage NOTIFY statusMessageChanged)
Q_PROPERTY(QString receivedData READ receivedData NOTIFY receivedDataChanged)
Q_PROPERTY(int clientCount READ clientCount NOTIFY clientCountChanged)
Q_PROPERTY(QString currentIP READ currentIP NOTIFY currentIPChanged)
Q_PROPERTY(quint16 currentPort READ currentPort NOTIFY currentPortChanged)
public:
explicit TcpServer(QObject *parent = nullptr);
~TcpServer();
bool isListening() const;
QString statusMessage() const;
QString receivedData() const;
int clientCount() const;
QString currentIP() const;
quint16 currentPort() const;
Q_INVOKABLE bool startServer(const QString &ip, quint16 port);
Q_INVOKABLE void stopServer();
Q_INVOKABLE void sendToAllClients(const QString &message);
Q_INVOKABLE void sendToClient(int index, const QString &message);
Q_INVOKABLE void disconnectClient(int index);
Q_INVOKABLE QStringList getAvailableIPs();
signals:
void isListeningChanged();
void statusMessageChanged();
void receivedDataChanged();
void clientCountChanged();
void currentIPChanged();
void currentPortChanged();
private slots:
void onNewConnection();
void onClientDisconnected();
void onClientReadyRead();
private:
QTcpServer *m_tcpServer;
QList<QTcpSocket*> m_clients;
bool m_isListening;
QString m_statusMessage;
QString m_receivedData;
int m_clientCount;
QString m_currentIP;
quint16 m_currentPort;
void updateStatusMessage(const QString &message);
void appendReceivedData(const QString &data);
};
#endif // TCPSERVER_H
TcpServer.cpp文件源码
#include "tcp_server.h"
#include <QNetworkInterface>
#include <QHostInfo>
TcpServer::TcpServer(QObject *parent)
: QObject(parent)
, m_tcpServer(new QTcpServer(this))
, m_isListening(false)
, m_clientCount(0)
, m_currentPort(0)
{
connect(m_tcpServer, &QTcpServer::newConnection, this, &TcpServer::onNewConnection);
updateStatusMessage("服务器未启动");
}
TcpServer::~TcpServer()
{
stopServer();
}
bool TcpServer::isListening() const
{
return m_isListening;
}
QString TcpServer::statusMessage() const
{
return m_statusMessage;
}
QString TcpServer::receivedData() const
{
return m_receivedData;
}
int TcpServer::clientCount() const
{
return m_clientCount;
}
QString TcpServer::currentIP() const
{
return m_currentIP;
}
quint16 TcpServer::currentPort() const
{
return m_currentPort;
}
QStringList TcpServer::getAvailableIPs()
{
QStringList ipAddresses;
// 添加特殊选项
ipAddresses << "0.0.0.0 (所有网络接口)";
ipAddresses << "127.0.0.1 (本地回环)";
// 获取所有IPv4地址
QList<QHostAddress> allAddresses = QNetworkInterface::allAddresses();
for (const QHostAddress &address : allAddresses) {
if (address.protocol() == QAbstractSocket::IPv4Protocol &&
address != QHostAddress::LocalHost) {
QNetworkInterface interface = QNetworkInterface::interfaceFromIndex(
address.scopeId().toInt());
if (interface.isValid() &&
(interface.flags() & QNetworkInterface::IsUp) &&
(interface.flags() & QNetworkInterface::IsRunning) &&
!(interface.flags() & QNetworkInterface::IsLoopBack)) {
QString ip = address.toString();
QString interfaceName = interface.humanReadableName();
// 排除常见的虚拟网卡和VPN
if (!interfaceName.contains("Virtual", Qt::CaseInsensitive) &&
!interfaceName.contains("VPN", Qt::CaseInsensitive) &&
!interfaceName.contains("VMware", Qt::CaseInsensitive) &&
!interfaceName.contains("VirtualBox", Qt::CaseInsensitive)) {
ipAddresses.append(QString("%1 (%2)").arg(ip).arg(interfaceName));
}
}
}
}
// 如果没有找到合适的IP,尝试使用主机名解析
if (ipAddresses.size() <= 2) { // 只有特殊选项
QString hostName = QHostInfo::localHostName();
QHostInfo hostInfo = QHostInfo::fromName(hostName);
for (const QHostAddress &address : hostInfo.addresses()) {
if (address.protocol() == QAbstractSocket::IPv4Protocol &&
address != QHostAddress::LocalHost) {
ipAddresses.append(address.toString());
}
}
}
return ipAddresses;
}
bool TcpServer::startServer(const QString &ip, quint16 port)
{
if (m_tcpServer->isListening()) {
m_tcpServer->close();
}
QHostAddress hostAddress;
// 解析IP地址
if (ip.startsWith("0.0.0.0")) {
hostAddress = QHostAddress::Any;
m_currentIP = "0.0.0.0";
} else if (ip.contains("127.0.0.1")) {
hostAddress = QHostAddress::LocalHost;
m_currentIP = "127.0.0.1";
} else {
// 提取纯IP地址(去掉括号内的描述)
QString cleanIP = ip;
int spaceIndex = ip.indexOf(' ');
if (spaceIndex > 0) {
cleanIP = ip.left(spaceIndex);
}
if (!hostAddress.setAddress(cleanIP)) {
updateStatusMessage("IP地址格式错误: " + cleanIP);
return false;
}
m_currentIP = cleanIP;
}
m_currentPort = port;
if (!m_tcpServer->listen(hostAddress, port)) {
updateStatusMessage("启动服务器失败: " + m_tcpServer->errorString());
return false;
}
m_isListening = true;
updateStatusMessage(QString("服务器已启动 - IP: %1 端口: %2").arg(m_currentIP).arg(m_currentPort));
emit isListeningChanged();
emit currentIPChanged();
emit currentPortChanged();
return true;
}
void TcpServer::stopServer()
{
if (m_tcpServer->isListening()) {
// 断开所有客户端连接
for (QTcpSocket *client : m_clients) {
client->disconnectFromHost();
if (client->state() != QAbstractSocket::UnconnectedState) {
client->waitForDisconnected(1000);
}
client->deleteLater();
}
m_clients.clear();
m_clientCount = 0;
emit clientCountChanged();
m_tcpServer->close();
m_isListening = false;
m_currentIP = "";
m_currentPort = 0;
updateStatusMessage("服务器已停止");
emit isListeningChanged();
emit currentIPChanged();
emit currentPortChanged();
}
}
void TcpServer::sendToAllClients(const QString &message)
{
if (m_clients.isEmpty()) {
return;
}
for (QTcpSocket *client : m_clients) {
if (client->state() == QAbstractSocket::ConnectedState) {
QByteArray data = (message + "\n").toUtf8();
client->write(data);
}
}
appendReceivedData(QString("[发送给所有客户端]: %1").arg(message));
}
void TcpServer::sendToClient(int index, const QString &message)
{
if (index < 0 || index >= m_clients.size()) {
return;
}
QTcpSocket *client = m_clients.at(index);
if (client->state() == QAbstractSocket::ConnectedState) {
QByteArray data = (message + "\n").toUtf8();
client->write(data);
appendReceivedData(QString("[发送给客户端%1]: %2").arg(index).arg(message));
}
}
void TcpServer::disconnectClient(int index)
{
if (index < 0 || index >= m_clients.size()) {
return;
}
QTcpSocket *client = m_clients.at(index);
client->disconnectFromHost();
}
void TcpServer::onNewConnection()
{
QTcpSocket *clientSocket = m_tcpServer->nextPendingConnection();
connect(clientSocket, &QTcpSocket::disconnected, this, &TcpServer::onClientDisconnected);
connect(clientSocket, &QTcpSocket::readyRead, this, &TcpServer::onClientReadyRead);
m_clients.append(clientSocket);
m_clientCount = m_clients.size();
QString clientInfo = QString("%1:%2").arg(clientSocket->peerAddress().toString()).arg(clientSocket->peerPort());
appendReceivedData(QString("[新客户端连接]: %1").arg(clientInfo));
updateStatusMessage(QString("服务器运行中 - IP: %1 端口: %2 - 客户端数量: %3").arg(m_currentIP).arg(m_currentPort).arg(m_clientCount));
emit clientCountChanged();
}
void TcpServer::onClientDisconnected()
{
QTcpSocket *client = qobject_cast<QTcpSocket*>(sender());
if (!client) {
return;
}
QString clientInfo = QString("%1:%2").arg(client->peerAddress().toString()).arg(client->peerPort());
m_clients.removeOne(client);
m_clientCount = m_clients.size();
appendReceivedData(QString("[客户端断开]: %1").arg(clientInfo));
updateStatusMessage(QString("服务器运行中 - IP: %1 端口: %2 - 客户端数量: %3").arg(m_currentIP).arg(m_currentPort).arg(m_clientCount));
client->deleteLater();
emit clientCountChanged();
}
void TcpServer::onClientReadyRead()
{
QTcpSocket *client = qobject_cast<QTcpSocket*>(sender());
if (!client) return;
// 读取所有可用数据,不依赖换行符
QByteArray data = client->readAll();
if (!data.isEmpty()) {
QString message = QString::fromUtf8(data);
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
QString clientInfo = QString("%1:%2")
.arg(client->peerAddress().toString())
.arg(client->peerPort());
// 移除可能的多余换行符
message = message.trimmed();
appendReceivedData(QString("[%1] %2: %3")
.arg(timestamp).arg(clientInfo).arg(message));
}
}
void TcpServer::updateStatusMessage(const QString &message)
{
if (m_statusMessage != message) {
m_statusMessage = message;
emit statusMessageChanged();
}
}
void TcpServer::appendReceivedData(const QString &data)
{
m_receivedData += data + "\n";
emit receivedDataChanged();
}
main.qml文件源码
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
ApplicationWindow {
id: window
width: 800
height: 600
title: "TCP服务器"
visible: true
// 服务器控制区域
Rectangle {
id: controlPanel
width: parent.width
height: 150
color: "#f0f0f0"
border.color: "#cccccc"
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
RowLayout {
Layout.fillWidth: true
Label {
text: "IP地址:"
}
ComboBox {
id: ipComboBox
Layout.preferredWidth: 250
editable: true
model: server.getAvailableIPs()
onAccepted: {
if (find(editText) === -1) {
model = model.concat(editText)
}
}
}
Label {
text: "端口号:"
}
TextField {
id: portField
Layout.preferredWidth: 100
text: "8080"
validator: IntValidator { bottom: 1; top: 65535 }
placeholderText: "输入端口号"
}
Button {
id: startButton
text: server.isListening ? "停止服务器" : "启动服务器"
onClicked: {
if (server.isListening) {
server.stopServer()
} else {
var port = parseInt(portField.text)
if (port > 0 && port <= 65535) {
server.startServer(ipComboBox.currentText, port)
} else {
statusLabel.text = "端口号无效"
}
}
}
background: Rectangle {
color: server.isListening ? "#e74c3c" : "#2ecc71"
radius: 4
}
contentItem: Text {
text: startButton.text
color: "white"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.bold: true
}
}
Button {
text: "刷新IP"
onClicked: {
ipComboBox.model = server.getAvailableIPs()
}
background: Rectangle {
color: "#3498db"
radius: 4
}
contentItem: Text {
text: "刷新IP"
color: "white"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
}
RowLayout {
Layout.fillWidth: true
Button {
text: "清空日志"
onClicked: {
receivedTextArea.clear()
}
background: Rectangle {
color: "#f39c12"
radius: 4
}
contentItem: Text {
text: "清空日志"
color: "white"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
Item {
Layout.fillWidth: true
}
Label {
text: "当前: " + (server.currentIP ? server.currentIP + ":" + server.currentPort : "未启动")
color: "#8e44ad"
font.bold: true
}
}
RowLayout {
Layout.fillWidth: true
Label {
id: statusLabel
text: server.statusMessage
Layout.fillWidth: true
color: server.isListening ? "#27ae60" : "#e74c3c"
font.bold: true
wrapMode: Text.Wrap
}
Label {
text: "客户端数量: " + server.clientCount
color: "#2980b9"
font.bold: true
}
}
RowLayout {
Layout.fillWidth: true
TextField {
id: messageField
Layout.fillWidth: true
placeholderText: "输入要发送的消息..."
onAccepted: sendButton.clicked()
}
Button {
id: sendButton
text: "发送给所有客户端"
enabled: server.isListening && server.clientCount > 0
onClicked: {
if (messageField.text.trim() !== "") {
server.sendToAllClients(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
}
}
}
}
}
// 客户端列表区域
Rectangle {
id: clientPanel
width: 200
anchors {
top: controlPanel.bottom
bottom: parent.bottom
left: parent.left
}
color: "#f8f9fa"
border.color: "#dee2e6"
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
Label {
text: "客户端列表"
font.bold: true
font.pixelSize: 16
Layout.alignment: Qt.AlignHCenter
}
ListView {
id: clientListView
Layout.fillWidth: true
Layout.fillHeight: true
model: server.clientCount
clip: true
delegate: Rectangle {
width: clientListView.width
height: 40
color: index % 2 === 0 ? "#ffffff" : "#f8f9fa"
border.color: "#dee2e6"
RowLayout {
anchors.fill: parent
anchors.margins: 5
Label {
text: "客户端 " + (index + 1)
Layout.fillWidth: true
elide: Text.ElideRight
}
Button {
text: "发送"
enabled: server.isListening
onClicked: {
clientMessageDialog.clientIndex = index
clientMessageDialog.open()
}
background: Rectangle {
color: "#3498db"
radius: 3
}
contentItem: Text {
text: "发送"
color: "white"
font.pixelSize: 10
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
Button {
text: "断开"
enabled: server.isListening
onClicked: server.disconnectClient(index)
background: Rectangle {
color: "#e74c3c"
radius: 3
}
contentItem: Text {
text: "断开"
color: "white"
font.pixelSize: 10
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
}
}
ScrollBar.vertical: ScrollBar {}
}
}
}
// 消息显示区域
Rectangle {
anchors {
top: controlPanel.bottom
bottom: parent.bottom
left: clientPanel.right
right: parent.right
}
color: "white"
border.color: "#dee2e6"
ScrollView {
anchors.fill: parent
anchors.margins: 5
TextArea {
id: receivedTextArea
text: server.receivedData
readOnly: true
wrapMode: TextArea.Wrap
selectByMouse: true
font.family: "Courier New"
font.pixelSize: 12
background: null
}
}
}
// 发送给特定客户端的对话框
Dialog {
id: clientMessageDialog
property int clientIndex: -1
title: "发送消息给客户端 " + (clientIndex + 1)
modal: true
standardButtons: Dialog.Ok | Dialog.Cancel
x: (window.width - width) / 2
y: (window.height - height) / 2
ColumnLayout {
width: parent ? parent.width : 100
Label {
text: "输入要发送的消息:"
}
TextField {
id: clientMessageField
Layout.fillWidth: true
placeholderText: "输入消息..."
onAccepted: clientMessageDialog.accept()
}
}
onAccepted: {
if (clientMessageField.text.trim() !== "") {
server.sendToClient(clientIndex, clientMessageField.text)
clientMessageField.clear()
}
}
onRejected: {
clientMessageField.clear()
}
}
}
main.cpp文件源码
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "tcp_server.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
// 注册TCP服务器到QML
TcpServer tcpServer;
QQmlApplicationEngine engine;
// 将TCP服务器实例暴露给QML
engine.rootContext()->setContextProperty("server", &tcpServer);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
三、效果演示
服务器端设置ip和port,测试发送和接受。
