Qt TCP连接硬件设备

Qt通过TCP方式连接硬件设备,一般有两种模式:服务器模式和客户端模式。

服务器模式:即硬件设备作为客户端主动连接Qt程序;

优点:能够实时掌握硬件设备的连接状况(如是否断线等),可以更加实时的处理需要进行重连的情况等。

缺点:设备的重连机制为黑盒,依赖设备TCP客户端的实现程序;

需要对设备的要求摸清楚,如有些设备可能会有心跳的反馈信息有详细的要求,如不按照 要求实现会导致掉线或其他状况发生。

客户端模式:即Qt程序作为客户端主动连接硬件设备(硬件设备是服务器);

优点:相比于服务器端,需要实现的功能略少,相对比较适合稳定的生产环境(物理环境、网络环境稳定,仪器设备运行环境稳定,不易出现各种故障)。

缺点:QTcpSocket客户端模式对连接的监控效果不太理想,可能需要重新实现一下检测是否断线并进行断线重连的机制(socket的连接状态码及write返回的效果可能不是真实情况或实时情况)。

Qt6支持了httpserver模式,Qt5不支持,需要基于QTcpSocket进行实现。

Demo:Qt5.15.2

cpp 复制代码
// TcpDeviceClient.h
#ifndef TCPDEVICECLIENT_H
#define TCPDEVICECLIENT_H

#include <QObject>
#include <QTcpSocket>
#include <QTimer>
#include <QHostAddress>
#include "logmanager.h"

class TcpDeviceClient : public QObject
{
    Q_OBJECT

public:
    explicit TcpDeviceClient(QObject *parent = nullptr);
    ~TcpDeviceClient();

    // 连接设备
    bool connectToDevice(const QString &host, quint16 port, int timeoutMs = 5000);

    // 断开连接
    void disconnectFromDevice();

    // 发送数据
    qint64 sendData(const QByteArray &data);

    // 获取连接状态
    bool isConnected() const;

    // 设置自动重连
    void setAutoReconnect(bool enable, int intervalMs = 5000);

    // 获取错误信息
    QString errorString() const;

signals:
    void connected();
    void disconnected();
    void dataReceived(const QByteArray &data);
    void errorOccurred(const QString &error);
    void connectionStatusChanged(bool connected);

public slots:
    void reconnect();

private slots:
    void onConnected();
    void onDisconnected();
    void onReadyRead();
    void onError(QAbstractSocket::SocketError error);
    void onReconnectTimeout();

private:
    QTcpSocket *m_socket;
    QTimer *m_reconnectTimer;
    QString m_host;
    quint16 m_port;
    bool m_autoReconnect;
    int m_reconnectInterval;
    bool m_isConnecting;
};

#endif // TCPDEVICECLIENT_H
cpp 复制代码
// TcpDeviceClient.cpp
#include "tcpdeviceclient.h"
#include <QDebug>

TcpDeviceClient::TcpDeviceClient(QObject *parent)
    : QObject(parent)
    , m_socket(new QTcpSocket(this))
    , m_reconnectTimer(new QTimer(this))
    , m_port(0)
    , m_autoReconnect(false)
    , m_reconnectInterval(5000)
    , m_isConnecting(false)
{
    //LogManager::initialize();
    // 连接信号槽
    connect(m_socket, &QTcpSocket::connected, this, &TcpDeviceClient::onConnected);
    connect(m_socket, &QTcpSocket::disconnected, this, &TcpDeviceClient::onDisconnected);
    connect(m_socket, &QTcpSocket::readyRead, this, &TcpDeviceClient::onReadyRead);
    connect(m_socket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::errorOccurred),
            this, &TcpDeviceClient::onError);

    // 重连定时器
    connect(m_reconnectTimer, &QTimer::timeout, this, &TcpDeviceClient::onReconnectTimeout);
    m_reconnectTimer->setSingleShot(true);
}

TcpDeviceClient::~TcpDeviceClient()
{
    disconnectFromDevice();
}

bool TcpDeviceClient::connectToDevice(const QString &host, quint16 port, int timeoutMs)
{
    qDebug()<<"尝试连接设备:"<<host<<":"<<port;
    if (m_isConnecting || isConnected()) {
        qDebug()<<"已有连接状态,不能重新连接"<<host<<"::"<<port;
        return false;
    }

    m_host = host;
    m_port = port;
    qDebug()<<"IP:"<<m_host<<"PORT:"<<m_port;
    m_isConnecting = true;

    // 连接超时处理
    QTimer::singleShot(timeoutMs, this, [this]() {
        if (m_isConnecting) {
            m_isConnecting = false;
            emit errorOccurred("连接超时");
            if (m_autoReconnect) {
                m_reconnectTimer->start(m_reconnectInterval);
            }
        }
    });

    m_socket->connectToHost(host, port);
    return true;
}

void TcpDeviceClient::disconnectFromDevice()
{
    qDebug()<<"TCPDeviceClient::disconnectFromDevice::";
    m_reconnectTimer->stop();
    m_isConnecting = false;
    // if(m_socket)
    // {
    //     qDebug()<<"tcp device client abort!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!11";
    //     m_socket->abort();
    // }

    if (m_socket->state() != QAbstractSocket::UnconnectedState) {
        m_socket->disconnectFromHost();
        if (m_socket->state() != QAbstractSocket::UnconnectedState) {
            m_socket->waitForDisconnected(1000);
        }
    }
}

