目录
前言:
一、Qt直接启动本身的KeepAlive
二、在应用层自己实现一个心跳检测
三、自定义心跳代码实现:
完整客户端服务端工程下载:
共用的结构体相关头文件:
客户端部分核心代码:
服务端部分核心代码:
运行结果展示:
前两篇关于qt tcp 相关的,可以通过以下传送门查看:
Qt TCP相关的一些整理:客户端常见操作 socket 通信 network-CSDN博客
Qt TCP相关的一些整理:服务端常见操作 socket 通信 network-CSDN博客
TCP本身是有一个保活状态的 keep-alive机制,默认是关闭的,需要单独启动就可以;默认保活时间是2小时,不过这个机制是在协议层,也就是传输层生效的,如果应用层出问题了,就不能及时发现问题;如果想要实现断线重连的操作,这个就不好实现了。
另一种方式,可以在应用层自定义模拟这个心跳检测机制,使用线程或者定时器来定时发心跳包即可实现保活功能,并且能做到断线重连的操作。
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
#include
#include
#include
#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
#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"<errorString();
}
}
TcpMainWindow::~TcpMainWindow()
{
delete ui;
}
void TcpMainWindow::myRead()
{
QByteArray buffer = m_client->readAll();
qDebug()<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
#include
#include
#include
#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 m_clients;
QTimer *m_checkTimer;
};
#endif // SERVERMAINWINDOW_H
源文件代码:
#include "servermainwindow.h"
#include "ui_servermainwindow.h"
#include
#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"<errorString();
}
}
ServerMainWindow::~ServerMainWindow()
{
delete ui;
}
void ServerMainWindow::connectSlot()
{
QTcpSocket *client = m_server->nextPendingConnection();
client->setSocketOption(QAbstractSocket::KeepAliveOption,true);
qDebug()<isValid()) return;
m_clients[client] = HEART_CHECK_TIMES; // 用于心跳检测的map
// 关联与客户端通信的自定义收包槽
connect(client,SIGNAL(readyRead()),this,SLOT(clientSlot()));
QByteArray buffer = "欢迎来到码蚁软件服务器。";
qDebug()<write(buffer);
}
void ServerMainWindow::clientSlot()
{
QTcpSocket * client = static_cast(sender());
QByteArray buffer = client->readAll();
qDebug()<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++;
}
}
}
当关闭服务端之后,再重新开启服务端: