今天看 nginx 的时候,虽然基本的配置和使用可以照着网上的教程即可,但是对于 nginx 的运行原理一直不是很理解,包括其中的概念,其中最困惑的就是 FastCGI 这一套东西,始终无法理解这套机制是怎么运行的?通过查资料发现,要理解 FastCGI 首先要知道 CGI 是什么,“通用网关接口(Common Gateway Interface)”,一看就是让人懵圈的名词,要想彻底明白什么是 CGI ,有必要回溯一下 Web 技术的发展。

最一开始的时候,Web 浏览器中展现的完全是静态页面,与用户的交互几乎没有,所以这时候的使用主要还是局限在页面的展示,比如说介绍一些产品,或者发布一些信息。现在在网上看一下静态的技术文档的时候,其实这时候使用的技术在 Web 早期的时候就能实现,但是随着互联网的发展,迫切需要与应用程序进行交互,如果要实现交互,使用静态页面是不行的,这就需要根据用户的需求来动态的展示页面。这时候 CGI 就出现了。

所以要明确的一点是 CGI 是为了生成动态页面而发展形成的。

  • 因为早期生成动态页面的动作是在 Web 服务器端完成的(虽然现在的趋势有点前后端分离的感觉,页面的生成可以完全靠前端的JavaScript来完成),所以这种技术是一种服务端技术;

  • 还要明白的是CGI不是一种具体的实现技术,比如说 Java 或者 Python 等编程语言,严格的说,它更像是 Http 或者 TCP、UDP 等等的一种协议,具体规定了动态页面怎么生成,从它的名字“接口”也可以看出,这其实只是一种规范而已;

CGI 和 Servlet 的运行过程

由于 Java Web 的核心内容 Servlet 正是与CGI是作为Web技术中底层数据传输的两个分支,它们生成动态页面的方式也是迥异的。其实如果想要更容易的理解什么是 CGI,以及与 Servlet 有什么区别?首先要明白的是什么是 Web Server ?什么是 Servlet Container ?如果这两个概念能够区分清楚对于理解 CGI 是很有帮助的。

那么 CGI 技术是怎样在生成动态页面的过程中起作用的呢?

在上面的过程中,要注意的几点:

  • Http 协议只存在于Web Client和Web Server之间,与CGI程序之间没有任何关系,所以使用CGI技术无法对Http的传输产生任何影响;

  • Web Server与CGI程序之间的数据传输遵循的是CGI规范,此时Web Server作为CGI请求的客户端,CGI程序作为CGI请求的服务器;

  • Web Server和CGI程序运行在同一个主机上,针对每个CGI请求,Web Server都会fork一个新的CGI程序的进程,等到请求处理完毕则该进程结束,等待下一个请求再来的时候,则重新fork一个进程运行CGI程序;

  • Web Server接受了Web Client发送的Http请求:

    1. 如果请求的是静态页面则直接到Web Server自己的目录下查询并直接返回;

    2. 如果是动态页面(至于如何判断是静态页面还是动态页面则是在Web Server中通过正则表达式进行匹配),通过解析该请求携带的信息,并依据CGI规范将这些信息打包到CGI规定的变量中,然后在Web Server中调用OS中对应的CGI程序来解析该CGI请求,然后生成相应的页面返回给Web Server服务器,然后Web Server再将该内容返回给客户端;

  • CGI程序不对Web 客户端开放,它只对Web Server 服务器负责以供调用,CGI程序是与OS有关的,需要根据OS的环境使用适当的语言来开发,目前其实最流行的就是迎着全世界的嘲讽占领世界的PHP脚本语言解析器,另外之前的时候还是使用Perl开发的,除此之外甚至可以使用C或者C++语言来编写,只要它能实现CGI的规范,但是这些都是直接和OS打交道的;

么为什么Java Web开发人员对于上述的过程比较困惑?首先是因为Servlet容器的原因,随着技术的发展,在Java Web领域,Web Server和Servlet Container的区分变得越来越不明显的,甚至将它们合二为一,比如我们常见的Tomcat或者Jetty服务器,严格来说它们只是Servlet Container,并不算Web Server,但是为了方便,渐渐地这两个概念开始重合,所以一开始在开发Java Web程序时也是很让人懵逼的,一会说Web Server,一会说Servlet Container,其实这两个东西是一个(仅限于Java中)。

对于JSP就类似于脚本语言中的动态页面,比如php、perl等。而我们在Java开发过程中常用的Tomcat对应到CGI开发中,其实是Web Server+CGI程序,将Http请求的处理和动态页面的生成集中在一个程序中处理。

CGI和Servlet的对比

对于生成动态页面和进行数据的传输,CGI和Servlet是两个选项,那它们之间的有什么具体的区别?

  1. 从使用上来说:

    • CGI已经基本属于历史故事了,而她的嫡系子孙FastCGI却仍然在发展,而且作为php的使用环境大受欢迎,包括nginx也支持使用FastCGI;

    • 而对于servlet来说,则是蓬勃发展,虽然目前Java EE有些式微,但是作为实现微服务的前沿框架Spring Boot仍然是建立在Servlet的基础之上;

  2. 从性能上来说:

    • Servlet运行在一个进程中,对于每个请求则会生成一个单独的线程,由这些独立的线程处理对应的http请求,Servlet执行完毕后不会销毁,而是驻留在内存中直到Servlet Container关闭,以便能随时处理http请求;

    • CGI则是Web Server针对每个Http请求生成一个CGI请求,然后将针对CGI请求生成一个单独的进程;由于CGI的这种机制,我们都知道一个普通的PC机有65536个端口,每个进程运行需要一个端口,那就是说CGI程序同时最多(当然是不可能的,因为还有其他程序)可以处理65536个请求,对于高并发的环境,这样的程序根本满足不了而且每处理完一个请求都会关闭,那么对于资源的复用,比如数据库连接,则无从谈起;

  3. 从数据共享来说

    • Servlet是由多个线程处理请求,所以各个请求之间可以进行很容易的进行数据共享;

    • 而由于CGI涉及多个进程,进程之间的通信,如果学过OS的话可以知道是多么痛苦,比如什么消息队列,管道还有共享内存等等,或者直接持久化来共享;

  4. 从兼容性来说

    • 针对Servlet,如果将编译生成的字节码文件移动到另外的主机或者另外的操作系统中,只要是存在JVM就仍然可以运行;

    • 但是对于CGI程序,因为是直接面向OS,而不是像Java程序一样面向虚拟机,所以当该程序移动到另外的OS时可能需要更改代码或者重新编译;

CGI 的改进 —— FastCGI

鉴于CGI的各种缺点,后来出现了FastCGI,其实从名字上就可以看出来,这是针对CGI提出的性能改进,这种方式解决了CGI中存在的问题,它的执行情况如下:

在上面的过程中与CGI有明显的不同在于:

  • 针对客户端的多个Http请求,FastCGI程序只有一个进程来处理CGI请求,而且处理完该请求之后也不会销毁,而是作为守护进程继续运行,这样可以在它的生命周期中处理多个请求,这样避免了高并发时服务器资源的耗尽问题;

  • 而且针对CGI中Web Server和CGI程序必须在同一个主机上的问题,FastCGI也进行了改进,允许FastCGI程序运行在不同的主机之上,而Web Server和FastCGI之间通过TCP连接进行通信,这种方式可以使Web Server和FastCGI程序独立配置和启动,也可以通过负载均衡提高系统的伸缩性和扩展性,当然两者仍然也可以同时部署在同一个主机上;

下图就是nginx中配置的FastCGI程序的运行:

参考

  1. Accessing the Standard CGI Variables

  2. Servlets vs. CGI

  3. 通过Servlet和CGI协议深入理解web数据传输

  4. Web开发技术发展历史

  5. Difference Between CGI and Servlet