开源 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增删改查

本章节主要内容是:介绍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,测试发送和接受。

相关推荐
lyp90h7 小时前
高效SQLite操作:基于C++模板元编程的自动化封装
c++
白鲸开源7 小时前
3.1.8<3.2.0<3.3.1,Apache DolphinScheduler集群升级避坑指南
java·开源·github
minji...7 小时前
Linux相关工具vim/gcc/g++/gdb/cgdb的使用详解
linux·运维·服务器·c++·git·自动化·vim
_OP_CHEN7 小时前
C++基础:(九)string类的使用与模拟实现
开发语言·c++·stl·string·string类·c++容器·stl模拟实现
蓝天智能7 小时前
QT MVC中View的特点及使用注意事项
开发语言·qt·mvc
爱编程的化学家7 小时前
代码随想录算法训练营第27天 -- 动态规划1 || 509.斐波那契数列 / 70.爬楼梯 / 746.使用最小花费爬楼梯
数据结构·c++·算法·leetcode·动态规划·代码随想录
数字化顾问8 小时前
C++分布式语音识别服务实践——架构设计与关键技术
c++
智能化咨询8 小时前
C++分布式语音识别服务实践——性能优化与实战部署
c++
ajassi20009 小时前
开源 C++ QT QML 开发(十四)进程用途
c++·qt·开源