QT编程之TCP编程

一、使用TcpServer和TcpSocket实现通讯,C++实现逻辑,QML实现UI界面。

二、服务器端实现

1、qml代码

css 复制代码
import QtQuick 2.15
import QtQuick.Controls 2.15
import com.example 1.0

Rectangle {
    id:fuwuqi
    width: 400
    height: 600
    property string str: ""
    property string editcontext: ""
    Text{
        id:xianshi
        anchors.top: parent.top
        x:5
        y:0
        width: 390
        height: 400
        Rectangle{
            color: 'pink'
            opacity: 0.25
            anchors.fill: parent
        }
    }
    TextEdit{
        id:shuru
        height: 180
        width: 390
        y:415
        x:5
        text:""
        Rectangle{
            color: 'pink'
            anchors.fill: parent
            opacity:0.5
        }
        onTextChanged: {
            console.log('改变了'+shuru.text)
        }
    }
    Button{
        id:fasong
        text:'发送'
        height: 15
        width: 60
        x:340
        y:400
        onClicked: {
            console.log("数据已发送")
            xianshi.text=xianshi.text+"服务器:\n     "+shuru.text+"\n"
            myLogciControl.sendDataToClient("172.22.122.113", shuru.text);
            shuru.text=''
        }
    }
    Button{
        id:qingpin
        text:'清屏'
        height: 15
        width: 60
        x:0
        y:400
        onClicked: {
            console.log("屏幕已清空")
            xianshi.text=''
        }
    }
    //注册TCP服务器

    MyTcpServer{
        id:myLogciControl
        onMsgChanged: {
            xianshi.text=xianshi.text+msg+"\n";
        }
        onReceivedStringChanged:{
         xianshi.text=xianshi.text+"客户端:\n     "+receivedString+"\n";
        }
    }
    Component.onCompleted: {
        myLogciControl.startServer("172.22.122.113",1234);
    }
}

2、.mytcpserver.h代码

cpp 复制代码
#ifndef MYTCPCLIENT_H
#define MYTCPCLIENT_H

#include <QTcpSocket>
#include <QTcpServer>
#include <QHostAddress>
// 1. 添加QML注册宏(必须在类定义前)
// 命名空间:com.example,版本:1.0,QML中类型名:MyTcpServer
#define QML_REGISTER_TCPSERVER  qmlRegisterType<MyTcpServer>("com.example", 1, 0, "MyTcpServer")
class MyTcpServer : public QTcpServer
{
    Q_OBJECT
    Q_PROPERTY(bool isListening READ isListening NOTIFY isListeningChanged)
    Q_PROPERTY(QString msg READ msg WRITE setMsg NOTIFY msgChanged)
    Q_PROPERTY(QString receivedString READ receivedString WRITE setReceivedString NOTIFY receivedStringChanged)
public:
    explicit MyTcpServer(QTcpServer *parent = nullptr);
    ~MyTcpServer() override;
    Q_INVOKABLE bool startServer(const QString &address = "0.0.0.0", quint16 port = 8888); // 启动服务器
    Q_INVOKABLE void stopServer(); // 停止服务器
    Q_INVOKABLE bool sendDataToClient(const QString &clientAddr,  QString data); // 给指定客户端发数据
   // Q_INVOKABLE void broadcastData(const QByteArray &data); // 广播数据给所有客户端
    // 获取服务器状态(匹配Q_PROPERTY)
    bool isListening();
    QString msg();
    void setMsg(QString msg);
    QString receivedString();
    void setReceivedString(QString str);
signals:
    // 通知QML的核心信号
    void isListeningChanged(); // 监听状态变化
    void serverStarted(); // 服务器启动成功
    void serverStopped(); // 服务器停止
    void newClientConnected(const QString &clientAddr, quint16 clientPort); // 新客户端连接
    void clientDisconnected(const QString &clientAddr, quint16 clientPort); // 客户端断开
    void errorOccurred(const QString &errorMsg); // 错误通知
    void msgChanged();
    void receivedStringChanged();
private slots:
    void onNewConnection(); // 处理新连接
    void onClientDisconnected(); // 处理客户端断开
    void onClientReadyRead(); // 处理客户端数据接收
    //void onServerError(QAbstractSocket::SocketError socketError); // 服务器错误处理
private:
    QTcpServer *m_tcpServer = nullptr; // TCP服务器核心对象
    QList<QTcpSocket*> m_clientSockets; // 管理所有客户端Socket
    // // 辅助方法:通过地址+端口查找客户端Socket
    // QTcpSocket* findClientSocket(const QString &clientAddr, quint16 clientPort) const;
    // // 重载:仅通过地址(简化QML调用)
     QTcpSocket* findClientSocket(const QString &clientAddr);
     QString m_msg;
     QString m_receivedString;
};
#endif // MYTCPCLIENT_H

3、mytcpserver.cpp代码

cpp 复制代码
#include "mytcpserver.h"
MyTcpServer::MyTcpServer(QTcpServer *parent)
    : QTcpServer{parent}
{
    m_tcpServer = new QTcpServer(this);
    // 绑定服务器核心信号
    connect(m_tcpServer, &QTcpServer::newConnection, this, &MyTcpServer::onNewConnection);
   // connect(m_tcpServer, &QTcpServer::acceptError, this, &MyTcpServer::onServerError);
    qDebug()<<"服务器初始化开始";
}

MyTcpServer::~MyTcpServer(){
    m_clientSockets.clear();
}

bool MyTcpServer::startServer(const QString &address, quint16 port)
{
    // 若已监听,先停止
    if (m_tcpServer->isListening()) {
        stopServer();
    }
    // 解析监听地址(0.0.0.0表示监听所有网卡)
    QHostAddress listenAddr = QHostAddress::Any;
    if (!address.isEmpty() && address != "0.0.0.0") {
        listenAddr = QHostAddress(address);
        if (listenAddr.isNull()) { // 地址解析失败
            emit errorOccurred(QString("无效的监听地址:%1").arg(address));
            return false;
        }
    }

    // 开始监听
    bool success = m_tcpServer->listen(listenAddr, port);
    if (success) {
        emit serverStarted();
        emit isListeningChanged();
        qDebug() << "服务器启动成功,监听地址:" << listenAddr.toString() << " 端口:" << port;
    } else {
        emit errorOccurred(QString("服务器启动失败:%1").arg(m_tcpServer->errorString()));
    }
    return success;
}


void MyTcpServer::stopServer(){
    if(!m_tcpServer->isListening()){
        return;
    }
    m_tcpServer->close();
    // 断开所有客户端(触发disconnected信号)
    for (QTcpSocket *socket : m_clientSockets){
        if(socket->state()!=QTcpSocket::UnconnectedState){
            socket->disconnectFromHost();
            socket->waitForDisconnected(1000);
        }
    }
    emit serverStopped();
    emit isListeningChanged();
    qDebug() << "服务器已停止";
}


void MyTcpServer::onNewConnection(){
    // 取出所有待处理的新连接(防止漏接)
    while (m_tcpServer->hasPendingConnections()){
        QTcpSocket *clientSocket = m_tcpServer->nextPendingConnection();
        if(!clientSocket){
            continue;
            qDebug()<<"满了???";
        }
        connect(clientSocket, &QTcpSocket::disconnected, this, &MyTcpServer::onClientDisconnected);
        connect(clientSocket, &QTcpSocket::readyRead, this, &MyTcpServer::onClientReadyRead);
        // 添加到客户端列表
        m_clientSockets.append(clientSocket);
        // 通知QML新客户端连接
        QString clientAddr = clientSocket->peerAddress().toString();
        quint16 clientPort = clientSocket->peerPort();
        emit newClientConnected(clientAddr, clientPort);
        qDebug() << "新客户端连接:" << clientAddr << ":" << clientPort;
        QString str="客户端新连接:" + clientAddr + " : " + QString::number(clientPort);
        setMsg(str);
        emit msgChanged();
    }
}

void MyTcpServer::onClientDisconnected(){
    // 获取断开的客户端Socket
    QTcpSocket *clientSocket = qobject_cast<QTcpSocket*>(sender());
    if (!clientSocket) {
        return;
    }
    // 记录客户端信息(断开前获取)
    QString clientAddr = clientSocket->peerAddress().toString();
    quint16 clientPort = clientSocket->peerPort();
    // 从列表移除并释放
    m_clientSockets.removeOne(clientSocket);
    clientSocket->deleteLater(); // 安全释放
    // 通知QML客户端断开
    emit clientDisconnected(clientAddr, clientPort);
    QString str="客户端断开连接:" + clientAddr + ":" + clientPort;
    setMsg(str);
    qDebug() << str;
    emit msgChanged();
}

void MyTcpServer::onClientReadyRead(){
    // 获取发送数据的客户端Socket
    QTcpSocket *clientSocket = qobject_cast<QTcpSocket*>(sender());
    if (!clientSocket) {
        return;
    }
    // 读取所有可用数据
    QByteArray data = clientSocket->readAll();
    QString clientAddr = clientSocket->peerAddress().toString();
    // 通知QML收到数据
    m_receivedString=data;
    emit receivedStringChanged();
    qDebug() << "收到客户端" << clientAddr << "数据:" << data;
}

QTcpSocket* MyTcpServer::findClientSocket(const QString &clientAddr){
    // 简化版:仅匹配地址(若同一客户端多端口连接,取第一个)
    for (QTcpSocket *socket : m_clientSockets) {
        if (socket->peerAddress().toString() == clientAddr) {
            return socket;
        }
    }
    return nullptr;
}

bool MyTcpServer::sendDataToClient(const QString &clientAddr,  QString str){
    QByteArray data= str.toUtf8();
    QTcpSocket* clientSocket = findClientSocket(clientAddr);
    if(!clientSocket||clientSocket->state() != QTcpSocket::ConnectedState){
        emit errorOccurred(QString("客户端未连接:%1").arg(clientAddr));
        return false;
    }
    qDebug()<<"发送事件";
    qint64 bytesWritten=clientSocket->write(data);
    if (bytesWritten == -1) {
        emit errorOccurred(QString("发送数据失败:%1").arg(clientSocket->errorString()));
        return false;
    }
    // 强制刷新缓冲区(确保数据立即发送)
    clientSocket->flush();
    qDebug() << "向客户端" << clientAddr << "发送数据:" << data;
    return true;
}
bool MyTcpServer::isListening(){
     return m_tcpServer->isListening();
}
QString MyTcpServer::msg(){
    return m_msg;
}
void MyTcpServer::setMsg(QString msg){
    m_msg=msg;
}
QString MyTcpServer::receivedString(){
    return m_receivedString;
}
void MyTcpServer::setReceivedString(QString str){
    m_receivedString=str;
}

三、客户端代码

1、qml代码

css 复制代码
import QtQuick 2.15
import QtQuick.Controls 2.15
import com.example 1.0

Rectangle {
    id:fuwuqi
    width: 400
    height: 600
    property string str: ""
    property string editcontext: ""
    Text{
        id:xianshi
        anchors.top: parent.top
        x:5
        y:0
        width: 390
        height: 400
        Rectangle{
            color: 'pink'
            opacity: 0.25
            anchors.fill: parent
        }
    }
    TextEdit{
        id:shuru
        height: 180
        width: 390
        y:415
        x:5
        text:""
        Rectangle{
            color: 'pink'
            anchors.fill: parent
            opacity:0.5
        }
        onTextChanged: {
            console.log('改变了'+shuru.text)
        }
    }
    Button{
        id:fasong
        text:'发送'
        height: 15
        width: 60
        x:340
        y:400
        onClicked: {
            console.log("数据已发送")
            xianshi.text=xianshi.text+"客户端:\n    "+shuru.text+"\n"
            myLogciControl.sendData(shuru.text)
            shuru.text=''
        }
    }
    Button{
        id:qingpin
        text:'清屏'
        height: 15
        width: 60
        x:0
        y:400
        onClicked: {
            console.log("屏幕已清空")
            xianshi.text=''
        }
    }
    //注册TCP服务器
    TcpClientControl{
        id:myLogciControl
        onMsgChanged: {
            xianshi.text=xianshi.text+ msg +"\n"
        }
        onReceivedStringChanged: {
             xianshi.text=xianshi.text+ "服务器\n     "+receivedString +"\n"
        }
    }
    Component.onCompleted: {
       myLogciControl.connectToServer("172.22.122.113",1234)
    }
}

2、tcpclientcontrol.h代码

