基于C++的fastcgi
C++在web开发上完全没有什么优势,唯一比较屌的就是处理并发和计算了,但是在常规的业务逻辑上面这两个优势等于没有所以说基本上是全面落后于现有的框架的。相似的开发方式就是ASP .NET那套了但是我不会用C#所以也是卵的。但是之前为了解决web通讯的问题我们尝试了C++的CGI开发,本质上来讲这个开发方式不是只靠一个程序实现的,而是有一个现成的CGI管理程序可以做到对C++程序的复用让其成为这个CGI管理的一个执行单元,从而实现了C++的后端开发。但是这个CGI管理程序不算优雅它无非是多线程监听的机制来对一个请求队列进行处理。简单来讲就是单队列加线程池的模型,但是这个线程池还不是标准的线程池,是需要手动指定的,同时没有一个自我补充机制,相当于一个线程死了之后它就永远死了除非我们手动给系统指令去启动它。
spawn-fcgi
这个东西spawn-fcgi就是我上面说的CGI管理程序我们的c++无非就是执行单元,但是这个东西本质上还是叫Fastcgi进程管理器,通过它形成对应的CGI总线,然后nginx直接代理这个总线就可以了。这个东西从09年到现在就更新了五次最后一次更新在14年你们可以想象它是多么的古老。
spawn-fcgi的下载地址是 http://redmine.lighttpd.net/projects/spawn-fcgi/wiki
这些都是源码版,直接下载编译安装就可以了,有了这个管理程序之后我们才算是仅仅完成了cgi总线但是每一个独立的cgi单元就是程序上面的事情了。
其实spawn-fcgi在php那套cgi出来之前还是非常NB的,基本上那个时候的web项目无论你是什么语言都必须要它来进行CGI管理,当时的php也是需要配置这个东西,而php本身就是执行器而已。但是现在php和其他的后端脚本语言基本都自身研发了一套对应的CGI控制程序所以它就这样死了。
FastCGI库 fcgi_stdio
搭建好CGI管理器之后就是编写对应的CGI程序给这个管理程序使用了。如果从boost库或者标准库开始写cgi用C/C++估计要把我写哭,但是又库一切都简单了,这个库我研究的不深但是基本到了可以使用它的地步,有一个系统本来都使用c++基于这个库开发的顺风顺水但是在计算AES加密签名的时候GG了,我使用的OpenSSL库无论怎么样都会算错,我试了起码五种不同函数与构成都不行于是那是的支付宝支付没有办法吊起所以GG了才会去用java写servlet的。但是除了这个之外的其他东西都没有什么问题比如说http与https请求、oss授权、对应数据的接收与发送(json)方式。
fcgi_stdio库的机制算是不能再简单了,使用C++编写简单的控制台程序然后进入主程序之后直接while循环FCGI_Accept() >= 0就可以了,这种写法的意思也很简单就是说这个控制台程序作为一个工作单元线程直接卡死在这里一旦消息队列之中分配到一个请求到这个循环之后循环进入得到请求头与请求体直接进行业务逻辑的处理,然后通过库中提供的输出函数输出就可以了,这样一来对应所有处理都在循环体内部就可以执行了。
控制台主程序的代码如下:
#include <QCoreApplication> #include "data_control.h" #include "mysql_control.h" #include "oss_control.h" #include "alipay_control.h" using std::string; int main(void) { MySQL_Control* Mysql = new MySQL_Control("192.168.0.2","root","xunshi123","vhome"); QSqlQuery query(Mysql->mysql); query.exec("select * from paks"); QJsonArray OBJ = MySQL_Control::QueryToJson(query); query.clear(); //qDebug()<<data.data(); while (FCGI_Accept() >= 0) { QJsonObject data = Data_Control::GetData(); if(data.isEmpty()) { Data_Control::output("no data to return"); continue; } } qDebug()<<"线程退出"; return 0; }
其实可以很简单的看出我们使用Data_Control这个类进行消息的收发,而之中的业务逻辑完全用C++去执行,当然这就是一个说明代码没有什么逻辑在里面。可以看出我们采用的QT进行编辑因为QT可以说是linux下最好的编辑器没有之一。
Data_Control类代码如下:
#include "data_control.h" Data_Control::Data_Control() { } QJsonObject Data_Control::GetData() { int len = atoi(getenv("CONTENT_LENGTH")); if(len < 0) { return QJsonObject(); } char InputBuffer[4096] = {0}; int i = 0; int x; while( i < len ) { /*从stdin中得到Form数据*/ x = FCGI_fgetc(stdin); if( x == EOF ) { break; } InputBuffer[i++] = x; } char* getchar = InputBuffer; QByteArray resultContent; resultContent = QByteArray(getchar); QJsonObject json = QJsonDocument::fromJson(resultContent).object(); return json; } void Data_Control::output(QString data) { data = "Content-type: textml\r\n\r\n" + data + "\n"; FCGI_printf(data.toStdString().data()); return; } void Data_Control::output(QJsonObject data) { if(data.isEmpty()) { FCGI_printf("Content-type: textml\r\n\r\n"); return; } output(QString(QJsonDocument(data).toJson())); return; } void Data_Control::output(QJsonArray data) { if(data.isEmpty()) { FCGI_printf("Content-type: textml\r\n\r\n"); return; } output(QString(QJsonDocument(data).toJson())); return; }
应该可以很快看出来对应收发数据其实就是字符串,请求到达的时候对应的请求体被完整转为字符串,然后我将其转为json对象通过对json的操作最终将json转为字符串来返回,可以看到返回的头信息其实也在这个字符串里面,大体就是这么麻烦,不像MVC框架可以由控制层这个东西就一个入口一个出口而已。
总结
其实这种方式并没有很好而且稳定性也有一点问题,但是只有一次CGI管理关闭了多个CGI进程,不知道为什么,不是内存泄露也不是空指针。
下面是几种测试情况环境是1核1g的虚拟机,通过nginx代理到cgi管理程序的测试结果,一共开启四个CGI线程
- 在获取数据库情况下,每一次post返回全表200条数据,并发10000次请求,其中40.11%响应超时。其他平均响应时间10989毫秒(共计10000次请求)
- 在获取数据库情况下,每一次post返回全表200条数据,并发1000次请求,其中0.2%响应超时。其他平均响应时间908毫秒(共计1000次请求)
- 在获取数据库情况下,每一次post返回全表200条数据,并发100个线程,每一次线程完成100请求,所有响应全部正常,平均响应时间101毫秒(共计10000次请求)
- 在不获取数据情况下,并发10000次请求,其中20.87%响应超时,平均响应时间7351毫秒(共计10000次请求)
- 在不获取数据情况下,并发1000次请求,所有响应全部正常,平均响应时间1毫秒(共计1000次请求)
- 在获取数据库情况下,并发100个线程,每一次线程完成100请求,所有响应全部正常,平均响应时间13毫秒(共计10000次请求)
性能其实不算非常好,采用如此麻烦的开发方式其实也不是很好。当时主要是其他都不会只能用C++才会这样,包括计算问题其实是可以解决的只是当时没有那么多时间而已。
可能未来某个项目需要大量计算或者复杂算法实现或者需要对接其他服务端硬件的时候这项技术才会有用武之地现在基本上只能说是多了一种方法而已。