qint64 TcpDeviceClient::sendData(const QByteArray &data)
{
    qDebug()<<"TCPDeviceClient::senddata";
    if (!isConnected()) {
        qDebug()<<"device is not connected.";
        emit errorOccurred("设备未连接");
        return -1;
    }

    qint64 bytesWritten = m_socket->write(data);
    if (bytesWritten == -1) {
        qDebug()<<"senddata failure.";
        emit errorOccurred("发送数据失败: " + m_socket->errorString());
    } else if (bytesWritten < data.size()) {
        qDebug()<<"senddata uncompleted.";
        emit errorOccurred("数据未完全发送");
    }

    return bytesWritten;
}

bool TcpDeviceClient::isConnected() const
{
    return m_socket->state() == QAbstractSocket::ConnectedState;
}

void TcpDeviceClient::setAutoReconnect(bool enable, int intervalMs)
{
    m_autoReconnect = enable;
    m_reconnectInterval = intervalMs;

    if (!enable) {
        m_reconnectTimer->stop();
    }
}

QString TcpDeviceClient::errorString() const
{
    return m_socket->errorString();
}

void TcpDeviceClient::reconnect()
{
    qDebug()<<"重新连接"<<m_host<<":"<<m_port;
    if (!m_host.isEmpty() && m_port > 0) {
        connectToDevice(m_host, m_port);
    }
}

void TcpDeviceClient::onConnected()
{
    m_isConnecting = false;
    m_reconnectTimer->stop();
    emit connected();
    emit connectionStatusChanged(true);
    qDebug() << "TCP设备连接成功:" << m_host << ":" << m_port;
}

void TcpDeviceClient::onDisconnected()
{
    emit disconnected();
    emit connectionStatusChanged(false);
    qDebug() << "TCP设备连接断开";

    if (m_autoReconnect) {
        m_reconnectTimer->start(m_reconnectInterval);
    }
}

void TcpDeviceClient::onReadyRead()
{
    QByteArray data = m_socket->readAll();
    qDebug()<<"TCP DEVICE CLIENT:data received:";
    qDebug()<<QString::fromStdString(data.toStdString());
    if (!data.isEmpty()) {
        emit dataReceived(data);
    }
}

void TcpDeviceClient::onError(QAbstractSocket::SocketError error)
{
    m_isConnecting = false;

    QString errorMsg;
    switch (error) {
    case QAbstractSocket::ConnectionRefusedError:
        errorMsg = "连接被拒绝";
        break;
    case QAbstractSocket::RemoteHostClosedError:
        errorMsg = "远程主机关闭连接";
        break;
    case QAbstractSocket::HostNotFoundError:
        errorMsg = "主机未找到";
        break;
    case QAbstractSocket::SocketAccessError:
        errorMsg = "套接字访问错误";
        break;
    case QAbstractSocket::SocketResourceError:
        errorMsg = "套接字资源错误";
        break;
    case QAbstractSocket::SocketTimeoutError:
        errorMsg = "套接字超时";
        break;
    case QAbstractSocket::NetworkError:
        errorMsg = "网络错误";
        break;
    default:
        errorMsg = "未知错误: " + m_socket->errorString();
        break;
    }

    emit errorOccurred(errorMsg);
    qDebug() << "TCP连接错误:" << errorMsg<<m_host<<":"<<m_port;

    if (m_autoReconnect && !m_isConnecting) {
        m_reconnectTimer->start(m_reconnectInterval);
    }
}

void TcpDeviceClient::onReconnectTimeout()
{
    if (m_autoReconnect && !m_host.isEmpty() && m_port > 0) {
        qDebug() << "尝试重新连接..." << m_host << ":" << m_port;
        connectToDevice(m_host, m_port);
    }
}
相关推荐
Elnaij几秒前
从C++开始的编程生活(18)——二叉搜索树基础
开发语言·c++
Java程序员威哥1 分钟前
【包教包会】SpringBoot依赖Jar指定位置打包:配置+原理+避坑全解析
java·开发语言·spring boot·后端·python·微服务·jar
a程序小傲2 分钟前
中国邮政Java面试被问:边缘计算的数据同步和计算卸载
java·服务器·开发语言·算法·面试·职场和发展·边缘计算
Java程序员威哥2 分钟前
Java微服务可观测性实战:Prometheus+Grafana+SkyWalking全链路监控落地
java·开发语言·python·docker·微服务·grafana·prometheus
C语言小火车3 分钟前
Qt信号与槽本质解析(面试复习版)
qt·面试·系统架构·面试题
全栈软件开发6 分钟前
PHP实时消息聊天室源码 PHP+WebSocket
开发语言·websocket·php
小尧嵌入式9 分钟前
【Linux开发二】数字反转|除数累加|差分数组|vector插入和访问|小数四舍五入及向上取整|矩阵逆置|基础文件IO|深入文件IO
linux·服务器·开发语言·c++·线性代数·算法·矩阵
一只小bit12 分钟前
Qt 网络:包含Udp、Tcp、Http三种协议的客户端实践手册
前端·c++·qt·页面
Java后端的Ai之路12 分钟前
【AI应用开发工程师】-分享Java 转 AI正确思路
java·开发语言·人工智能·java转行·程序员转型
wheeldown15 分钟前
【Linux】TCP协议【2】: 从 echo 到远程命令执行:Linux TCP 服务器的并发与安全实践
linux·服务器·tcp/ip