Skip to content

不停访问服务器会造成系统崩溃 #61

@akinggw

Description

@akinggw

用定时器不停地访问服务器,会造成系统崩溃。经检查,是多线程引起的,可能会存在还在处理时,连接被关闭的情况。
修改如下:
class JQLIBRARY_EXPORT Session: public QObject 类增加一个参数:
QFuture m_workingFuc;

void JQHttpServer::AbstractManage::handleAccepted(const QPointer< Session > &session)
{
session->m_workingFuc = QtConcurrent::run( handleThreadPool_.data(), this, session {
//emit onRedReady(session);

    if ( !this->httpAcceptedCallback_ )
    {
        qDebug() << "JQHttpServer::Manage::handleAccepted: error, httpAcceptedCallback_ is nullptr";
        return;
    }

    this->httpAcceptedCallback_( session , this->getMainObject());
} );

}

然后在Session所有删除自身前,等待线程结束:
#define JQHTTPSERVER_SESSION_REPLY_PROTECTION2( functionName, ... )
if ( ioDevice_.isNull() )
{
qDebug().noquote() << QStringLiteral( "JQHttpServer::Session::" ) + functionName + ": error1";
m_workingFuc.waitForFinished();
this->deleteLater();
return VA_ARGS;
}

void JQHttpServer::Session::replyFile(const QString &filePath, const int &httpStatusCode)
{
JQHTTPSERVER_SESSION_REPLY_PROTECTION( "replyFile" )

if ( QThread::currentThread() != this->thread() )
{
    replyHttpCode_ = httpStatusCode;

    QMetaObject::invokeMethod( this, "replyFile", Qt::QueuedConnection, Q_ARG( QString, filePath ), Q_ARG( int, httpStatusCode ) );
    return;
}

JQHTTPSERVER_SESSION_REPLY_PROTECTION2( "replyFile" )

replyIoDevice_.reset( new QFile( filePath ) );
QPointer< QFile > file = ( qobject_cast< QFile * >( replyIoDevice_.data() ) );

if ( !file->open( QIODevice::ReadOnly ) )
{
    qDebug() << "JQHttpServer::Session::replyFile: open file error:" << filePath;
    replyIoDevice_.clear();
    m_workingFuc.waitForFinished();
    this->deleteLater();
    return;
}

replyBodySize_ = file->size();

const auto &&data = replyFileFormat
                        .arg(
                            QString::number( httpStatusCode ),
                            QFileInfo( filePath ).fileName(),
                            QString::number( replyBodySize_ ) )
                        .toUtf8();

waitWrittenByteCount_ = data.size() + file->size();
ioDevice_->write( data );

}

void JQHttpServer::Session::replyFile(const QString &fileName, const QByteArray &fileData, const int &httpStatusCode)
{
JQHTTPSERVER_SESSION_REPLY_PROTECTION( "replyFile" )

if ( QThread::currentThread() != this->thread() )
{
    replyHttpCode_ = httpStatusCode;

    QMetaObject::invokeMethod( this, "replyFile", Qt::QueuedConnection, Q_ARG( QString, fileName ), Q_ARG( QByteArray, fileData ), Q_ARG( int, httpStatusCode ) );
    return;
}

JQHTTPSERVER_SESSION_REPLY_PROTECTION2( "replyFile" )

auto buffer = new QBuffer;
buffer->setData( fileData );

if ( !buffer->open( QIODevice::ReadWrite ) )
{
    qDebug() << "JQHttpServer::Session::replyFile: open buffer error";
    delete buffer;
    m_workingFuc.waitForFinished();
    this->deleteLater();
    return;
}

replyIoDevice_.reset( buffer );
replyIoDevice_->seek( 0 );

replyBodySize_ = fileData.size();

const auto &&data =
    replyFileFormat
        .arg( QString::number( httpStatusCode ), fileName, QString::number( replyBodySize_ ) )
        .toUtf8();

waitWrittenByteCount_ = data.size() + fileData.size();
ioDevice_->write( data );

}

void JQHttpServer::Session::replyImage(const QImage &image, const QString &format, const int &httpStatusCode)
{
JQHTTPSERVER_SESSION_REPLY_PROTECTION( "replyImage" )

if ( QThread::currentThread() != this->thread() )
{
    replyHttpCode_ = httpStatusCode;

    QMetaObject::invokeMethod( this, "replyImage", Qt::QueuedConnection, Q_ARG( QImage, image ), Q_ARG( QString, format ), Q_ARG( int, httpStatusCode ) );
    return;
}

JQHTTPSERVER_SESSION_REPLY_PROTECTION2( "replyImage" )

auto buffer = new QBuffer;

if ( !buffer->open( QIODevice::ReadWrite ) )
{
    qDebug() << "JQHttpServer::Session::replyImage: open buffer error";
    delete buffer;
    m_workingFuc.waitForFinished();
    this->deleteLater();
    return;
}

if ( !image.save( buffer, format.toLatin1().constData() ) )
{
    qDebug() << "JQHttpServer::Session::replyImage: save image to buffer error";
    delete buffer;
    m_workingFuc.waitForFinished();
    this->deleteLater();
    return;
}

replyIoDevice_.reset( buffer );
replyIoDevice_->seek( 0 );

replyBodySize_ = buffer->buffer().size();

const auto &&data =
    replyImageFormat
        .arg( QString::number( httpStatusCode ), format.toLower(), QString::number( replyBodySize_ ) )
        .toUtf8();

waitWrittenByteCount_ = data.size() + buffer->buffer().size();
ioDevice_->write( data );

}

void JQHttpServer::Session::replyImage(const QString &imageFilePath, const int &httpStatusCode)
{
JQHTTPSERVER_SESSION_REPLY_PROTECTION( "replyImage" )

if ( QThread::currentThread() != this->thread() )
{
    replyHttpCode_ = httpStatusCode;

    QMetaObject::invokeMethod( this, "replyImage", Qt::QueuedConnection, Q_ARG( QString, imageFilePath ), Q_ARG( int, httpStatusCode ) );
    return;
}

JQHTTPSERVER_SESSION_REPLY_PROTECTION2( "replyImage" )

auto file = new QFile( imageFilePath );

if ( !file->open( QIODevice::ReadOnly ) )
{
    qDebug() << "JQHttpServer::Session::replyImage: open buffer error";
    delete file;
    m_workingFuc.waitForFinished();
    this->deleteLater();
    return;
}

replyIoDevice_.reset( file );
replyIoDevice_->seek( 0 );

replyBodySize_ = file->size();

const auto &&data =
    replyImageFormat
        .arg( QString::number( httpStatusCode ), QFileInfo( imageFilePath ).suffix(), QString::number( replyBodySize_ ) )
        .toUtf8();

waitWrittenByteCount_ = data.size() + file->size();
ioDevice_->write( data );

}

void JQHttpServer::Session::replyBytes(const QByteArray &bytes, const QString &contentType, const int &httpStatusCode)
{
JQHTTPSERVER_SESSION_REPLY_PROTECTION( "replyBytes" )

if ( QThread::currentThread() != this->thread( ))
{
    replyHttpCode_ = httpStatusCode;

    QMetaObject::invokeMethod(this, "replyBytes", Qt::QueuedConnection, Q_ARG(QByteArray, bytes), Q_ARG(QString, contentType), Q_ARG(int, httpStatusCode));
    return;
}

JQHTTPSERVER_SESSION_REPLY_PROTECTION2( "replyBytes" )

auto buffer = new QBuffer;
buffer->setData( bytes );

if ( !buffer->open( QIODevice::ReadWrite ) )
{
    qDebug() << "JQHttpServer::Session::replyBytes: open buffer error";
    delete buffer;
    m_workingFuc.waitForFinished();
    this->deleteLater();
    return;
}

replyIoDevice_.reset( buffer );
replyIoDevice_->seek( 0 );

replyBodySize_ = buffer->buffer().size();

const auto &&data =
    replyBytesFormat
        .arg( QString::number( httpStatusCode ), contentType, QString::number( replyBodySize_ ) )
        .toUtf8();

waitWrittenByteCount_ = data.size() + buffer->buffer().size();
ioDevice_->write(data);

}

void JQHttpServer::Session::replyOptions()
{
JQHTTPSERVER_SESSION_REPLY_PROTECTION( "replyOptions" )

if ( QThread::currentThread() != this->thread() )
{
    replyHttpCode_ = 200;

    QMetaObject::invokeMethod( this, "replyOptions", Qt::QueuedConnection );
    return;
}

JQHTTPSERVER_SESSION_REPLY_PROTECTION2( "replyOptions" )

replyBodySize_ = 0;

const auto &&buffer = replyOptionsFormat.toUtf8();

waitWrittenByteCount_ = buffer.size();
ioDevice_->write( buffer );

}

void JQHttpServer::Session::inspectionBufferSetup1()
{
if ( !headerAcceptedFinished_ )
{
forever
{
static QByteArray splitFlag( "\r\n" );

        auto splitFlagIndex = receiveBuffer_.indexOf( splitFlag );

        // 没有获取到分割标记,意味着数据不全
        if ( splitFlagIndex == -1 )
        {
            // 没有获取到 method 但是缓冲区内已经有了数据,这可能是一个无效的连接
            if ( requestMethod_.isEmpty() && ( receiveBuffer_.size() > 4 ) )
            {

// qDebug() << "JQHttpServer::Session::inspectionBuffer: error0";
m_workingFuc.waitForFinished();
this->deleteLater();
return;
}

            return;
        }

        // 如果未获取到 method 并且已经定位到了分割标记符,那么直接放弃这个连接
        if ( requestMethod_.isEmpty() && ( splitFlagIndex == 0 ) )
        {

// qDebug() << "JQHttpServer::Session::inspectionBuffer: error1";
m_workingFuc.waitForFinished();
this->deleteLater();
return;
}

        // 如果没有获取到 method 则先尝试分析 method
        if ( requestMethod_.isEmpty() )
        {
            auto requestLineDatas = receiveBuffer_.mid( 0, splitFlagIndex ).split( ' ' );
            receiveBuffer_.remove( 0, splitFlagIndex + 2 );

            if ( requestLineDatas.size() != 3 )
            {

// qDebug() << "JQHttpServer::Session::inspectionBuffer: error2";
m_workingFuc.waitForFinished();
this->deleteLater();
return;
}

            requestMethod_ = requestLineDatas.at( 0 );
            requestUrl_ = requestLineDatas.at( 1 );
            requestCrlf_ = requestLineDatas.at( 2 );

            if ( ( requestMethod_ != "GET" ) &&
                 ( requestMethod_ != "OPTIONS" ) &&
                 ( requestMethod_ != "POST" ) &&
                 ( requestMethod_ != "PUT" ) )
            {

// qDebug() << "JQHttpServer::Session::inspectionBuffer: error3:" << requestMethod_;
m_workingFuc.waitForFinished();
this->deleteLater();
return;
}
}
else if ( splitFlagIndex == 0 )
{
receiveBuffer_.remove( 0, 2 );

            headerAcceptedFinished_ = true;

            if ( ( requestMethod_.toUpper() == "GET" ) ||
                 ( requestMethod_.toUpper() == "OPTIONS" ) ||
                 ( ( requestMethod_.toUpper() == "POST" ) && ( ( contentLength_ > 0 ) ? ( !receiveBuffer_.isEmpty() ) : ( true ) ) ) ||
                 ( ( requestMethod_.toUpper() == "PUT" ) && ( ( contentLength_ > 0 ) ? ( !receiveBuffer_.isEmpty() ) : ( true ) ) ) )
            {
                this->inspectionBufferSetup2();
            }
        }
        else
        {
            auto index = receiveBuffer_.indexOf( ':' );

            if ( index <= 0 )
            {

// qDebug() << "JQHttpServer::Session::inspectionBuffer: error4";
m_workingFuc.waitForFinished();
this->deleteLater();
return;
}

            auto headerData = receiveBuffer_.mid( 0, splitFlagIndex );
            receiveBuffer_.remove( 0, splitFlagIndex + 2 );

            const auto &&key = headerData.mid( 0, index );
            auto value = headerData.mid( index + 1 );

            if ( value.startsWith( ' ' ) )
            {
                value.remove( 0, 1 );
            }

            requestHeader_[ key ] = value;

            if ( key.toLower() == "content-length" )
            {
                contentLength_ = value.toLongLong();
            }
        }
    }
}
else
{
    this->inspectionBufferSetup2();
}

}

void JQHttpServer::Session::inspectionBufferSetup2()
{
requestBody_ += receiveBuffer_;
receiveBuffer_.clear();

if ( !handleAcceptedCallback_ )
{
    qDebug() << "JQHttpServer::Session::inspectionBuffer: error4";
    m_workingFuc.waitForFinished();
    this->deleteLater();
    return;
}

if ( ( contentLength_ != -1 ) && ( requestBody_.size() != contentLength_ ) )
{
    return;
}

if ( contentAcceptedFinished_ )
{
    return;
}

contentAcceptedFinished_ = true;
handleAcceptedCallback_( this );

}

void JQHttpServer::Session::onBytesWritten(const qint64 &written)
{
if ( this->waitWrittenByteCount_ < 0 ) { return; }

autoCloseTimer_->stop();

this->waitWrittenByteCount_ -= written;
if ( this->waitWrittenByteCount_ <= 0 )
{
    this->waitWrittenByteCount_ = 0;
    m_workingFuc.waitForFinished();
    QTimer::singleShot( 500, this, &QObject::deleteLater );
    return;
}

if ( !replyIoDevice_.isNull() )
{
    if ( replyIoDevice_->atEnd() )
    {
        replyIoDevice_->deleteLater();
        replyIoDevice_.clear();
    }
    else
    {
        ioDevice_->write( replyIoDevice_->read( 512 * 1024 ) );
    }
}

autoCloseTimer_->start();

}

经过测试,不在出现系统崩溃。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions