开源 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 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(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 
#include 
#include 
#include 
#include 
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 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 
#include 
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 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(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(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 
#include 
#include 
#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,测试发送和接受。

posted @ 2025-11-07 12:28  yangykaifa  阅读(2)  评论(0)    收藏  举报