QTcpSocket::waitForBytesWritten()的问题

开发一个包含较重的网络传输(发送)负载的程序时,在一个QThread辅助线程中使用QTcpSocket::waitForBytesWritten()等待发送操作完成。发现时常诱发QAbstractSocket::SocketTimeoutError,甚至出现难以调试的崩溃问题。

一种处理措施:将QTcpSocket::waitForBytesWritten()调用替换为QTcpSocket::flush()调用。或仅执行QTcpSocket::write()写操作,由Qt事件循环处理所有工作。使用此方法时,可能需要跨线程共享QTcpSocket对象,此时对QTcpSocket对象的定义和引用应全部通过指针在堆区完成,并进行加锁保护(有助于在一定程度上避免高速调用时的栈错误)。

另一种处理措施:使用一个继承自QObject工作者子对象将对QTcpSocket的初始化和引用全部置于QThread辅助线程中进行。使用一个同时包含了工作者子对象和QThread辅助线程的、继承自QObject的控制类进行管理。子类被控制类通过moveToThread()函数整体移入QThread线程内并做好信号与槽之间的连接,启动线程后,即可使用子线程自己的事件循环进行调度和管理。

#include <QVector>
#include <QQueue>
#include <QString>
#include <QMap>
#include <QTcpSocket>
#include <QThread>
#include <QMutex>
#include <QMutexLocker>
#include <QReadWriteLock>
#include <QTimer>
#include <QApplication>
#include <QHostAddress>
 
/* HEADER FILE */
/* TCP Networking Data Sending Thread Worker Object */
//This object is moved to a child thread to have its own event loop
class TCPClientDataSender : public QTcpSocket{
    Q_OBJECT
 
public:
    explicit TCPClientDataSender();
    ~TCPClientDataSender();
 
public slots:
    /* Connection Management Command Handlers */
    void ConnectToServerEventHandler(const QString sServerIP, quint16 iPort, bool IsAutoReconnectEnabled, unsigned int iAutoReconnectDelay, bool WairForOperationToComplete);
    void DisconnectFromServerEventHandler(bool WairForOperationToComplete);
    void SetAutoReconnectOptionsEventHandler(bool IsAutoReconnectEnabled, unsigned int iAutoReconnectDelay);
    void SendDataToServerEventHandler();
 
    /* TCP Socket Event Handler Slots */
    void TCPClientDataSender_Connected();
    void TCPClientDataSender_Disconnected();
    void TCPClientDataSender_Error(QAbstractSocket::SocketError errErrorInfo);
    void TCPClientDataSender_ReadyRead();
 
    /* Functional Slots */
    void TryReconnect();
 
signals:
    void SocketResponseReceivedFromServerEvent(QString sResponse);
 
private:
    QString _sServerIP; //INTERNAL: Remote IP Address
    quint16 _iPort; //INTERNAL: Remote port
    bool _IsAutoReconnectEnabled; //INTERNAL: Is auto reconnect function on
    unsigned int _iAutoReconnectDelay; //INTERNAL: Auto reconnect retry interval
    bool _IsDataSending; //INTERNAL: Marks if we are sending data, avoid recursive calling of SendDataToServerEventHandler() and segmentation faults
};
 
/* TCP Networking Client Wrapper */
class TCPClient : public QObject{
    Q_OBJECT
 
public:
    TCPClient(); //Default constructor, loads options from ini file or default values
    TCPClient(const QString sServerIP, quint16 iPort, bool IsAutoReconnectEnabled, unsigned int iAutoReconnectDelay); //Constructor with options. Will update options saved in ini file
    ~TCPClient();
 
    /* Options Management */
    void LoadSettings(); //Load settings from persistent storage
    void SaveSettings() const; //Save settings to persistent storage
 
    /* TCP Socket Object Management */
    bool IsConnected() const; //Get if we have connected to a remote server
 
    /* Connection Management */
    void SetServerParameters(const QString sServerIP, quint16 iPort); //Host information (IP & Port)
    const QString & GetServerIP() const;
    quint16 GetServerPort() const;
    void ConnectToServer(bool WairForOperationToComplete = false); //Connect to remote server with saved values
    void ConnectToServer(const QString sServerIP, quint16 iPort, bool IsAutoReconnectEnabled=false, unsigned int iAutoReconnectDelay=0, bool WairForOperationToComplete = false); //Connect to remote server with given address and port. Will update options saved in ini file
    void DisconnectFromServer(bool WairForOperationToComplete = false); //Disconnect
    void SendDataToServer();
 
    /* Options */
    void SetAutoReconnectMode(bool IsAutoReconnectEnabled); //Set & Get auto reconnect function (handles error events)
    bool GetIsAutoReconnectEnabled() const;
    void SetAutoReconnectDelay(unsigned int iAutoReconnectDelay); //Set & Get auto reconnect retry interval
    unsigned int GetAutoReconnectDelay() const;
 
    /* Validators */
    bool IsValidIPAddress(const QString sIPAddress) const; //Check if the given address is valid
    bool IsValidTCPPort(quint16 iPort, bool UseRegisteredPortsOnly=true) const; //Check if the given port ID is valid (typically in the range of [1,65535], or [1024,32767] if RegisteredPortsOnly is true)
 
    /* Threads & Worker Objects */
    QThread * trdTCPDataSender; //Thread which is used to host and control worker thread
    TCPClientDataSender * tcpDataSender; //Worker object
 
public slots:
    /* Worker Object Event Handler */
    void SocketResponseReceivedFromServerEventHandler(QString sResponse);
 
signals:
    /* Signals to Communicate with Worker Object */
    void ConnectToServerEvent(const QString sServerIP, quint16 iPort, bool IsAutoReconnectEnabled, unsigned int iAutoReconnectDelay, bool WairForOperationToComplete);
    void DisconnectFromServerEvent(bool WairForOperationToComplete);
    void SetAutoReconnectOptionsEvent(bool IsAutoReconnectEnabled, unsigned int iAutoReconnectDelay);
    void SendDataToServerEvent();
 
    /* Signals to Communicate with Upper Layer */
    void ResponseReceivedFromServerEvent(QString sResponse);
 
private:
    /* Connection Management */
    //volatile bool _IsConnected; //INTERNAL: Get if we have connected to remote server. Not used, use tcpDataSender->state() instead.
 
    /* Options Var */
    QString _sServerIP; //INTERNAL: Remote IP Address
    quint16 _iPort; //INTERNAL: Remote port
    bool _IsAutoReconnectEnabled; //INTERNAL: Is auto reconnect function on
    unsigned int _iAutoReconnectDelay; //INTERNAL: Auto reconnect retry interval
};
 
/* TCP Client */
extern TCPClient * tcpDataClient;
 
/* IMPLEMENTATION */
/* TCP Client */
TCPClient * tcpDataClient;
 
/* TCP Networking Data Sending Thread Worker Object */
TCPClientDataSender::TCPClientDataSender(){
    //Initialize internal variables
    _IsDataSending=false;
 
    //Create TCP socket object and connect events
    connect(this, SIGNAL(connected()), this, SLOT(TCPClientDataSender_Connected()), Qt::QueuedConnection);
    connect(this, SIGNAL(disconnected()), this, SLOT(TCPClientDataSender_Disconnected()), Qt::QueuedConnection);
    qRegisterMetaType<QAbstractSocket::SocketError>("QAbstractSocket::SocketError"); //Register QAbstractSocket::SocketError type for QueuedConnection
    connect(this, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(TCPClientDataSender_Error(QAbstractSocket::SocketError)), Qt::QueuedConnection);
    connect(this, SIGNAL(readyRead()), this, SLOT(TCPClientDataSender_ReadyRead()), Qt::QueuedConnection);
}
 
TCPClientDataSender::~TCPClientDataSender(){
}
 
/* Connection Management Command Handlers */
void TCPClientDataSender::ConnectToServerEventHandler(const QString sServerIP, quint16 iPort, bool IsAutoReconnectEnabled, unsigned int iAutoReconnectDelay, bool WairForOperationToComplete){
    _sServerIP=sServerIP;
    _iPort=iPort;
    _IsAutoReconnectEnabled=IsAutoReconnectEnabled;
    _iAutoReconnectDelay=iAutoReconnectDelay;
 
    connectToHost(sServerIP, iPort);
    if (WairForOperationToComplete){
        waitForConnected();
    }
 
    return;
}
 
void TCPClientDataSender::DisconnectFromServerEventHandler(bool WairForOperationToComplete){
    disconnectFromHost();
    if (WairForOperationToComplete){
        waitForDisconnected();
    }
    return;
}
 
void TCPClientDataSender::SetAutoReconnectOptionsEventHandler(bool IsAutoReconnectEnabled, unsigned int iAutoReconnectDelay){
    _IsAutoReconnectEnabled=IsAutoReconnectEnabled;
    _iAutoReconnectDelay=iAutoReconnectDelay;
    return;
}
 
void TCPClientDataSender::SendDataToServerEventHandler(){
    //Check if SendDataToServerEventHandler() is running, avoid recursive calling of SendDataToServerEventHandler() and segmentation faults
    if (_IsDataSending){
        //If this is a recursive calling, simply returns
        return;
    }
    else{
        //Marks SendDataToServerEventHandler() is running
        _IsDataSending=true;
    }
 
    //Send data to remote
    //It's suggested that you maintain a data queue to cache data panding sending
 
    _IsDataSending=false;
    return;
}
 
/* TCP Socket Event Handler Slots */
void TCPClientDataSender::TCPClientDataSender_Connected(){
    qDebug()<<"TCPClient: Connected to"<<_sServerIP<<":"<<_iPort;
    return;
}
 
void TCPClientDataSender::TCPClientDataSender_Disconnected(){
    qDebug()<<"TCPClient: Disconnected from"<<_sServerIP<<":"<<_iPort;
    return;
}
 
void TCPClientDataSender::TCPClientDataSender_Error(QAbstractSocket::SocketError errErrorInfo){
    qDebug()<<"TCPClient: Error:"<<errErrorInfo;
    disconnectFromHost();
    for (int i = 0; i <= INT_MAX; ++i){ //Manually implies WaitForDisonnected;
        if (state()==QTcpSocket::UnconnectedState){ //Check if we have disconnected
            break;
        }
        QApplication::processEvents(); //Call QApplication::processEvents() to process event loop (Avoid jamming main thread)
    }
    if (_IsAutoReconnectEnabled){ //Check if we need to reconnect
        qDebug()<<"TCPClient: Will retry connect after"<<_iAutoReconnectDelay<<"ms";
        QTimer::singleShot(_iAutoReconnectDelay, this, SLOT(TryReconnect()));
    }
    return;
}
 
void TCPClientDataSender::TCPClientDataSender_ReadyRead(){
    while (bytesAvailable()){
        //Read a command line and emit a signal
        emit SocketResponseReceivedFromServerEvent(readLine());
 
        //Process events
        QApplication::processEvents();
    }
    return;
}
 
/* Functional Slots */
void TCPClientDataSender::TryReconnect(){
    qDebug()<<"TCPClient: Retrying to connect to"<<_sServerIP<<":"<<_iPort;
    connectToHost(_sServerIP, _iPort);
    for (int i = 0; i <= INT_MAX; ++i){ //Manually implies WaitForConnected;
        if (state()==QTcpSocket::ConnectedState){ //Check if we have connected
            qDebug()<<"TCPClient: Reconnected to"<<_sServerIP<<":"<<_iPort;
            break;
        }
        QApplication::processEvents(); //Call QApplication::processEvents() to process event loop (Avoid jamming main thread)
    }
    if (state()!=QTcpSocket::ConnectedState){
        qDebug()<<"TCPClient: Will retry connect after"<<_iAutoReconnectDelay<<"ms";
        disconnectFromHost();
        for (int i = 0; i <= INT_MAX; ++i){ //Manually implies WaitForDisonnected;
            if (state()==QTcpSocket::UnconnectedState){ //Check if we have disconnected
                break;
            }
            QApplication::processEvents(); //Call QApplication::processEvents() to process event loop (Avoid jamming main thread)
        }
        QTimer::singleShot(_iAutoReconnectDelay, this, SLOT(TryReconnect()));
    }
    return;
}
 
/* TCP Networking Client Wrapper */
TCPClient::TCPClient(){
    //Load settings
    TCPClient::LoadSettings();
 
    //Create worker object
    tcpDataSender = new TCPClientDataSender;
 
    //Creat thread object
    trdTCPDataSender = new QThread;
    tcpDataSender->moveToThread(trdTCPDataSender);
 
    //Connect events and handlers
    connect(this, SIGNAL(ConnectToServerEvent(QString,quint16,bool,unsigned int,bool)), tcpDataSender, SLOT(ConnectToServerEventHandler(QString,quint16,bool,unsigned int,bool)), Qt::QueuedConnection);
    connect(this, SIGNAL(DisconnectFromServerEvent(bool)), tcpDataSender, SLOT(DisconnectFromServerEventHandler(bool)), Qt::QueuedConnection);
    connect(this, SIGNAL(SetAutoReconnectOptionsEvent(bool,uint)), tcpDataSender, SLOT(SetAutoReconnectOptionsEventHandler(bool,uint)), Qt::QueuedConnection);
    connect(this, SIGNAL(SendDataToServerEvent()), tcpDataSender, SLOT(SendDataToServerEventHandler()), Qt::QueuedConnection);
    connect(tcpDataSender, SIGNAL(SocketResponseReceivedFromServerEvent(QString)), this, SLOT(SocketResponseReceivedFromServerEventHandler(QString)), Qt::QueuedConnection);
 
    //Start child thread's own event loop
    trdTCPDataSender->start();
}
 
TCPClient::TCPClient(const QString sServerIP, quint16 iPort, bool IsAutoReconnectEnabled, unsigned int iAutoReconnectDelay){
    //Save settings
    _sServerIP=sServerIP;
    _iPort=iPort;
    _IsAutoReconnectEnabled=IsAutoReconnectEnabled;
    _iAutoReconnectDelay=iAutoReconnectDelay;
    TCPClient::SaveSettings();
 
    //Create worker object
    tcpDataSender = new TCPClientDataSender;
 
    //Creat thread object and move worker object (and all of it's child objects) to this thread
    trdTCPDataSender = new QThread;
    tcpDataSender->moveToThread(trdTCPDataSender);
 
    //Connect events and handlers
    connect(this,SIGNAL(ConnectToServerEvent(QString,quint16,bool,unsigned int,bool)),tcpDataSender,SLOT(ConnectToServerEventHandler(QString,quint16,bool,unsigned int,bool)),Qt::QueuedConnection);
    connect(this,SIGNAL(DisconnectFromServerEvent(bool)),tcpDataSender,SLOT(DisconnectFromServerEventHandler(bool)),Qt::QueuedConnection);
    connect(this,SIGNAL(SetAutoReconnectOptionsEvent(bool,uint)),tcpDataSender,SLOT(SetAutoReconnectOptionsEventHandler(bool,uint)),Qt::QueuedConnection);
    connect(this,SIGNAL(SendDataToServerEvent()),tcpDataSender,SLOT(SendDataToServerEventHandler()),Qt::QueuedConnection);
    connect(tcpDataSender, SIGNAL(SocketResponseReceivedFromServerEvent(QString)), this, SLOT(SocketResponseReceivedFromServerEventHandler(QString)), Qt::QueuedConnection);
 
    //Start child thread's own event loop
    trdTCPDataSender->start();
}
 
TCPClient::~TCPClient(){
    //Save settings
    if (TCPClient::IsConnected()){
        TCPClient::DisconnectFromServer();
    }
    TCPClient::SaveSettings();
 
    //Quit child thread
    trdTCPDataSender->quit();
    if (!trdTCPDataSender->wait(1000)){
        trdTCPDataSender->terminate();
    }
 
    //Delete worker object
    tcpDataSender->deleteLater();
    tcpDataSender=NULL;
 
    //Delete child thread
    trdTCPDataSender->deleteLater();
    trdTCPDataSender=NULL;
}
 
