Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
akinggw opened this issue Sep 26, 2023 · 6 comments
Open

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

akinggw opened this issue Sep 26, 2023 · 6 comments

Comments

@akinggw
Copy link

akinggw commented Sep 26, 2023

用定时器不停地访问服务器,会造成系统崩溃。经检查,是多线程引起的,可能会存在还在处理时,连接被关闭的情况。
修改如下:
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();

}

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

@akinggw
Copy link
Author

akinggw commented Sep 26, 2023

经测试,QFuture 不起作用,现修改成如下:
volatile int m_isSafeExit;

增加一个函数:
void JQHttpServer::Session::waitWorkingForFinished()
{
while(m_isSafeExit == 2)
{
QApplication::processEvents(QEventLoop::AllEvents, 200);
}
m_isSafeExit = 3;
}

void JQHttpServer::AbstractManage::handleAccepted(const QPointer< Session > &session)
{
if(session->m_isSafeExit == 3) return;

QtConcurrent::run( handleThreadPool_.data(), [ this, session]() {
    //emit onRedReady(session);

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

    session->m_isSafeExit = 2;
    this->httpAcceptedCallback_( session , this->getMainObject());
    session->m_isSafeExit = 1;
} );

}

经测试,系统已经不在崩溃,但连接多了以后,会出现主动关闭的情况

@188080501
Copy link
Owner

188080501 commented Sep 26, 2023

连接多了是多到多少,目前Qt select模型下并发能力有限,短时间内超过1000个请求就可能会漏

@akinggw
Copy link
Author

akinggw commented Sep 26, 2023

就一个连接,每秒发送一次请求,服务器就会崩溃

@188080501
Copy link
Owner

不修改源码的话,也会崩溃吗?

@akinggw
Copy link
Author

akinggw commented Sep 26, 2023

不修改源码的话就会崩溃,修改后,经过测试,就不崩溃了,但存在偶尔服务器拒绝服务,直接关闭连接

@188080501
Copy link
Owner

感谢反馈
因为session会在多线程使用,所以可能发生session被先删除的问题。因为我这里使用场景,session会被继续跨线程传递,因此没有做wait等待处理完成。而是使用智能指针做了null判断。但是这个判断不是100%稳妥

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants