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);
    }
}
相关推荐
kylezhao20191 小时前
如何在 C# 项目中使用 NLog 进行日志记录
开发语言·c#
好度1 小时前
配置java标准环境?(详细教程)
java·开发语言
teacher伟大光荣且正确2 小时前
关于Qt QReadWriteLock(读写锁) 以及 QSettings 使用的问题
java·数据库·qt
知新坊2 小时前
飞牛NAS 没有公网 IP?使用它让 NAS 访问、文件远程像在局域网
网络·网络协议·tcp/ip
建群新人小猿2 小时前
陀螺匠企业助手-我的日程
android·大数据·运维·开发语言·容器
superman超哥2 小时前
仓颉借用检查器工作原理深度解析
c语言·开发语言·c++·python·仓颉
悟能不能悟2 小时前
java map判断是否有key,get(key)+x,否则put(key,x)的新写法
java·开发语言
山风wind2 小时前
IP地址、子网掩码与网络连通性:从入门到精通
网络·tcp/ip·php
blurblurblun2 小时前
Go语言特性
开发语言·后端·golang