QT实现HTTP JSON高效多线程处理服务器

时间:2022-11-01 23:06:09

QT实现HTTP JSON高效多线程处理服务器

Legahero QQ:1395449850

现在一个平台级的系统光靠web打天下是不太现实的了,至少包含APP和web两部分,在早期APP直接访问web交换数据,后来程序员们发现由于 web界面的变化和数据展现多变,APP需要一个稳定、轻量的数据交互接口协议,重量的web无法满足,http json由于数据扩展性好、数据结构简单、轻量成为首选协议。

最近考虑用QT实现HTTP JSON服务器,主要原因是:使用java (servlet、com.sun.net.httpserver)容易反编译,虽然网上提供了一大把的java加密、混淆方案,但总感觉麻烦和不靠谱;其次单点服务器下运行效率java比C++慢;再有运维部署exe程序比web服务器简单(当然你要保证你的程序需要最够稳定);最后可以使你的json服务与web程序服务完全独立,web的维护不会影响到json服务,这点很重要能够保证你的系统不至于在web运维期间完全瘫痪。

选择QT来实现,编程速度比其他的C++快,windows、linux兼容性好,这里边需要解决几个问题:

  1. 框架的业务实现部分必须最够简单,业务部分容易实现和扩充;
  2. 高效多线程并发处理必须最够强;
  3. 体量小,CPU、内存占用小,能够长时间稳定运行;

处理流程总结如下:

QT实现HTTP JSON高效多线程处理服务器

HTTP协议基于TCP协议,首先实现一个异步收发的TCP服务类:

//继承QTCPSERVER以实现多线程TCPscoket的服务器。

class QAsynTcpServer : public QTcpServer

{

Q_OBJECT

public:

explicit QAsynTcpServer(QObject *parent = 0,int numConnections = 10000);

~QAsynTcpServer();

void setMaxPendingConnections(int numConnections);//重写设置最大连接数函数

protected slots:

void sockDisConnectSlot(int handle,const QString & ip, quint16 prot, QThread *th);//断开连接的用户信息

public:

void clear(); //断开所有连接,线程计数器请0

protected:

void incomingConnection(qintptr socketDescriptor);//覆盖已获取多线程

QHash<int,QTcpSocket *> * m_ClientList;//管理连接的map

private:

int maxConnections;

};

再从该QAsynTcpServer类继承实现Http服务类:

class QHttpServer : public QAsynTcpServer

{

Q_OBJECT

public:

QHttpServer(QObject *parent = 0,int numConnections=1000);

virtual ~QHttpServer();

private Q_SLOTS:

void handleRequest(QHttpRequest *request, QHttpResponse *response);

protected slots:

void sockDisConnectSlot(int handle,const QString & ip, quint16 prot, QThread *th);//断开连接的用户信息

protected:

void incomingConnection(qintptr socketDescriptor);//覆盖已获取多线程

};

实现多线程处理必须重写void QTcpServer::incomingConnection(qintptr socketDescriptor)的实现:

void QHttpServer::incomingConnection(qintptr socketDescriptor)
{
    qDebug() << "QHttpServer:incomingConnection,ThreadId:"<<QThread::currentThreadId()  ;
 
    //继承重写此函数后,QQAsynTcpServer默认的判断最大连接数失效,自己实现
    if (m_ClientList->size() > maxPendingConnections())
    {
        QTcpSocket tcp;
        tcp.setSocketDescriptor(socketDescriptor);        
        tcp.disconnectFromHost();
        qDebug() << "tcpClient->size() > maxPendingConnections(),disconnectFromHost";
        return;
    }
    auto th = ThreadHandle::getClass().getThread();
    QAsynHttpSocket* tcpTemp = new QAsynHttpSocket(socketDescriptor);
    QString ip =  tcpTemp->peerAddress().toString();
    qint16 port = tcpTemp->peerPort();
 
    //NOTE:断开连接的处理,从列表移除,并释放断开的Tcpsocket,线程管理计数减1,此槽必须实现
    connect(tcpTemp,SIGNAL(sockDisConnect(const int ,const QString &,const quint16, QThread *)),
            this,SLOT(sockDisConnectSlot(const int ,const QString &,const quint16, QThread *)));
 
    //必须在QAsynHttpSocket的线程中执行
    connect(tcpTemp, SIGNAL(newRequest(QHttpRequest *, QHttpResponse *)), this,
            SLOT(handleRequest(QHttpRequest *, QHttpResponse *)), Qt::DirectConnection);
 
    tcpTemp->moveToThread(th);//把tcp类移动到新的线程,从线程管理类中获取
    m_ClientList->insert(socketDescriptor,tcpTemp);//插入到连接信息中
 
    qDebug() << "QHttpServer m_ClientList add:"<<socketDescriptor  ;
}

