QT实现检测MySQL数据库连接状态并断开重连

背景

在QT中做有关于MySQL的业务时,有时候需要知道MySQL数据库的当前连接状态。但QT在提供的QSqlDatabase模块中并没有相关的功能,不像QTcpSocket在连接服务或断开服务时都有相应的信号发出。

网上方法
  • (1)使用QSqlDatabase::isOpen()判断

经测试,在突然与数据库所在网络断开后,该接口仍返回true,不靠谱。

  • (2)设置连接参数setConnectOptions(“MYSQL_OPT_RECONNECT=1”)

未测试,看网上的说法是在长时间未操作(默认8小时)数据库则会与数据库断开,如设置这个参数则会重连,但不知道在断网后的情况怎样。

  • (3)定时执行sql语句来判断

使用定时器,定时执行sql语句(“select 1”)来判断数据库是否连接正常。经测试,在突然断网的情况下,执行sql语句会阻塞20秒甚至更长的时间(不知道这个时间是由什么决定的?可否设置?);但若是数据库服务挂掉,则不会阻塞。

解决方法

采用第3种方法 + 每次执行sql前ping一下网络是否连接正常,以此来避免断网时执行sql的阻塞。
(1)子线程中创建专门的连接来检测数据库连接状态;
(2)其他线程则创建自己的连接来执行相应的业务;并可根据子线程检测的状态结果来做相应的判断。
如果有别的方法,还请大佬们指导一下

代码

.h文件

#ifndef DBHEARTBEATTHREAD_H
#define DBHEARTBEATTHREAD_H

#include <QObject>
#include <QThread>
#include <QMap>

class DBHeartbeatThread : public QThread
{
    Q_OBJECT

public:
    enum DBType : quint8 {
        MySQL = 0,
        SqlServer,
        Oracle
    };

    struct ConnectParam {
        QString ip;
        quint16 port;
        QString dbName;
        QString userName;
        QString password;
    };

public:
    explicit DBHeartbeatThread(QObject *parent = nullptr);
    explicit DBHeartbeatThread(const DBType &type, const ConnectParam &param, QObject *parent = nullptr);

public:
    void setDBTypeAndConnectParm(const DBType &type, const ConnectParam &param);
    void setCheckIpIsOnlineInterval(int msec);
    void setConnectDBInterval(int msec);
    void setCheckDBHeartbeatInterval(int msec);
    QString lastError() const;

protected:
    void run() override;

signals:
    void signal_connectedDB();       // 数据库连接成功
    void signal_disconnectedDB();    // 与数据库断开连接
    void signal_connectedNet();      // 网络连接成功
    void signal_disconnectedNet();   // 网络断开连接

private:
    DBType m_type;
    ConnectParam m_param;
    int m_checkIpIsOnlineInterval;
    int m_connectDBInterval;
    int m_checkDBHeartbeatInterval;

    QMap<DBType, QString> m_mapDBTypeDriver;
    QString m_lastError;

    const int kCheckIpFailTime = 3;    // 检测网络心跳失败最大次数
    const int kCheckDBFailTime = 3;    // 检测数据库心跳失败最大次数
};

#endif // DBHEARTBEATTHREAD_H

.cpp文件

#include "dbheartbeatthread.h"
#include <QDebug>
#include <QSqlError>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QTimer>
#include <QDateTime>
#include <QProcess>

DBHeartbeatThread::DBHeartbeatThread(QObject *parent) : QThread(parent)
{
    m_checkIpIsOnlineInterval = 100;
    m_connectDBInterval = 100;
    m_checkDBHeartbeatInterval = 100;
    m_lastError = "";

    m_mapDBTypeDriver = {{MySQL, "QMYSQL"}, {SqlServer, "QODBC"}, {Oracle, "QOCI"}};
}

DBHeartbeatThread::DBHeartbeatThread(const DBType &type, const ConnectParam &param, QObject *parent)
    : DBHeartbeatThread(parent)
{
    m_type = type;
    m_param = param;
}

void DBHeartbeatThread::setDBTypeAndConnectParm(const DBHeartbeatThread::DBType &type,
        const DBHeartbeatThread::ConnectParam &param)
{
    m_type = type;
    m_param = param;
}

void DBHeartbeatThread::setCheckIpIsOnlineInterval(int msec)
{
    m_checkIpIsOnlineInterval = msec;
}

void DBHeartbeatThread::setConnectDBInterval(int msec)
{
    m_connectDBInterval = msec;
}

void DBHeartbeatThread::setCheckDBHeartbeatInterval(int msec)
{
    m_checkDBHeartbeatInterval = msec;
}

QString DBHeartbeatThread::lastError() const
{
    return m_lastError;
}

