C++ Qt TCP的心跳检测机制,断线重连技术,应用层代码重新实现

目录

前言:

一、Qt直接启动本身的KeepAlive

二、在应用层自己实现一个心跳检测

 三、自定义心跳代码实现:

完整客户端服务端工程下载:

共用的结构体相关头文件:

        客户端部分核心代码:

        服务端部分核心代码:

运行结果展示:


前两篇关于qt tcp 相关的,可以通过以下传送门查看:

Qt TCP相关的一些整理:客户端常见操作 socket 通信 network-CSDN博客

Qt TCP相关的一些整理:服务端常见操作 socket 通信 network-CSDN博客

前言:

        TCP本身是有一个保活状态的 keep-alive机制,默认是关闭的,需要单独启动就可以;默认保活时间是2小时,不过这个机制是在协议层,也就是传输层生效的,如果应用层出问题了,就不能及时发现问题;如果想要实现断线重连的操作,这个就不好实现了。

        另一种方式,可以在应用层自定义模拟这个心跳检测机制,使用线程或者定时器来定时发心跳包即可实现保活功能,并且能做到断线重连的操作。

一、Qt直接启动本身的KeepAlive

    m_client = new QTcpSocket(this);
    // 启动心跳检测
    m_client->setSocketOption(QAbstractSocket::KeepAliveOption,true);
    m_client->connectToHost("127.0.0.1",8898);

二、在应用层自己实现一个心跳检测

        客户端部分:需要使用定时器来定时发送心跳包,并且根据间隔和保活总时长来设定一个阈值次数,一开始按最大时长的次数来初始化,当不停的递减阈值为0时,就需要断线,如果需要重连,那就重新连接一下;当然收到包时,需要重置阈值;

        服务端部分:需要使用一个map容器来保存已经连上的套接字及阈值,起一条线程来定时轮询容器,当发现阈值为0时则断线,并且从容器中删除键值对;当新的客户端连接成功连上时,要增加新的键值对到map容器中;当收到数据包时,要对阈值进行重置;

 三、自定义心跳代码实现:

完整客户端服务端工程下载:

点我下载

共用的结构体相关头文件:

struct_data.h 源码
#ifndef STRUCT_DATA_H
#define STRUCT_DATA_H

enum TypeInfo{
    HEART_CHECK_REQ,   // 心跳检测请求
    HEART_CHECK_RES,   // 心跳检测响应
};

struct Head
{
    int type;
    int len;
};

struct HeartCheckReq
{
    Head head;
    HeartCheckReq() {
        head.type = HEART_CHECK_REQ;
        head.len = sizeof(HeartCheckReq);
    }
};

struct HeartCheckRes
{
    Head head;
    HeartCheckRes() {
        head.type = HEART_CHECK_RES;
        head.len = sizeof(HeartCheckRes);
    }
};

#endif // STRUCT_DATA_H

        客户端部分核心代码:

头文件代码: 

#ifndef TCPMAINWINDOW_H
#define TCPMAINWINDOW_H

#include <QMainWindow>
#include <QTcpSocket>
#include <QTimer>
#include <QTime>
#include "struct_data.h"

namespace Ui {
class TcpMainWindow;
}

class TcpMainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit TcpMainWindow(QWidget *parent = 0);
    ~TcpMainWindow();

private slots:
    void myRead();  // 收包槽函数

    void on_pushButton_clicked();

    void heartCheckSlot(); // 定时发心跳包的槽

private:
    Ui::TcpMainWindow *ui;
    QTcpSocket *m_client;
    int m_heartCheckTimes;
    QTimer *m_checkTimer;

};

#endif // TCPMAINWINDOW_H

源文件代码:

#include "tcpmainwindow.h"
#include "ui_tcpmainwindow.h"
#include <QDebug>

#define HEART_CHECK_TIMES 6 // 保活30秒,每5秒发一次心跳包,阈值为6

TcpMainWindow::TcpMainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::TcpMainWindow)
{
    ui->setupUi(this);
    m_client = new QTcpSocket(this);
    // 启动tcp默认的保活
    //m_client->setSocketOption(QAbstractSocket::KeepAliveOption,true);
    m_client->connectToHost("127.0.0.1",8898);
    if(m_client->waitForConnected()){
        qDebug()<<"conn ok";
        connect(m_client,SIGNAL(readyRead()),this,SLOT(myRead()));
        m_heartCheckTimes = HEART_CHECK_TIMES; // 阈值初始化
        // 心跳检测相关的定时器 和 关联操作
        m_checkTimer = new QTimer(this);
        connect(m_checkTimer,SIGNAL(timeout()),this,SLOT(heartCheckSlot()));
        m_checkTimer->start(5000); // 5秒的间隔定时

    }else{
        qDebug()<<"conn fail"<<m_client->errorString();
    }
}

TcpMainWindow::~TcpMainWindow()
{
    delete ui;
}

void TcpMainWindow::myRead()
{
    QByteArray buffer = m_client->readAll();
    qDebug()<<buffer;
    // 只是简单的打印输出,还没有做解包处理

    m_heartCheckTimes = HEART_CHECK_TIMES;
}

void TcpMainWindow::on_pushButton_clicked()
{
    char buffer[] = "码蚁软件欢迎您";
    qDebug()<<m_client->write(buffer,sizeof(buffer));
}

void TcpMainWindow::heartCheckSlot()
{
    HeartCheckReq req;
    m_client->write((char*)&req,req.head.len); // 发送心跳包
    m_heartCheckTimes--; // 递减阈值
    ui->textBrowser->append(QString("当前时间:%1 心跳阈值为 %2").arg(QTime::currentTime().toString()).arg(m_heartCheckTimes));
    if(m_heartCheckTimes <= 0){
        // 需要做断线重连操作
        m_client->close();
        m_client->connectToHost("127.0.0.1",8898);
        if(m_client->waitForConnected()){
            m_heartCheckTimes = HEART_CHECK_TIMES; // 重连成功,重置阈值
            ui->textBrowser->append("重连成功");
        }
    }
}

        服务端部分核心代码:

头文件代码: 

#ifndef SERVERMAINWINDOW_H
#define SERVERMAINWINDOW_H

#include <QMainWindow>
#include <QTcpServer>
#include <QTcpSocket>
#include <QTimer>
#include "struct_data.h"

namespace Ui {
class ServerMainWindow;
}

class ServerMainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit ServerMainWindow(QWidget *parent = 0);
    ~ServerMainWindow();
private slots:
    void connectSlot(); // 处理连接的槽
    void clientSlot(); // 与客户端交互的槽
    void checkTimer(); // 定时检测心跳的槽
private:
    Ui::ServerMainWindow *ui;
    QTcpServer *m_server;
    QMap<QTcpSocket*,int> m_clients;
    QTimer *m_checkTimer;
};

#endif // SERVERMAINWINDOW_H

源文件代码:

#include "servermainwindow.h"
#include "ui_servermainwindow.h"
#include <QDebug>

#define HEART_CHECK_TIMES 6

ServerMainWindow::ServerMainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::ServerMainWindow)
{
    ui->setupUi(this);
    m_server = new QTcpServer(this);

    if(m_server->listen(QHostAddress::Any,8898)){
        qDebug()<<"listen ok";
        connect(m_server,SIGNAL(newConnection()),this,SLOT(connectSlot()));
        // 心跳检测相关的定时器及关联操作
        m_checkTimer = new QTimer(this);
        connect(m_checkTimer,SIGNAL(timeout()),this,SLOT(checkTimer()));
        m_checkTimer->start(5000);

    }else{
        qDebug()<<"listen fail"<<m_server->errorString();
    }

}

ServerMainWindow::~ServerMainWindow()
{
    delete ui;
}

void ServerMainWindow::connectSlot()
{
    QTcpSocket *client = m_server->nextPendingConnection();
    client->setSocketOption(QAbstractSocket::KeepAliveOption,true);
    qDebug()<<client;
    if(!client->isValid()) return;
    m_clients[client] = HEART_CHECK_TIMES; // 用于心跳检测的map
    // 关联与客户端通信的自定义收包槽
    connect(client,SIGNAL(readyRead()),this,SLOT(clientSlot()));
    QByteArray buffer = "欢迎来到码蚁软件服务器。";
    qDebug()<<client->write(buffer);
}

void ServerMainWindow::clientSlot()
{
    QTcpSocket * client = static_cast<QTcpSocket *>(sender());
    QByteArray buffer = client->readAll();
    qDebug()<<buffer.data();
    m_clients[client] = HEART_CHECK_TIMES; // 重置心跳阈值
    int type = ((Head*)buffer.data())->type;
    if(type == HEART_CHECK_REQ){
        ui->textBrowser->append(QString("收到 %1 端口 %2 心跳包").arg(client->peerAddress().toString()).arg(client->peerPort()));
        // 回一个响应包
        HeartCheckRes res;
        client->write((char*)&res,res.head.len);
    }
}

void ServerMainWindow::checkTimer()
{
    for(auto it=m_clients.begin();it!=m_clients.end();){
        it.value()--;
        ui->textBrowser->append(QString("%1 %2 的阈值 %3").arg(it.key()->peerAddress().toString()).arg(it.key()->peerPort()).arg(it.value()));
        if(it.value()==0){
            ui->textBrowser->append(QString("发现无用连接 %1").arg(it.key()->peerAddress().toString()));
            it.key()->close();
            m_clients.erase(it++);
        }else{
            it++;
        }
    }
}

运行结果展示:

当关闭服务端之后,再重新开启服务端:

猜你喜欢

转载自blog.csdn.net/mars1199/article/details/134482555