ThreadHandle::getClass().getThread()为每个连接分配一个处理线程,m_ClientList管理所有的连接。

handleRequest处理Http请求,必须在该连接的线程中执行,所以使用了Qt::DirectConnection,在新的请求发生时触发,在该处实现和扩展json业务,以下为代码样例:

//处理新的http 请求,这里处理业务

void QHttpServer::handleRequest(QHttpRequest *req, QHttpResponse *resp)

{

qDebug() << "QHttpServer:handleRequest,ThreadId:"<<QThread::currentThreadId()  ;

qDebug() <<"path:"<< req->path();

QJsonDocument doc=QJsonDocument::fromBinaryData(req->body());

QJsonObject recv_obj=doc.object();//这是接收到的json对象

QJsonObject resp_obj; //返回json对象

resp_obj.insert("man_num",4);

resp_obj.insert("time", "20150601113432");

QByteArray data = QJsonDocument(resp_obj).toJson(QJsonDocument::Compact);

resp->setHeader("Content-Type", "text/html");

resp->setHeader("Content-Length", QString::number(data.length()));

resp->writeHead(200);

resp->end(data);

resp->flush();

req->deleteLater();

resp->deleteLater();

qDebug() <<"handleRequest end";

}

在这里使用QJsonDocument来解析json请求,并给请求回json应答,按应用求求实现自己的业务逻辑。

当客户端关闭连接的时候需要释放连接占用的线程资源,并从连接管理中删除。

//释放线程资源

void QHttpServer::sockDisConnectSlot(int handle,const QString & ip, quint16 prot,QThread * th)

{

qDebug() << "QHttpServer:sockDisConnectSlot,ThreadId:"<<QThread::currentThreadId()  ;

qDebug() << "QHttpServer m_ClientList size:"<<m_ClientList->size()  ;

qDebug() << "QHttpServer m_ClientList:remove:"<<handle  ;

m_ClientList->remove(handle);//连接管理中移除断开连接的socket

ThreadHandle::getClass().removeThread(th); //告诉线程管理类那个线程里的连接断开了,释放数量

qDebug() << "QHttpServer m_ClientList size:"<<m_ClientList->size()  ;

}

在这里我们理解为客户端采用一个请求一个应答的往来方式实现http 解析,从QTcpSocket继承建立自己的QAsynHttpSocket类:

class QAsynHttpSocket: public QTcpSocket

{

Q_OBJECT

public:

explicit QAsynHttpSocket(qintptr socketDescriptor, QObject *parent = 0);

~QAsynHttpSocket();

void write(const QByteArray &data);

//void flush();

void waitForBytesWritten();

Q_SIGNALS:

void newRequest(QHttpRequest *, QHttpResponse *);

void allBytesWritten();

signals:

//NOTE:断开连接的用户信息,此信号必须发出!线程管理类根据信号计数的

void sockDisConnect(const int ,const QString &,const quint16, QThread *);

private Q_SLOTS:

void doParseRequest();

void doDisconnected();

void doUpdateWriteCount(qint64);

private:

static int doMessageBegin(http_parser *parser);

static int doMessageComplete(http_parser *parser);

static int doHeaderField(http_parser *parser, const char *at, size_t length);

static int doHeaderValue(http_parser *parser, const char *at, size_t length);

static int doHeadersComplete(http_parser *parser);

static int doBody(http_parser *parser, const char *at, size_t length);

static int doUrl(http_parser *parser, const char *at, size_t length);

static int doStatus(http_parser *parser, const char *at, size_t length);

static int doChunkHeader(http_parser *parser);

static int doChunkComplete(http_parser *parser);

private:

qintptr socketID;

http_parser *m_parser;

http_parser_settings *m_parserSettings;

//临时变量

QByteArray m_currentUrl;

// The ones we are reading in from the parser

HeaderHash m_currentHeaders;

QString m_currentHeaderField;

QString m_currentHeaderValue;

QByteArray m_currentBody;

QByteArray m_currentStatus;

// Keep track of transmit buffer status

qint64 m_transmitLen;

qint64 m_transmitPos;

private:

Q_DISABLE_COPY(QAsynHttpSocket)

};

在本程序中使用nodejs 的http-parser的源代码来解析http 请求,http-parser是一个用C代码编写的HTTP消息解析器,可以解析HTTP请求或者回应消息,该解析器常常在高性能的HTTP应用中使用;在解析的过程中,它不会调用任何系统资源,不会在HEAP上申请内存,不会缓存数据,并且可以在任意时刻打断解析过程,而不会产生任何影响,对于每个HTTP消息(在WEB服务器中就是每个请求),需要的内存占用很少(40字节?)。

http-parser的特性:无第三方依赖、可以处理持久消息(keep-alive)、支持解码chunk编码的消息、支持Upgrade协议升级(如无例外就是WebSocket)、可以防御缓冲区溢出攻击。解析器可以处理以下类型的HTTP消息:头部的字段和值、Content-Length、请求方法、返回的HTTP代码、Transfer-Encoding、HTTP版本、请求的URL、HTTP消息body主体。

使用非常简单,建立回调函数,执行http_parser_execute连续分析数据。可以在QAsynHttpSocket的构造函数中初始化http_parser,绑定回调函数。

QAsynHttpSocket::QAsynHttpSocket(qintptr socketDescriptor, QObject *parent) : //构造函数在主线程执行,lambda在子线程

QTcpSocket(parent),socketID(socketDescriptor),

m_parser(0),

m_parserSettings(0),

m_transmitLen(0),

m_transmitPos(0)

{

this->setSocketDescriptor(socketDescriptor);

m_parser = (http_parser *)malloc(sizeof(http_parser));

http_parser_init(m_parser, HTTP_REQUEST);

m_parserSettings = new http_parser_settings();

m_parserSettings->on_message_begin = doMessageBegin;

m_parserSettings->on_message_complete = doMessageComplete;

m_parserSettings->on_header_field = doHeaderField;

m_parserSettings->on_header_value = doHeaderValue;

//在HeadersComplete完成时激发newRequest

m_parserSettings->on_headers_complete = doHeadersComplete;

m_parserSettings->on_body = doBody;

m_parserSettings->on_status = doStatus;

m_parserSettings->on_url = doUrl;

m_parserSettings->on_chunk_header = doChunkHeader;

m_parserSettings->on_chunk_complete = doChunkComplete;

m_parser->data = this;

connect(this, SIGNAL(readyRead()), this, SLOT(doParseRequest()));

connect(this, SIGNAL(disconnected()), this, SLOT(doDisconnected()));

//当所有的字节写完updateWriteCount激发allBytesWritten信号

connect(this, SIGNAL(bytesWritten(qint64)), this, SLOT(doUpdateWriteCount(qint64)));

qDebug() << "new connect:"<<socketDescriptor ;

}

http_parser有两种类型的回调:

  • 通知 typedef int (*http_cb) (http_parser *);包括:on_message_begin,on_headers_complete,on_message_complete
  • 数据 typedef int (*http_data_cb) (http_parser *, const char at, size_t length);包括;(只限与请求)on_uri, (通用) on_header_fieldon_header_value,on_body

用户的回调函数应该返回0表示成功。返回非0的值,会告诉解析器发生了错误,解析器会立刻退出。

Socket的readyRead()对应的槽实现函数中持续调用http_parser_execute。

void QAsynHttpSocket::doParseRequest()

{

Q_ASSERT(m_parser);

qDebug() << "QHttpConnection:parseRequest,ThreadId:"<<QThread::currentThreadId()  ;

while (this->bytesAvailable()) {

qDebug() << "readAll,ThreadId:"<<QThread::currentThreadId()  ;

QByteArray arr = this->readAll();

qDebug() << "http_parser_execute begin,ThreadId:"<<QThread::currentThreadId()  ;

http_parser_execute(m_parser, m_parserSettings, arr.constData(), arr.size());

qDebug() << "http_parser_execute end,ThreadId:"<<QThread::currentThreadId()  ;

}

}

http_parser在每一个http请求消息解析完成后触发MessageComplete回调,这个时候可建立QHttpRequest和QHttpResponse,并发射新请求到来信号,触发handleRequest业务处理。

int QAsynHttpSocket::doMessageComplete(http_parser *parser)

{

qDebug() << "doMessageComplete" ;

// TODO: do cleanup and prepare for next request

QAsynHttpSocket *theConnection = static_cast<QAsynHttpSocket *>(parser->data);

QHttpRequest* request = new QHttpRequest(theConnection);

/** set method **/

request->setMethod(static_cast<QHttpRequest::HttpMethod>(parser->method));

/** set version **/

request->setVersion(

QString("%1.%2").arg(parser->http_major).arg(parser->http_minor));

/** get parsed url **/

struct http_parser_url urlInfo;

int r = http_parser_parse_url(theConnection->m_currentUrl.constData(),

theConnection->m_currentUrl.size(),

parser->method == HTTP_CONNECT, &urlInfo);

Q_ASSERT(r == 0);

Q_UNUSED(r);

request->setUrl(createUrl(theConnection->m_currentUrl.constData(), urlInfo));

// Insert last remaining header,这个已经在doHeadersComplete中执行

//theConnection->m_currentHeaders[theConnection->m_currentHeaderField.toLower()] =

//   theConnection->m_currentHeaderValue;

request->setHeaders(theConnection->m_currentHeaders);

/** set client information **/

request->m_remoteAddress = theConnection->peerAddress().toString();

request->m_remotePort = theConnection->peerPort();

qDebug() << "QHttpResponse:new,ThreadId:"<<QThread::currentThreadId()  ;

QHttpResponse *response = new QHttpResponse(theConnection);

if (parser->http_major < 1 || parser->http_minor < 1)

response->m_keepAlive = false;

Q_EMIT theConnection->newRequest(request, response);

return 0;

}

本程序框架代码很少,在windows10下占用内存9.5M,经过测试完全满足高并发长期稳定运行。 代码下载

备注:本程序参照网上局部代码并经过大幅度整理修改,大部分代码原创实现。

QT实现HTTP JSON高效多线程处理服务器的更多相关文章

  1. iOS开发网络篇—发送json数据给服务器以及多值参数

    iOS开发网络篇—发送json数据给服务器以及多值参数 一.发送JSON数据给服务器 发送JSON数据给服务器的步骤: (1)一定要使用POST请求 (2)设置请求头 (3)设置JSON数据为请求体 ...

  2. 【转】iOS开发网络篇—发送json数据给服务器以及多值参数

    原文: http://www.cnblogs.com/wendingding/p/3950132.html 一.发送JSON数据给服务器 发送JSON数据给服务器的步骤: (1)一定要使用POST请求 ...

  3. Qt中使用Json

    Qt中使用Json需要一下几个类: QJsonValue            代表了json格式中的一个值 QJsonObject          代表了json格式的一个对象 QJsonArra ...

  4. IOS-网络&lpar;发送JSON数据给服务器和多值参数)

    三步走: 1.使用POST请求 2.设置请求头 [request setValue:@"application/json" forHTTPHeaderField:@"Co ...

  5. Qt QJson解析json数据

    Qt QJson解析json数据 //加载根目录文件 void TeslaManageData::loadRootFolderFiles() { QNetworkAccessManager *mana ...

  6. android使用JSON数据和服务器进行交互

    //点击按钮发送反馈信息给服务端,成功则进入优惠券界面 Button upload = (Button) findViewById(R.id.upload); final String finalLa ...

  7. QT解析嵌套JSON表达式

    QT5开发环境集成了解析JSON表达式的库.使用很方便. 友情提示一下,好像在QT4环境里.须要到官网下载相关的库文件才干使用解析功能.话不多说,上代码 1.在pro文件里增加 QT += scrip ...

  8. 「译」使用 System&period;Net&period;Http&period;Json 高效处理Json

    在这篇文章,我将介绍一个名为 System.Net.Http.Json 的扩展库,它最近添加到了 .NET 中,我们看一下这个库能够给我们解决什么问题,今天会介绍下如何在代码中使用. 在此之前我们是如 ...

  9. iOS开发 -- 发送JSON数据给服务器

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // 1.URL NSURL *url = [NSURL URLW ...

随机推荐

  1. SolidWorks的简单介绍及基本用法

    写这博客的动机来源于构建之法微信群里面的的一位老师.sw是一个强大的机械设计制图软件,我记得大一的时候学制图学的3d软件是inventor,而后发现sw用起来更方便更高效,于是就自学了sw,由于是自学 ...

  2. Linux常用目录

  3. &lbrack;c&num;基础&rsqb;AutoResetEvent

    摘要 AutoResetEvent:msdn的描述是通知正在等待的线程已发生事件.此类不能被继承.也就是说它有那么一个时间点,会通知正在等待的线程可以做其它的事情了. AutoResetEvent 该 ...

  4. Leetcode 278 First Bad Version 二分查找(二分下标)

    题意:找到第一个出问题的版本 二分查找,注意 mid = l + (r - l + 1) / 2;因为整数会溢出 // Forward declaration of isBadVersion API. ...

  5. python进阶3--文件系统

    文件系统 python的标准库中包括大量工具,可以处理文件系统中的文件,构造和解析文件名,也可以检查文件内容. pyhton表文件名表示为简单的字符串,另外还提供了一些工具,用来由os.path中平台 ...

  6. 快速排序java

    快速排序(Quicksort)是对冒泡排序的一种改进.它是先在数组中找到一个关键数,第一趟排序将比关键数小的放在它的左边,比关键数大的放在它的右边.当第一趟排序结束后,再依次递归将左边和右边的进行排序 ...

  7. Talend open studio数据导入、导出、同步Mysql、oracle、sqlserver简单案例

    推荐大家一个BI工具:talend open studio.我也是刚接触,懂得不多,感觉比较神奇就想大家推荐一下... 由于公司项目,接触了一下BI工具talend,感觉功能很强大, 可以同步多种数据 ...

  8. Selenide UI 自动化测试

       我没有拼写错误,确实不是 Selenium ,但是,只要是 Web UI 自动化测试框架,基本上都是基于Selenium 的.Selenide 也不例外.那为啥不直接用Selenium呢? 因为 ...

  9. 《BUG创造队》第二次团队作业:团队项目选题报告

    项目 内容 这个作业属于哪个课程 2016级软件工程 这个作业的要求在哪里 实验六 团队作业2:团队项目选题 团队名称 BUG创造队 作业学习目标 可行性自评总结,并且采用NABCD方法进行项目初步分 ...

  10. 学习笔记--python中使用多进程、多线程加速文本预处理

    一.任务描述 最近尝试自行构建skip-gram模型训练word2vec词向量表.其中有一步需要统计各词汇的出现频率,截取出现频率最高的10000个词汇进行保留,形成常用词词典.对于这个问题,我建立了 ...