QHttpServer 填坑

QHttpServer 是我从github 下载的一个开源库。前些时候,有兄弟拿着这个库做项目,总是崩溃,然后来找我。一开始还以为真这库有什么bug,仔细一看,是这兄弟自己没注意埋下的坑。

我这里用简短的代码,简单的重现他的问题。

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    http = new QHttpServer();
    connect(http, &QHttpServer::newRequest, this, &MainWindow::request);
    http->listen(QHostAddress::Any, 8087);
}

void MainWindow::request(QHttpRequest *req, QHttpResponse *resp)
{
    m_req = req;
    m_resp = resp;
    connect(req, &QHttpRequest::data, this, &MainWindow::appendBody);
    connect(req, &QHttpRequest::end, this, &MainWindow::end);
}

void MainWindow::appendBody(const QByteArray &body)
{
    m_body += body;
}

void MainWindow::end()
{
    qDebug()<< m_body;
    m_resp->setHeader("Content-Length", QString::number(m_body.size()));
    m_resp->writeHead(200);
    m_resp->end(m_body);

    m_req->deleteLater();
    m_resp->deleteLater();
    m_req = nullptr;
    m_resp = nullptr;
}

首先是在构造函数中,创建 http 服务,监听8087 端口。然后在 request 槽函数中,绑定槽函数 appendBody 和 end 。 一切看似正常,对于小程序,并且是服务端与客户端一对一的时候,的确是没啥大问题。但当客户端多,且需要同时访问服务端的时候,会发生什么呢?

QHttpServer 填坑
就像上面的图,大家同时访问服务器,但我们保存客户端请求的指针只有一个,这势必会造成冲突,引起混乱,崩溃也是意料之中。
所以为避免这种情况,我们需要一个专门处理客户端请求的类,当接收到客户端请求时,就新建一个这样的对象,去处理请求的数据,处理完成自动释放自己,每一个客户端的请求都有一个专门的对象负责,这样才不会打架嘛。
还有,网络拥堵,数据接收到一半,客户端和服务端的连接突然中断,导致服务器接收不到客户端发过来的end 的消息。当接收不到end消息的时候,那处理该请求的对象,就会一直等待着,直到程序结束才会被释放。为了防止这种事情发生,我们还应该做一个请求计数,维护一个链表。当这个链表大于一定的数量(大于同时接收到请求的数量)时,将链表中最早的那个处理请求的对象给释放掉。

//mainwindow.cpp

QList<HandleRequest*> handleRequests;

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    http = new QHttpServer();
    connect(http, &QHttpServer::newRequest, this, &MainWindow::request);
    http->listen(QHostAddress::Any, 8087);
}

void MainWindow::request(QHttpRequest *req, QHttpResponse *resp)
{
    HandleRequest *handleRequest = new HandleRequest(req, resp, this);
    handleRequest->setHandleRequests(&handleRequests);
    handleRequests.append(handleRequest);

    if(handleRequests.count() > 50) {
        handleRequests.first()->deleteLater();
        handleRequests.pop_front();
    }
}

//handlerequest.cpp

HandleRequest::HandleRequest(QHttpRequest *req, QHttpResponse *resp, QObject *parent) :
    QObject(parent),
    m_req(req),
    m_resp(resp)
{
    m_req->storeBody();
    connect(m_req, &QHttpRequest::end, this, &HandleRequest::end);
}

void HandleRequest::end()
{
    QByteArray m_body = m_req->body();
    qDebug()<< m_body;

    m_resp->setHeader("Content-Length", QString::number(m_body.size()));
    m_resp->writeHead(200);
    m_resp->end(m_body);

    m_req->deleteLater();
    m_resp->deleteLater();
    m_req = nullptr;
    m_resp = nullptr;

    m_handleRequests->removeOne(this);
    this->deleteLater();
}

void HandleRequest::setHandleRequests(QList<HandleRequest *> *handleRequests)
{
    m_handleRequests = handleRequests;
}

源码1
源码2