从服务器端关闭连接时的 QAbstractSocket 奇怪行为

Posted

技术标签:

【中文标题】从服务器端关闭连接时的 QAbstractSocket 奇怪行为【英文标题】:QAbstractSocket strange behavior when connection is closed from server side 【发布时间】:2017-08-23 04:48:40 【问题描述】:

我正在实现一个从 QAbstractSocket 继承的简单 TCP 套接字类。我正在测试我的代码,然后我注意到 QAbstractSocket 出现了一种奇怪的行为。当连接从服务器端结束时,QAbstractSocket 永远不会发出 disconnected() 信号,但它会不断发出 readyRead() 信号。

我正在将 SimpleTCPSocket 类信号连接到我的 Widget 类的构造函数中的 Widget 插槽:

connect(simpleSocket, SIGNAL(readyRead()), this, SLOT(readDataFromSimpleSocket()));
connect(simpleSocket, SIGNAL(disconnected()), this, SLOT(onSimpleSocketDisconnect()));

这是我的插槽的实现:

void Widget::readDataFromSimpleSocket()

    QString aux;
    size_t sizeOfDataAvilable;
    sizeOfDataAvilable = simpleSocket->bytesAvailable();
    aux = "Message received from readDataFromSimpleSocket slot: bytes available ";
    aux += QString::number(sizeOfDataAvilable);
    qDebug() << aux;
    dataFromServer = (char*) malloc (sizeof(char) * sizeOfDataAvilable);
    simpleSocket->read(dataFromServer, sizeOfDataAvilable);
    qDebug() << "All data has been read.";


void Widget::onSimpleSocketDisconnect()

    qDebug() << "Disconnected!!";

当服务器应用程序关闭时,我收到了 readReady() 信号,但没有可供读取的字节。这是我的输出。

"Message received from readDataFromSimpleSocket slot: bytes available 0"
All data has been read.

我得到了像“永远”这样的上述输出。无论在服务器端关闭连接后经过了多少时间,我的 SimpleTCPSocket 类都不会发出 disconnected() 信号。 这是我的套接字类的头文件:

#include "simpletcpsocket_global.h"
#include <QTcpSocket>
#include <QAuthenticator>
#include <QNetworkProxy>

class SIMPLETCPSOCKETSHARED_EXPORT SimpleTCPSocket : public QAbstractSocket

    Q_OBJECT
public:
    SimpleTCPSocket(QObject *parent = Q_NULLPTR);
    void setHostInfo(const QString &hostName, quint16 port, QIODevice::OpenMode openMode = QIODevice::ReadWrite, QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::AnyIPProtocol);
    void setProxyInfo(QNetworkProxy::ProxyType proxyType, QString proxyHostName, quint16 proxyPort, QString username, QString password);
    void connectSimpleSocketToHost();
private:
    QNetworkProxy simpleSocketProxy;
    QString proxyUser, proxyPassword;
    QString hostName;
    int hostPort;
    QAbstractSocket::NetworkLayerProtocol protocol;
    QIODevice::OpenMode openMode;
    bool isHostSet, isConnectionOK, isProxySet;
private slots:
    void onHostFound(); // # slot 1
    void onSocketConnected(); // # slot 2
    void onSocketError(QAbstractSocket::SocketError error); // # slot 3
    void onUpdateSocketState(QAbstractSocket::SocketState state); // # slot 4
    void onProxyAuthRequired(const QNetworkProxy &simpleSocketProxy, QAuthenticator *authenticator); // # slot 5
    void onSocketAboutToClose(); // # slot 6
    void onSocketClose(); // # slot 7
signals:
    void remoteHostName(QString remoteHostName); // emmited whenever the host is found
    void attemptToConnectFinished(QString resultMessage); // emitted when the method connect is executed, attempt to connect to host
    void errorMessage(QString errorMessage); // emitted whenever an error occurs
    void socketStateMessage(QString socketState); // emitted whenever the socket state is changed
    void connectionInfo(QString connectionInformation); // emmited whenever the
;

这是我的类的实现:

#include "simpletcpsocket.h"
#include <QDebug>
#include <QTime>

SimpleTCPSocket::SimpleTCPSocket(QObject *parent)
    :QAbstractSocket(QAbstractSocket::TcpSocket, parent)

    connect(this, SIGNAL(hostFound()), this, SLOT(onHostFound()));
    connect(this, SIGNAL(connected()), this, SLOT(onSocketConnected()));
    connect(this, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onSocketError(QAbstractSocket::SocketError)));
    connect(this, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onUpdateSocketState(QAbstractSocket::SocketState)));
    connect(this, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)), this, SLOT(onProxyAuthRequired(const QNetworkProxy &, QAuthenticator *)));
    connect(this, SIGNAL(aboutToClose()), this, SLOT(onSocketAboutToClose()));
    connect(this, SIGNAL(disconnected()), this, SLOT(onSocketClose()));
    isHostSet = false;
    isConnectionOK = false;
    isProxySet = false;


void SimpleTCPSocket::connectSimpleSocketToHost()

    if (isProxySet)
    
        this->setProxy(simpleSocketProxy);
    
    else
    
        this->setProxy(QNetworkProxy::NoProxy);
    
    if (isHostSet)
    
        QTime t;
        t.start();
        static_cast<QAbstractSocket*>(this)->connectToHost(hostName, hostPort, openMode, protocol);
        emit attemptToConnectFinished("Attempt to connect to host ended. Time elapsed: " + QString::number(t.elapsed()) + " ms");
    
    else
    
        emit errorMessage("Host has not been set. You must set host name, port, open mode, and IP protocol first.");
    


void SimpleTCPSocket::onHostFound()

    emit remoteHostName("Remote host name: " + static_cast<QAbstractSocket*>(this)->peerName());


void SimpleTCPSocket::onSocketConnected()

    isConnectionOK = true;
    QString connectionInfoString;
    connectionInfoString = "Peer address: " + this->peerAddress().toString();
    connectionInfoString += "; Peer port: " + QString::number(this->peerPort());
    connectionInfoString += "; Local address: " + this->localAddress().toString();
    connectionInfoString += "; Local port: " + QString::number(this->localPort());
    connectionInfoString += ";";
    emit connectionInfo(connectionInfoString);


void SimpleTCPSocket::onSocketError(QAbstractSocket::SocketError error)

    switch(error)
    
        case QAbstractSocket::ConnectionRefusedError:
        
            emit errorMessage("The connection was refused by the peer (or timed out).");
            break;
        
        case QAbstractSocket::RemoteHostClosedError:
        
            emit errorMessage("The remote host closed the connection. Note that the client socket (i.e., this socket) will be closed after the remote close notification has been sent.");
            break;
        
        case QAbstractSocket::HostNotFoundError:
        
            emit errorMessage("The host address was not found.");
            break;
        
        case QAbstractSocket::SocketAccessError:
        
            emit errorMessage("The socket operation failed because the application lacked the required privileges.");
            break;
        
        case QAbstractSocket::SocketResourceError:
        
            emit errorMessage("The local system ran out of resources (e.g., too many sockets).");
            break;
        
        case QAbstractSocket::SocketTimeoutError:
        
            emit errorMessage("The socket operation timed out.");
            break;
        
        case QAbstractSocket::DatagramTooLargeError:
        
            emit errorMessage("The datagram was larger than the operating system's limit (which can be as low as 8192 bytes).");
            break;
        
        case QAbstractSocket::NetworkError:
        
            emit errorMessage("An error occurred with the network (e.g., the network cable was accidentally plugged out).");
            break;
        
        case QAbstractSocket::AddressInUseError:
        
            emit errorMessage("The address specified to QAbstractSocket::bind() is already in use and was set to be exclusive.");
            break;
        
        case QAbstractSocket::SocketAddressNotAvailableError:
        
            emit errorMessage("The address specified to QAbstractSocket::bind() does not belong to the host.");
            break;
        
        case QAbstractSocket::UnsupportedSocketOperationError:
        
            emit errorMessage("The requested socket operation is not supported by the local operating system (e.g., lack of IPv6 support).");
            break;
        
        case QAbstractSocket::ProxyAuthenticationRequiredError:
        
            emit errorMessage("The socket is using a proxy, and the proxy requires authentication.");
            break;
        
        case QAbstractSocket::SslHandshakeFailedError:
        
            emit errorMessage("The SSL/TLS handshake failed, so the connection was closed (only used in QSslSocket)");
            break;
        
        case QAbstractSocket::UnfinishedSocketOperationError:
        
            emit errorMessage("Used by QAbstractSocketEngine only, The last operation attempted has not finished yet (still in progress in the background).");
            break;
        
        case QAbstractSocket::ProxyConnectionRefusedError:
        
            emit errorMessage("Could not contact the proxy server because the connection to that server was denied");
            break;
        
        case QAbstractSocket::ProxyConnectionClosedError:
        
            emit errorMessage("The connection to the proxy server was closed unexpectedly (before the connection to the final peer was established)");
            break;
        
        case QAbstractSocket::ProxyConnectionTimeoutError:
        
            emit errorMessage("The connection to the proxy server timed out or the proxy server stopped responding in the authentication phase.");
            break;
        
        case QAbstractSocket::ProxyNotFoundError:
        
            emit errorMessage("The proxy address set with setProxy() (or the application proxy) was not found.");
            break;
        
        case QAbstractSocket::ProxyProtocolError:
        
            emit errorMessage("The connection negotiation with the proxy server failed, because the response from the proxy server could not be understood.");
            break;
        
        case QAbstractSocket::OperationError:
        
            emit errorMessage("An operation was attempted while the socket was in a state that did not permit it.");
            break;
        
        case QAbstractSocket::SslInternalError:
        
            emit errorMessage("The SSL library being used reported an internal error. This is probably the result of a bad installation or misconfiguration of the library.");
            break;
        
        case QAbstractSocket::SslInvalidUserDataError:
        
            emit errorMessage("Invalid data (certificate, key, cypher, etc.) was provided and its use resulted in an error in the SSL library.");
            break;
        
        case QAbstractSocket::TemporaryError:
        
            emit errorMessage("A temporary error occurred (e.g., operation would block and socket is non-blocking).");
            break;
        
        case QAbstractSocket::UnknownSocketError:
        
            emit errorMessage("An unknown socket error occurred.");
            break;
        
    


void SimpleTCPSocket::onUpdateSocketState(QAbstractSocket::SocketState state)

    switch(state)
    
        case QAbstractSocket::UnconnectedState:
        
            isConnectionOK = false;
            emit socketStateMessage("The socket is not connected.");
            break;
        
        case QAbstractSocket::HostLookupState:
        
            emit socketStateMessage("The socket is performing a host name lookup.");
            break;
        
        case QAbstractSocket::ConnectingState:
        
            emit socketStateMessage("The socket has started establishing a connection.");
            break;
        
        case QAbstractSocket::ConnectedState:
        
            isConnectionOK = true;
            emit socketStateMessage("A connection is established.");
            break;
        
        case QAbstractSocket::BoundState:
        
            emit socketStateMessage("The socket is bound to an address and port.");
            break;
        
        case QAbstractSocket::ClosingState:
        
            emit socketStateMessage("The socket is about to close (data may still be waiting to be written).");
            break;
        
        case QAbstractSocket::ListeningState:
        
            emit socketStateMessage("For internal use only.");
            break;
        
        default:
        
            emit socketStateMessage("Unkown state.");
        
    


void SimpleTCPSocket::onProxyAuthRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator)

    qDebug() << "Proxy authentication required.";
    this->setProxy(proxy);
    this->proxy().setUser(proxyUser);
    this->proxy().setPassword(proxyPassword);
    authenticator->setUser(proxyUser);
    authenticator->setPassword(proxyPassword);


void SimpleTCPSocket::onSocketAboutToClose()

    qDebug() << "Connection about to close. Saving pending data to log (not implemented yet).";


void SimpleTCPSocket::onSocketClose()

    qDebug() << "Connection closed. Saving and closing log (not implemented yet).";
    isConnectionOK = false;


void SimpleTCPSocket::setHostInfo(const QString &hostName, quint16 port, QIODevice::OpenMode openMode, QAbstractSocket::NetworkLayerProtocol protocol)

    this->hostName = hostName;
    this->hostPort = port;
    this->openMode = openMode;
    this->protocol = protocol;
    isHostSet = true;


void SimpleTCPSocket::setProxyInfo(QNetworkProxy::ProxyType proxyType, QString proxyHostName, quint16 proxyPort, QString username, QString password)

    simpleSocketProxy.setType(proxyType);
    simpleSocketProxy.setHostName(proxyHostName);
    simpleSocketProxy.setPort(proxyPort);
    simpleSocketProxy.setUser(username);
    simpleSocketProxy.setPassword(password);
    isProxySet = true;

我的代码可能有什么问题?

问候

【问题讨论】:

@WindyFields 我为我的班级添加了代码。这是一个非常简单的 QAbsracSocket 实现。 如果我是你,我会用 QTcpSocket 替换你的自定义套接字(出于调试目的),看看它是否有效。您明确地说:断开信号输出断开连接。所以如果它有效,那么你搞砸了你的 SimpleSocket 类,如果它没有,那么你的连接实际上并没有“断开”或者从套接字读取有一些问题......对不起,我没有看到任何其他原因如此奇怪的行为。此外,您可能应该尝试从 QDataStream like in the example 读取数据。 @WindyFields 我用 QTcpSocket 替换了我的自定义套接字,它工作正常。可能是我的实现有问题。 @WindyFields 感谢您对如何读取数据的提示。 【参考方案1】:

我在 Qt 论坛上发布了这个问题,然后我得到了这个answer。 raven-worx 写道:“你的实现没有任何问题。不幸的是,这是相当正常的行为。”。这样,问题就“解决了”。

【讨论】:

以上是关于从服务器端关闭连接时的 QAbstractSocket 奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章

JavaFX程序关闭或丢失连接时的PHP函数[关闭]

写入响应标头后服务器关闭连接时的 HttpClient 异常

怎么在服务器端关闭websocket连接

[Java]I/O底层原理之二:Socket工作机制

面向连接的Socket服务端关闭问题

TCP连接中的TIME_WAIT状态