void DBHeartbeatThread::run()
{
    /// 数据库连接异常:
    /// 1、断网,用checkIpIsOnline判断
    /// 2、数据库服务挂了,在MySQL上测试query.exec(sql)不会阻塞,会直接就是执行失败
    QSqlDatabase db;

    // 打开数据库
    auto openDB = [&]() -> bool {
        static QString connectName = "heartbeatCheck";
        if (QSqlDatabase::contains(connectName)) {
            db = QSqlDatabase::database(connectName);
        } else {
            db = QSqlDatabase::addDatabase(m_mapDBTypeDriver.value(m_type), connectName);
        }

        if (db.isOpen()) {
            db.close();
        }

        db.setHostName(m_param.ip);
        db.setPort(m_param.port);
        db.setDatabaseName(m_param.dbName);
        db.setUserName(m_param.userName);
        db.setPassword(m_param.password);

        QTime startTime = QTime::currentTime();
        if (db.open()) {
            QTime endTime = QTime::currentTime();
            qDebug() << "数据库打开成功耗时: " << startTime.msecsTo(endTime) << "ms";
            return true;
        }

        QTime endTime = QTime::currentTime();
        qDebug() << "数据库打开失败耗时: " << startTime.msecsTo(endTime) << "ms";
        m_lastError = QString("数据库打开失败,原因:%1").arg(db.lastError().text());
        return false;
    };

    // 检测数据库是否在线(执行sql操作)
    auto checkDBIsOnline = [&db]() -> bool {
        QSqlQuery query(db);
        QString sql = "select 1";

        QTime startTime = QTime::currentTime();
        if (query.exec(sql)) {
            return true;
        }

        QTime endTime = QTime::currentTime();
        qDebug() << "检测心跳断开耗时:" << startTime.msecsTo(endTime) << "ms";
        return false;
    };

    // 检测IP是否在线
    auto checkIpIsOnline = [](const QString & ip) -> bool {
        QProcess cmd;
        QString command = "";
#ifdef Q_OS_WIN
        /// -n 要发送的回显请求数
        /// -w 等待每次回复的超时时间(毫秒)
        command = QString("ping %1 -n 1 -w 1000").arg(ip);
#else
        /// -s ping发送的字节数
        /// -c ping的次数
        command = QString("ping -s 1 -c 1 %1").arg(ip);
#endif
        cmd.start(command);
        cmd.waitForFinished(1000 * 1);

        QString retStr = cmd.readAll();
        if (retStr.indexOf("ttl", 0, Qt::CaseInsensitive) == -1) {
            return false;
        }

        return true;
    };

    QTimer timerCheckIp;       // 检测IP是否在线定时器
    QTimer timerConnectDB;     // 连接数据库定时器
    QTimer timerDBHeartbeat;   // 检测数据库心跳定时器

    timerCheckIp.setInterval(m_checkIpIsOnlineInterval);
    connect(&timerCheckIp, &QTimer::timeout, [&] {
        timerCheckIp.stop();
        if (checkIpIsOnline(m_param.ip)) {
            qDebug() << "连接网络成功,正在连接数据库";
            emit signal_connectedNet();
            timerConnectDB.start();
        } else {
            timerCheckIp.start();
        }
    });

    timerConnectDB.setInterval(m_connectDBInterval);
    connect(&timerConnectDB, &QTimer::timeout, [&] {
        timerConnectDB.stop();
        if (checkIpIsOnline(m_param.ip)) {
            if (openDB()) {
                qDebug() << "连接数据库成功,开始心跳检测";
                emit signal_connectedDB();
                timerDBHeartbeat.start();
            } else {
                timerConnectDB.start();
            }
        } else {
            qDebug() << "网络断开,正在重连";
            emit signal_disconnectedNet();
            timerCheckIp.start();
        }
    });

    timerDBHeartbeat.setInterval(m_checkDBHeartbeatInterval);
    connect(&timerDBHeartbeat, &QTimer::timeout, [&] {
        static int checkIpFailTime = 0;
        static int checkDBFailTime = 0;
        timerDBHeartbeat.stop();

        if (checkIpIsOnline(m_param.ip)) {
            checkIpFailTime = 0;

            if (checkDBIsOnline()) {
                checkDBFailTime = 0;
                timerDBHeartbeat.start();
            } else {
                ++checkDBFailTime;
                if (checkDBFailTime == kCheckDBFailTime) {
                    checkDBFailTime = 0;
                    qDebug() << "数据库断开,正在重连";
                    emit signal_disconnectedDB();
                    timerConnectDB.start();
                } else {
                    timerDBHeartbeat.start();
                }
            }
        } else {
            ++checkIpFailTime;
            if (checkIpFailTime == kCheckIpFailTime) {
                checkIpFailTime = 0;
                qDebug() << "网络断开,正在重连";
                emit signal_disconnectedNet();
                timerCheckIp.start();
            } else {
                timerDBHeartbeat.start();
            }
        }
    });

    timerCheckIp.start();
    qDebug() << "正在连接网络";
    exec();
}

  • 10
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
Qt中连接MySQL数据库,需要使用Qt的数据库模块和MySQL驱动程序。下面是连接MySQL数据库的步骤: 1. 安装MySQL驱动程序:需要先下载并安装QtMySQL驱动程序。可以从Qt官网下载MySQL驱动程序或者使用第三方提供的MySQL驱动程序。 2. 引入数据库模块:在Qt项目中添加数据库模块,可以使用Qt Creator中的项目向导来实现,或者手动在项目文件中添加以下代码: ``` QT += sql ``` 3. 创建数据库连接:在应用程序中创建一个数据库连接对象,并设置连接参数,例如: ``` QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL"); db.setHostName("localhost"); db.setDatabaseName("mydb"); db.setUserName("username"); db.setPassword("password"); ``` 4. 打开数据库连接:执行以下代码来打开数据库连接: ``` if (!db.open()) { qDebug() << "Failed to connect to database."; return -1; } ``` 5. 执行SQL语句:使用QSqlQuery对象来执行SQL语句,例如: ``` QSqlQuery query; query.exec("SELECT * FROM mytable"); while (query.next()) { QString name = query.value(0).toString(); int age = query.value(1).toInt(); qDebug() << "Name:" << name << "Age:" << age; } ``` 以上就是连接MySQL数据库的基本步骤。需要注意的是,如果使用的是Qt官方提供的MySQL驱动程序,需要在Qt应用程序的运行目录中添加MySQL客户端库文件。如果使用第三方提供的MySQL驱动程序,则需要根据其文档来配置。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值