一直对CGI的概念就是客户端交由处理的具体请求事物的后台程序,看到这篇文章通俗易懂的把CGI讲解清楚,往后对自己写后台业务也会有更多理解。
文章转载自CGI
我们知道,服务端要处理用户的请求,必须通过编程语言来编写对应的处理逻辑,而你要做的不仅如此,你碰到的第一个问题就是如何接收用户的TCP请求并解析出HTTP协议格式。
OK,专业的事情交给专业的人来干。必然,我们应该用专业的Web Server(如Nginx、 Apache)来接收和解析TCP请求。而业务处理部分不可能会有开源的东西直接拿来用的,毕竟每一家公司的业务都是不同的,所以需要采用C、C++、Perl、Java、Node、Python等等编程语言来编码,并编译为一个可执行程序。(由于php等脚本语言是解释执行的,所以需要靠对应的php解释器来执行。)
说到这里,问题就来了,Web服务器如何与业务处理程序进行通信呢,以及其通信时传递数据的格式是怎么样的呢。这里就需要一套标准的协议,于是人们创造了CGI协议。 所以能处理CGI协议数据的业务处理程序,就被称为CGI程序。
Web Server和CGI程序通过环境变量、标准输入、标准输出进行通信。
协议与语言无关,只要一门语言支持标准输入、标准输入、读取环境变量,它就可以用来写CGI程序,就是说可以用bash、perl、c语言等来写。
环境变量中存放的是一次请求的相关信息,比如QUERY_STRING、
PATH_INFO、REMOTE_ADDR、REMOTE_HOST、CONTENT_TYPE、CONTENT_LENGTH、REQUEST_METHOD、SERVER_NAME等。 从标准输入读取的数据如表单中post过来的东西。
CGI
当客户端发出一个执行CGI程序的请求给服务器以后,服务器根据CGI的程序类别,决定某种数据传递方式,即服务器与CGI程序的沟通方式,这就是编写CGI程序的关键点。一般情况下,服务器与CGI程序之间是通过标准输入输出进行数据传递的。
这种通过标准输入输出来实现服务器与CGI之间的数据传递的标准,对于基于文件的UINX系统尤其适用
对服务器与CGI程序而言就是:服务器的标准输出与CGI程序的标准输入相连接,服务器标准输入与CGI的标准输出相连接。如此,就构成了一个完整的数据循环通路。
事实上,每个CGI程序的环境变量集在该CGI程序开始执行到终止,其对应的环境变量集就失效了,也就是对应环境变量集生存期与CGI程序是一致的.
工作流程
- 一个用户请求激活一个CGI应用程序;
- CGI应用程序将交互主页里用户输入信息提取出来;
- 将用户输入的信息传给服务器主机应用程序(如数据库查询〕;
- 将服务器处理结果通过HTML文件返回给用户;
- CGI进程结束。
优点
程序执行一次就退出,不需要关心资源释放,而是在每次处理完请求之后,由系统统一回收。
缺点
Fork-and-execute的模式,是的每次处理用户请求,都必须经历fork cgi进程、销毁cgi进程,一系列的I/O开销降低了网络的吞吐量
每一个客户端的连接都会启动一个CGI进程,在启动CGI进程过程中会不断重复去加载 CGI 脚本,整个过程还是很浪费系统资源,特别是在大并发的访问条件下,性能会很低下的
例:
Apache+CGI
配置Apache以允许CGI很简单。只需要在Apache的配置中,做一个脚本别名的配置:
|
|
这样,所有请求/cgi-bin路径,都被Apache认为是CGI脚本,那么Apache就会去执行usr/local/apache/cgi-bin/这个路径下的程序了。比如,如果有URL为http://www.example.com/cgi-bin/test.cgi的请求,Apache会试图执行/usr/local/apache/cgi-bin/test.cgi文件并返回其输出。当然,这个文件必须存在而且可执行,并以特定的方法产生输出,否则Apache返回一个出错消息。
编写CGI程序
对于CGI模式,这个过程理解起来其实比较简单,所以我们编写的CGI程序也只需要能够进行输入输出流的读写即可。
比如甚至可以用bash来编写一个程序:
|
|
不过下文讲到的FastCGI,由于需要持续运行保持一个死循环来接收新的请求,所以为了方便,C++程序中一般也要引入一个库。
FASTCGI
为了解决CGI的性能问题,出现了FastCGI,fastcgi则采用常驻进程的方式,在每个请求处理和结束的过程中,始终常驻在内存中,一旦激活,不需要花时间去fork,有效降低了前端请求应答的时延。在进行并发请求一个数据接口,或者请求一个文件的测试中,fastCGI的每个请求平均响应时间明显是更短,因此其吞吐量必然也更大。
FASTCGI原理,大概是我画的图这样的:
来看下Nginx里往FastCGI转发请求的配置:
|
|
可见,fastCGI确实是以Socket或UnixSocket的形式建立连接的。 其大概流程是,Web服务器接收到请求后,通过自身的FastCGI转发模块(如nginx的fastcgi_pass)跟fastCGI进程管理器建立连接,详细如下:
- Web 服务器启动时载入初始化FastCGI执行环境 。 例如
IIS ISAPI
、apache mod_fastcgi
、nginx ngx_http_fastcgi_module
、lighttpd mod_fastcgi
- FastCGI进程管理器(例如跟Nginx配合的spawn_fcgi)自身初始化,启动多个CGI解释器进程并等待来自Web 服务器的连接。启动FastCGI进程时,可以配置以ip和UNIX 域socket两种方式启动。我们看到上面我的老博客的Nginx配置,是把请求转发给php-fpm具有地址和端口,那么php-fpm应该就是以socket的方式启动的。
- 当客户端请求到达Web 服务器时, Web 服务器将请求采用socket方式转发到 FastCGI主进程,FastCGI主进程选择并连接到一个CGI解释器(如果是C++写的就不是解释器了,而是C++的CGI程序)。Web 服务器将CGI环境变量和标准输入发送到FastCGI子进程。
- FastCGI子进程完成处理后将标准输出和错误信息从同一socket连接返回Web服务器。当FastCGI子进程关闭连接时,请求便处理完成。
- FastCGI子进程接着等待并处理来自Web 服务器的下一个连接。
由于 FastCGI 程序并不需要不断的产生新进程,可以大大降低服务器的压力并且产生较高的应用效率。它的速度效率最少要比CGI 技术提高 5 倍以上。它由于跟Web服务器之间通过socket这样的通信,所以还支持分布式的部署, 即 FastCGI程序可以在web 服务器以外的主机上执行。
Nginx+spawn-fcgi
这是Nginx一套nginx下配置fastcgi的常用组合,使用spawn-fcgi这个来让Nginx具有cgi的能力。
首先,安装nginx,安装的nginx自己就具有了转发cgi的能力。
|
|
但是,还得安装一个fastCGI进程管理器,他负责把CGI程序初始化起来,准备好,等待Web服务器的链接。所以需要执行:
|
|
如果编写fastCGI的C++程序,还需要安装到系统上对应的库,在C++代码中进行引用。然后C++代码写成类似这样的:
|
|
与CGI程序的区别,就是主框架中有个循环:
|
|
然后需要配置Nginx里的fastcgi转发:
|
|
然后要启动spawn-fcgi:
|
|
apache+mod_fastcgi
通过mod_fcgid模块实现。这个模块曾属于第三方,但是在2009年被授予ASF,成为Apache的一个子项目。mod_fcgid
的安装参考mod_fcgid目录下的README-FCGID文件,里面包含具体安装方法,这里不在赘述
如果正常安装的话,会自动在apache配置文件中新增如下配置:
|
|
除了上面加载模块外,还需要在CGI运行目录加入如下配置项:
|
|
webserver——qzhttp
这个是公司内部的一个webserver,集成了fastcgi的功能。qzhttp支持fastcgi/cgi的模式,具有一系列成熟的监控模块,在fastcgi模式下,可以自适应的根据请求的数量来动态调整常驻进程的个数
FastCGI的缺点
采用fastcgi固然能够提高吞吐量,但是由于是常驻进程的模式,cgi版本变更发布相比apache+cgi的方式则显得不是那么灵活了。需要按照特定的方式灰度发布生效,后续将会进一步介绍