Qt QTcpSocket断网重连(一)

在网络传输中,会出现各种各样的情况,在长链接的使用中断网重连机制就显得尤为重要了。

一、需要断网重连的情况

  1. 接收不到数据的情况(网络闪断)
  2. 接收到数据为空

二、断网检查方式

  1. 通过接收到数据是否 <= 0 判断,如果 <= 0 说明已经断开连接了
  2. 判断一段时间内是否有接收到数据(长链接一般有心跳包进行链接诊断)

三、代码实现

.h部分

#ifndef TCPTHREAD_H
#define TCPTHREAD_H

#include <QObject>
#include <QTcpSocket>
#include <QThread>

class TcpThread : public QThread
{
    Q_OBJECT

public:
    TcpThread();
    ~TcpThread();

    void startThread(const QString& ip, int port);
    void stopThreaad();

protected:
    virtual void run();

protected slots:
    void onConnect();
    void onDisConnect();
    void onReadMsg();
    void onSendTcp();

public:
    int     m_iSendData;
    int     m_iRecv_TimeOut;

private:
    QTcpSocket* m_TcpSocket;

    bool        m_isThreaStopped;
    bool 		m_isOkConect;
    QString 	m_QStrSocketIp;
    int 		m_nSockPort;
    QByteArray 	m_datagram;
};

#endif // TCPTHREAD_H

.cpp部分

#include "tcpthread.h"
#include "qtimer.h"

TcpThread::TcpThread()
    : m_iSendData(0)
{
    qDebug()<<"11";
    m_TcpSocket = nullptr ;
    m_isThreaStopped = false;
    m_isOkConect = false;
}

TcpThread::~TcpThread()
{
    qDebug()<<"exit_1";
    m_isThreaStopped = true;
    qDebug()<<"exit_2";
    quit();
    qDebug()<<"exit_3";
    wait();
    qDebug()<<"exit_4";

}

//线程的run函数
void TcpThread::run()
{
    bool b_recv_flag = false;

    if (!m_TcpSocket)
    {
        m_TcpSocket = new QTcpSocket();
        connect(m_TcpSocket, SIGNAL(readyRead()), this, SLOT(onReadMsg()),Qt::DirectConnection);//让接受函数在run子线程中执行(发送者执行)
        connect(m_TcpSocket, SIGNAL(connected()), this, SLOT(onConnect()));
        connect(m_TcpSocket, SIGNAL(disconnected()), this, SLOT(onDisConnect()));
    }

    while (!m_isThreaStopped)
    {
        //检测客户端 socket指针是否为空
        if(b_recv_flag)
            msleep(100);
        if (!m_isOkConect)
        {
            // 终止当前连接并重置套接字(立即关闭套接字,丢弃写缓冲区中的任何挂起数据)
            m_TcpSocket->abort();
            m_TcpSocket->connectToHost(m_QStrSocketIp, m_nSockPort);
            //等待连接。。。延时三秒,三秒内连不上返回false
            m_isOkConect = m_TcpSocket->waitForConnected(3000);
            m_iRecv_TimeOut = -1;

        }
        if (!m_isOkConect)
        {
            msleep(1);
            continue;
        }
        else
        {
            qDebug()<<"tcp_flag:"<<m_iRecv_TimeOut;  
            onSendTcp();
        }
        b_recv_flag = m_TcpSocket->waitForReadyRead(100);
        if  (b_recv_flag)   
        {   
        	m_iRecv_TimeOut = 0;
        }
        else                
        {   
        	m_iRecv_TimeOut++; 
        	if(m_iRecv_TimeOut>150) m_iRecv_TimeOut=150; 
        }
        
        if(m_iRecv_TimeOut >= 150)
        {
            m_iRecv_TimeOut = -1;
            m_TcpSocket->disconnectFromHost();
        }
    }
    
    qDebug()<<"exit_5";
    m_TcpSocket->disconnectFromHost();
    qDebug()<<"exit_6";
}

void TcpThread::onDisConnect()
{
    //socket一旦断开则自动进入这个槽函数
    //通过把 m_isOkConect 设为false,在socket线程的run函数中将会重新连接主机
    qDebug()<<"socket is disconnect!"<<endl;
    m_isOkConect = false;
    m_iRecv_TimeOut = -1;
    qDebug()<<"disconnect_tcp";
}

void TcpThread::startThread(const QString& ip, int port )
{
    m_QStrSocketIp = ip;
    m_nSockPort = port;
    m_isThreaStopped = false;
    start();
}

void TcpThread::stopThreaad()
{
    qDebug()<<"stop :3";
    m_isThreaStopped = true;
    qDebug()<<"stop :4";
    qDebug()<<"stop :5";
}

void TcpThread::onConnect()
{
    //已连接
}

void TcpThread::onReadMsg()
{
    if(m_TcpSocket->bytesAvailable() <= 0)
    {
        //  判定连接失败
        m_TcpSocket->disconnectFromHost();
    }
    while (m_TcpSocket->bytesAvailable() > 0)
    {
        // 接收数据
        m_datagram.clear();
        m_datagram.resize(m_TcpSocket->bytesAvailable());
        m_TcpSocket->read(m_datagram.data(), m_datagram.size());
        QString str_tcp_receive = QString::fromLocal8Bit(m_datagram);

        msleep(100);
    }
}

void TcpThread::onSendTcp()
{

    if((!m_TcpSocket)||m_TcpSocket->state()!=QAbstractSocket::ConnectedState)
        return;
    QString str_info = "";
    m_iSendData = m_TcpSocket->write(str_info.toStdString().c_str(), strlen(str_info.toStdString().c_str()));
    m_TcpSocket->flush();

    if (m_iSendData < 0)
    {
        m_TcpSocket->disconnectFromHost();
        return;
    }
    msleep(1000);
}

代码介绍,如果是因为没有收到数据,大约15秒后会自动重连,如果是因为接受到数据 <= 0,那么会立即重连

这一篇有两个潜在的bug:
(1)Linux下偶尔会有连不上情况,且会导致内存无限加大
(2)服务器端物理断网后,当一旦连上服务器端会出现多次连接情况,但是客户端实际上只发送了一次有效连接(不是特别明白出现原因)

在这一篇中解决了以上两个问题(目前没再见过):https://blog.csdn.net/bloke_come/article/details/105650204

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章