cpp 复制代码
#ifndef TCPCLIENTCONTROL_H
#define TCPCLIENTCONTROL_H
#include <QObject>
#include <QTcpSocket>
#define QML_REGISTER_TCPSCLIENTCONTROL  qmlRegisterType<TcpClientControl>("com.example", 1, 0, "TcpClientControl")
class TcpClientControl : public QObject
{
    Q_OBJECT
    // 暴露连接状态给QML(是否已连接服务器)
    Q_PROPERTY(bool isConnected READ isConnected NOTIFY isConnectedChanged);
    // 暴露当前连接的服务器地址+端口(可选)
    Q_PROPERTY(QString serverAddress READ serverAddress NOTIFY serverAddressChanged);
    Q_PROPERTY(quint16 serverPort READ serverPort NOTIFY serverPortChanged);
    Q_PROPERTY(QString msg READ msg WRITE setMsg NOTIFY msgChanged)
    Q_PROPERTY(QString receivedString READ receivedString WRITE setReceivedString NOTIFY receivedStringChanged)
public:
    explicit TcpClientControl(QObject *parent = nullptr);
    // 供QML调用的核心方法
    Q_INVOKABLE bool connectToServer(const QString &address, quint16 port); // 连接服务器
    Q_INVOKABLE void disconnectFromServer(); // 断开连接
    Q_INVOKABLE bool sendString(const QString &dataStr); // 发送字符串(适配QML)
    Q_INVOKABLE bool sendData(const QByteArray &data); // 发送二进制数据(底层)
    Q_INVOKABLE bool reconnect(); // 重连服务器(可选)
    // 属性读取方法
    bool isConnected() const;
    QString serverAddress() const;
    quint16 serverPort() const;
    void setMsg(QString msg);
    QString msg();
    QString receivedString();
    void setReceivedString(QString str);
signals:
    // 通知QML的核心信号
    void isConnectedChanged(); // 连接状态变化
    void serverAddressChanged();
    void serverPortChanged();
    void connectedToServer(); // 连接成功
    void disconnectedFromServer(); // 断开连接
    void dataReceived(const QByteArray &data); // 收到二进制数据
    void errorOccurred(const QString &errorMsg); // 错误通知
    void msgChanged();
    void receivedStringChanged();
private slots:
    // 内部信号槽(处理Socket事件)
    void onSocketConnected(); // 连接成功
    void onSocketDisconnected(); // 断开连接
    void onSocketReadyRead(); // 收到数据
    void onSocketError(QAbstractSocket::SocketError socketError); // 错误处理
private:
    QTcpSocket *m_tcpSocket = nullptr; // 核心Socket对象
    QString m_serverAddress; // 记录当前连接的服务器地址
    quint16 m_serverPort = 0; // 记录当前连接的服务器端口
    QString m_msg;
    QString m_receivedString;
};
#endif // TCPCLIENTCONTROL_H

3、tcpclientcontrol.cpp代码

cpp 复制代码
#include "tcpclientcontrol.h"
#include <QDebug>
TcpClientControl::TcpClientControl(QObject *parent)
    : QObject(parent)
{
    // 初始化Socket
    m_tcpSocket = new QTcpSocket(this);
    // 绑定Socket核心信号
    connect(m_tcpSocket, &QTcpSocket::connected, this, &TcpClientControl::onSocketConnected);
    connect(m_tcpSocket, &QTcpSocket::disconnected, this, &TcpClientControl::onSocketDisconnected);
    connect(m_tcpSocket, &QTcpSocket::readyRead, this, &TcpClientControl::onSocketReadyRead);
    connect(m_tcpSocket, &QTcpSocket::errorOccurred, this, &TcpClientControl::onSocketError);
}

bool TcpClientControl::connectToServer(const QString &address, quint16 port)
{
    // 若已连接,先断开
    if (m_tcpSocket->state() == QTcpSocket::ConnectedState) {
        disconnectFromServer();
    }
    // 记录服务器地址和端口
    m_serverAddress = address;
    m_serverPort = port;
    emit serverAddressChanged();
    emit serverPortChanged();
    // 连接服务器(异步,结果通过connected/disconnected/error信号通知)
    m_tcpSocket->connectToHost(QHostAddress(address), port);
    // 无需等待连接(避免阻塞QML主线程),状态通过信号通知
    return true;
}

void TcpClientControl::disconnectFromServer()
{
    if (m_tcpSocket->state() != QTcpSocket::UnconnectedState) {
        m_tcpSocket->disconnectFromHost();
        // 若处于连接中状态,强制关闭
        if (m_tcpSocket->state() == QTcpSocket::ConnectingState) {
            m_tcpSocket->abort();
        }
    }
}

bool TcpClientControl::sendString(const QString &dataStr)
{
    // QML字符串转QByteArray(UTF-8编码,避免中文乱码)
    QByteArray data = dataStr.toUtf8();
    return sendData(data);
}

bool TcpClientControl::sendData(const QByteArray &data)
{
    // 检查连接状态
    if (m_tcpSocket->state() != QTcpSocket::ConnectedState) {
        emit errorOccurred("未连接到服务器,发送失败");
        return false;
    }

    // 发送数据(异步,返回是否写入缓冲区成功)
    qint64 bytesWritten = m_tcpSocket->write(data);
    if (bytesWritten == -1) {
        emit errorOccurred(QString("发送数据失败:%1").arg(m_tcpSocket->errorString()));
        return false;
    }

    // 强制刷新缓冲区(确保数据立即发送)
    m_tcpSocket->flush();
    qDebug() << "客户端发送数据:" << data;
    return true;
}

bool TcpClientControl::reconnect()
{
    // 重连上次连接的服务器
    if (m_serverAddress.isEmpty() || m_serverPort == 0) {
        emit errorOccurred("未记录服务器地址和端口,重连失败");
        return false;
    }
    return connectToServer(m_serverAddress, m_serverPort);
}

bool TcpClientControl::isConnected() const
{
    return m_tcpSocket->state() == QTcpSocket::ConnectedState;
}

QString TcpClientControl::serverAddress() const
{
    return m_serverAddress;
}

quint16 TcpClientControl::serverPort() const
{
    return m_serverPort;
}

void TcpClientControl::onSocketConnected()
{
    emit isConnectedChanged();
    emit connectedToServer();
    QString str= "客户端与服务器的连接:" + m_serverAddress + " : " + QString::number(m_serverPort);
    setMsg(str);
    emit msgChanged();
    qDebug() << str;
}

void TcpClientControl::onSocketDisconnected()
{
    emit isConnectedChanged();
    emit disconnectedFromServer();
    QString str= "客户端断开与服务器的连接:" + m_serverAddress + ":" + m_serverPort;
    setMsg(str);
    emit msgChanged();
}

void TcpClientControl::onSocketReadyRead()
{
    // 读取所有可用数据
    QByteArray data = m_tcpSocket->readAll();
    if (data.isEmpty()) {
        return;
    }
    // 发送二进制数据信号(供需要原始字节的场景)
    emit dataReceived(data);
    // 转换为字符串信号(适配QML显示)
    QString dataStr = QString::fromUtf8(data);
    m_receivedString=dataStr;
    emit receivedStringChanged();
    qDebug() << "客户端收到服务器数据:" << data << "(字符串:" << dataStr << ")";
}

void TcpClientControl::onSocketError(QAbstractSocket::SocketError socketError)
{
    Q_UNUSED(socketError);
    QString errorMsg = QString("客户端错误:%1").arg(m_tcpSocket->errorString());
    emit errorOccurred(errorMsg);
    emit isConnectedChanged(); // 错误会导致连接状态变化
    qDebug() << errorMsg;
}

QString TcpClientControl::msg(){
    return m_msg;
}
void TcpClientControl::setMsg(QString msg){
    m_msg=msg;
}
QString TcpClientControl::receivedString(){
    return m_receivedString;
}
void TcpClientControl::setReceivedString(QString str){
    m_receivedString=str;
}
相关推荐
Blasit5 小时前
Qt C++ 编译 libevent静态库
开发语言·c++·qt
宵时待雨5 小时前
C语言笔记归纳19:动态内存管理
java·开发语言·算法
weixin_307779135 小时前
Jenkins Pipeline共享库(Shared Library)完全指南
运维·开发语言·自动化·jenkins·etl
weixin_307779135 小时前
Jenkins Font Awesome API插件:现代化插件界面的图标引擎
开发语言·前端·自动化·jenkins
铅笔小新z5 小时前
【C++】 vector 全面解析:从使用到底层实现
开发语言·c++
好好沉淀5 小时前
开发过程中动态 SQL 中where 1=1的作用是什么
java·服务器·开发语言·数据库·sql
froginwe115 小时前
Bootstrap4 输入框组
开发语言
listhi5205 小时前
matlab大规模L1范数优化问题
开发语言·matlab
傅里叶的耶5 小时前
C++ Primer Plus(第6版):第二章 开始学习C++
开发语言·c++·学习