/* Options Management */
void TCPClient::LoadSettings(){
    //Load settings
    return;
}
 
void TCPClient::SaveSettings() const{
    //Save settings
    return;
}
 
/* TCP Socket Object Management */
bool TCPClient::IsConnected() const{
    return (tcpDataSender->state()==QTcpSocket::ConnectedState);
}
 
/* Connection Management */
void TCPClient::SetServerParameters(const QString sServerIP, quint16 iPort){
    if (TCPClient::IsValidIPAddress(sServerIP) && TCPClient::IsValidTCPPort(iPort)){
        //Save settings
        _sServerIP=sServerIP;
        _iPort=iPort;
        TCPClient::SaveSettings();
    }
    return;
}
 
const QString & TCPClient::GetServerIP() const{
    return _sServerIP;
}
 
quint16 TCPClient::GetServerPort() const{
    return _iPort;
}
 
void TCPClient::ConnectToServer(bool WairForOperationToComplete){
    emit ConnectToServerEvent(_sServerIP,_iPort,_IsAutoReconnectEnabled,_iAutoReconnectDelay,WairForOperationToComplete);
    return;
}
 
void TCPClient::ConnectToServer(const QString sServerIP, quint16 iPort, bool IsAutoReconnectEnabled, unsigned int iAutoReconnectDelay, bool WairForOperationToComplete){
    //Save settings
    _sServerIP=sServerIP;
    _iPort=iPort;
    _IsAutoReconnectEnabled=IsAutoReconnectEnabled;
    _iAutoReconnectDelay=iAutoReconnectDelay;
    TCPClient::SaveSettings();
 
    //Connect
    emit ConnectToServerEvent(_sServerIP,_iPort,_IsAutoReconnectEnabled,_iAutoReconnectDelay,WairForOperationToComplete);
    return;
}
 
void TCPClient::DisconnectFromServer(bool WairForOperationToComplete){
    emit DisconnectFromServerEvent(WairForOperationToComplete);
    return;
}
 
void TCPClient::SendDataToServer(){
    emit SendDataToServerEvent();
    return;
}
 
/* Options */
void TCPClient::SetAutoReconnectMode(bool IsAutoReconnectEnabled){
    _IsAutoReconnectEnabled=IsAutoReconnectEnabled;
    TCPClient::SaveSettings();
    emit SetAutoReconnectOptionsEvent(_IsAutoReconnectEnabled,_iAutoReconnectDelay);
    return;
}
 
bool TCPClient::GetIsAutoReconnectEnabled() const{
    return _IsAutoReconnectEnabled;
}
 
void TCPClient::SetAutoReconnectDelay(unsigned int iAutoReconnectDelay){
    _iAutoReconnectDelay=iAutoReconnectDelay;
    TCPClient::SaveSettings();
    emit SetAutoReconnectOptionsEvent(_IsAutoReconnectEnabled,_iAutoReconnectDelay);
    return;
}
 
unsigned int TCPClient::GetAutoReconnectDelay() const{
    return _iAutoReconnectDelay;
}
 
/* Worker Object Event Handler */
void TCPClient::SocketResponseReceivedFromServerEventHandler(QString sResponse){
    qDebug()<<"TCPClient: Response"<<sResponse<<"received from the remote";
    emit ResponseReceivedFromServerEvent(sResponse);
    return;
}
 
/* Validators */
bool TCPClient::IsValidIPAddress(const QString sIPAddress) const{
    QHostAddress hstTestAddr;
    return hstTestAddr.setAddress(sIPAddress);
}
 
bool TCPClient::IsValidTCPPort(quint16 iPort, bool UseRegisteredPortsOnly) const{
    quint16 iPortIDMin = 1, iPortIDMax = 65535;
    if (UseRegisteredPortsOnly){
        iPortIDMin=1024;
        iPortIDMax=32767;
    }
    return ((iPort >= iPortIDMin) && (iPort <= iPortIDMax));
}
it
除非特别注明,本页内容采用以下授权方式: Creative Commons Attribution-ShareAlike 3.0 License