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

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