CGI,FastCGI,spawn-fcgi,nginx组合使用

fastCGI

1. CGI

1.1 简介

通用网关接口(Common Gateway Interface、CGI)描述了客户端和服务器程序之间传输数据的一种标准,可以让一个客户端,从网页浏览器向执行在网络服务器上的程序请求数据。

CGI独立于任何语言的,CGI 程序可以用任何脚本语言或者是完全独立编程语言实现,只要这个语言可以在这个系统上运行。Unix shell script、Python、 Ruby、PHP、 perl、Tcl、 C/C++和 Visual Basic 都可以用来编写 CGI 程序。

最初,CGI 是在 1993 年由美国国家超级电脑应用中心(NCSA)为 NCSA HTTPd Web 服务
器开发的。这个 Web 服务器使用了 UNIX shell 环境变量来保存从 Web 服务器传递出去的参数,然后生成一个运行 CGI 的独立的进程。

1.2 CGI处理流程

  1. web服务器收到客户端(浏览器)的请求Http Request,启动CGI程序,并通过环境变量、标准输入传递数据
  2. CGI进程启动解析器、加载配置(如业务相关配置)、连接其它服务器(如数据库服务器)、逻辑处理等
  3. CGI进程将处理结果通过标准输出、标准错误,传递给web服务器
  4. web服务器收到CGI返回的结果,构建Http Response返回给客户端,并杀死CGI进程

CGI 运行流程 图
在这里插入图片描述


web服务器与CGI通过环境变量、标准输入、标准输出、标准错误互相传递数据。在遇到用户连接请求:

  • 先要创建CGI子进程,然后CGI子进程处理请求,处理完事退出这个子进程:fork-and-execute
  • CGI方式是客户端有多少个请求,就开辟多少个子进程,每个子进程都需要启动自己的解释器、加载配置,连接其他服务器等初始化工作,这是CGI进程性能低下的主要原因。当用户请求非常多的时候,会占用大量的内存、cpu等资源,造成性能低下。

输入:读取环境变量、标准输入
输出:标准输出、标准错误

CGI使外部程序与Web服务器之间交互成为可能。CGI程序运行在独立的进程中,并对每个Web请求建立一个进程,这种方法非常容易实现,但效率很差,难以扩展。面对大量请求,进程的大量建立和消亡使操作系统性能大大下降。此外,由于地址空间无法共享,也限制了资源重用。

1.3 环境变量

CGI程序通过getenv()函数获取环境变量中的值

GET请求,它将数据打包放置在环境变量QUERY_STRING中,CGI从环境变量
QUERY_STRING中获取数据。

常见的环境变量如下表所示:

环境变量含义
AUTH_TYPE存取认证类型
CONTENT_LENGTH由标准输入传递给CGI程序的数据长度,以bytes或字元数来计算
CONTENT_TYPE请求的MIME类型
GATEWAY_INTERFACE服务器的CGI版本编号
HTTP_ACCEPT浏览器能直接接收的Content-types, 可以有HTTP Accept header定义
HTTP_USER_AGENT递交表单的浏览器的名称、版本和其他平台性的附加信息
HTTP_REFERER递交表单的文本的URL,不是所有的浏览器都发出这个信息,不要依赖它
PATH_INFO传递给CGI程序的路径信息
QUERY_STRING传递给CGI程序的请求参数,也就是用"?"隔开,添加在URL后面的字串
REMOTE_ADDRclient端的host名称
REMOTE_HOSTclient端的IP位址
REMOTE_USERclient端送出来的使用者名称
REMOTE_METHODclient端发出请求的方法(如get、post)
SCRIPT_NAMECGI程序所在的虚拟路径,如/cgi-bin/echo
SERVER_NAMEserver的host名称或IP地址
SERVER_PORT收到request的server端口
SERVER_PROTOCOL所使用的通讯协定和版本编号
SERVER_SOFTWAREserver程序的名称和版本

1.4 标准输入

环境变量的大小是有一定的限制的,当需要传送的数据量大时,储存环境变量的空间可能会不足,造成数据接收不完全,甚至无法执行CGI程序。

因此后来又发展出另外一种方法:POST,也就是利用I/O重新导向的技巧,让CGI程序可以由stdin和stdout直接跟浏览器沟通。

当我们指定用这种方法传递请求的数据时,web服务器收到数据后会先放在一块输入缓冲区
中,并且将数据的大小记录在CONTENT_LENGTH这个环境变量,然后调用CGI程序并将
CGI程序的stdin指向这块缓冲区,于是我们就可以很顺利的通过stdin和环境变数CONTENT_LENGTH得到所有的信息,再没有信息大小的限制了。

1.5 CGI程序结构

CGI程序的执行流程

CGI程序起始于 被Web程序拉起来
先读取环境变量(getenv(),获取HTTP请求头部分)
从标准输入读数据(fgets() fread() ,获取请求体)
数据处理 // 不同的CGI程序,只有这部分不一样(具体的业务处理)
向标准输出输出数据(用HTML格式组织好 printf()输出)
CGI程序退出

在这里插入图片描述
CGI程序的缺点:资源消耗大;某些初始化操作冗余;CGI进程会在服务器端被频繁的创建、销毁 ,降低了服务器端的处理效率



客户端通过浏览器向服务器发送一个登陆请求 http://localhost/login?user=zhang3&passwd=123456

  1. 客户端给服务器发送http请求, 要求登陆
  2. 服务器接收客户端请求, 服务器无法处理登陆操作
    • 服务器要处理请求, 先创建一个子进程, 目的的帮助服务器解析服务器无法解析的数据
      • Cgi程序是由程序员写好之后提供给web服务器的
    • cgi程序先进行初始化
      • 连接数据库
    • web服务器将要处理的数据发送给cgi程序
      • cgi程序进行数据库查询
      • 将查询结果发送给web服务器
      • 服务器将CGI进程销毁
    • web服务器将结果发送给客户端
  3. 使用CGI结论:
    • 如果使用CGI解析服务器要处理的动态数据
      • CGI进程会在服务器端被频繁的创建、销毁 ;降低了服务器端的处理效率

1.6 测试

CGI程序通过getenv()函数获取环境变量中的值


#include <stdio.h>
#include <stdlib.h>
int main()
{
    puts(getenv("APPLE"));//通过读取环境变量里的值,来获取收到的数据
    return 0;
}

[root@lwh testcpp]# APPLE=123456 ./execute # 我在运行这个程序之前直接设置环境变量(运行结束,这个环境变量就失效)
123456
[root@lwh testcpp]# APPLE=hhhh ./execute 
hhhh
[root@lwh testcpp]# ./execute 
Segmentation fault (core dumped)
[root@lwh testcpp]# 
#include <stdio.h>
#include <stdlib.h>
int main()
{
    char buff[128] = {0};
    fgets(buff, sizeof(buff), stdin); //从标准输入获取收到的数据
    puts(buff);

    puts(getenv("APPLE")); //通过读取环境变量里的值,来获取收到的数据
    return 0;
}



# echo abcd | ./execute 是通过‘|’把echo程序的标准输出的数据传递给了./execute的标准输入
# APPLE=jhjhjh          是临时设置这个环境变量的值
[root@lwh testcpp]# echo abcd | APPLE=jhjhjh ./execute 
abcd

jhjhjh
[root@lwh testcpp]# 

2. FastCGI

2.1 什么是FastCGI

快速通用网关接口(Fast Common Gateway Interface/FastCGI)是通用网关接口(CGI)的改进,描述了客户端和服务器程序之间传输数据的一种标准。

FastCGI致力于减少Web服务器与CGI程式之间互动的开销,从而使服务器可以同时处理更多的Web请求。与为每个请求创建一个新的进程不同,FastCGI使用持续的进程来处理一连串的请求。这些进程由FastCGI进程管理器管理,而不是web服务器。

CGI与FastCGI的关系:CGI 就是所谓的短生存期应用程序,FastCGI 就是所谓的长生存期应用程序。FastCGI像是一个常驻(longlive)型的CGI,它可以一直执行着,不会每次都要花费时间去fork一次

2.2 FastCGI处理流程

  1. Web 服务器启动时载入初始化FastCGI执行环境。 例如IIS、ISAPI、apache mod_fastcgi、nginx ngx_http_fastcgi_module、lighttpd mod_fastcgi。
  2. FastCGI进程管理器自身初始化,启动多个CGI解释器进程并等待来自Web服务器的连接。启动FastCGI进程时,可以配置以ip和UNIX 域socket两种方式启动。
  3. 当客户端请求到达Web 服务器时, Web 服务器将请求采用socket方式转发给FastCGI主进程,FastCGI主进程选择并连接到一个CGI解释器。Web 服务器将CGI环境变量和标准输入发送到FastCGI子进程。
  4. FastCGI子进程完成处理后将标准输出和错误信息从同一socket连接返回Web 服务器。当FastCGI子进程关闭连接时,请求便处理完成。
  5. FastCGI子进程接着等待并处理来自Web 服务器的下一个连接。

fastcgi执行流程
在这里插入图片描述

在这里插入图片描述

  1. 客户端向服务器端发送登录请求

  2. 服务器收到登录请求无法处理, 服务器端启动fastCGI进程管理器

  3. 在fastCGI进管理器中管理了一个CGI程序

    • 进入循环的意思?
      • 循环处理服务器发送的请求
      • 如果服务器没有发送请求消息, 循环阻塞
  4. fastCGI处理完毕之后, 将结果发送给web服务器

  5. web服务器将结果发送给客户端

由于FastCGI程序并不需要不断的产生新进程,可以大大降低服务器的压力并且产生较高的应用效率。它的速度效率最少要比CGI 技术提高 5 倍以上。它还支持分布式的部署,即FastCGI 程序可以在web 服务器以外的主机上执行。

CGI 是所谓的短生存期应用程序,FastCGI 是所谓的长生存期应用程序。FastCGI像是一个常驻(long-live)型的CGI,它可以一直执行着,不会每次都要花费时间去fork一次(这是CGI最为人诟病的fork-and-execute 模式)。

2.3 FastCGI程序结构

FastCGI程序流程图
在这里插入图片描述



2.4 fastCGI的环境变量 - fastcgi.conf

环境变量说明
SCRIPT_FILENAME脚本文件请求的路径
QUERY_STRING请求的参数;如?app=123
REQUEST_METHOD请求的动作(GET,POST)
CONTENT_TYPE请求头中的Content-Type字段
CONTENT_LENGTH请求头中的Content-length字段
SCRIPT_NAME脚本名称
REQUEST_URI请求的地址不带参数
DOCUMENT_URI与$uri相同
DOCUMENT_ROOT网站的根目录。在server配置中root指令中指定的值
SERVER_PROTOCOL请求使用的协议,通常是HTTP/1.0或HTTP/1.1
GATEWAY_INTERFACEcgi 版本
SERVER_SOFTWAREnginx 版本号,可修改、隐藏
REMOTE_ADDR客户端IP
REMOTE_PORT客户端端口
SERVER_ADDR服务器IP地址
SERVER_PORT服务器端口
SERVER_NAME服务器名,域名在server配置中指定的server_name


3. 组合使用nginx,fastcgi,spawn-fcgi

3.1 fastCGI和spawn-fcgi的安装

1.安装fastCGI

tar -zxvf fcgi-2.4.1-SNAP-0910052249.tar.gz 
ls
cd fcgi-2.4.1-SNAP-0910052249/
ls 
./configure      # --prefix=/usr  //安装到/usr目录下
make             # make -j 2 两个线程同时make,快一些
	# - error: fcgio.cpp:50:14: error: 'EOF' was not declared in this scope
	# - 解决方案: 添加声明 EOF 的头文件到fcgio.cpp 
	#		#include <stdio.h> ->   c 
	#		#include <cstdio>  -> C++
make             # 重新执行make
sudo make install
# make和执行时需要加载动态库:libfcgi.so 
[root@lwh lib]# pwd
/usr/local/lib
[root@lwh lib]# vim /etc/ld.so.conf # 更改一下配置 添加库路径
[root@lwh lib]# ldconfig            # 然后生效一下

2.安装spawn-fcgi

下载地址: http://redmine.lighttpd.net/projects/spawn-fcgi/wiki 安装

./configure       # --prefix=/usr  //安装到/usr目录下
make
sudo make install

[root@lwh spawn-fcgi-1.6.4]# whereis spawn-fcgi
spawn-fcgi: /usr/local/bin/spawn-fcgi


3.2 nginx,fastcgi,fastcgi进程管理器spawn-fcgi 三者之间的关系

nginx 不能像apache那样直接执行外部可执行程序,但nginx可以作为代理服务器,将请求转发给后端服务 器,这也是nginx的主要作用之一。其中nginx就支持FastCGI代理,接收客户端的请求,然后将请求转发给后 端fastcgi进程。下面介绍如何使用C/C++编写cgi/fastcgi,并部署到nginx中。

通过前面的介绍知道,fastcgi进程由FastCGI进程管理器管理,而不是nginx。这样就需要一个FastCGI管理, 管理我们编写fastcgi程序。我们使用spawn-fcgi作为FastCGI进程管理器。

spawn-fcgi是一个通用的FastCGI进程管理器,简单小巧,原先是属于lighttpd的一部分,后来由于使用比较 广泛,所以就迁移出来作为独立项目了。spawn-fcgi使用pre-fork 模型,功能主要是打开监听端口,绑定地 址,然后fork-and-exec创建我们编写的fastcgi应用程序进程,退出完成工作。fastcgi应用程序初始化,然后 进入死循环侦听socket的连接请求。

在这里插入图片描述

  • 进程管理器启动的时候
    • 会创建fastCGI进程
    • 因此: 进程管理器和fastCGI进程是父子关系
  • 进程管理器启动的时候
    • 需要指定监听的端口和IP地址, 并对其绑定
      • 因此:进程管理器可以接收nginx发送的数据
      • 然后进程管理器会将数据发送给fastCGI进程
  • Nginx处理不了的动态数据, 会转发, 转发给fastCGI进程
  • fastCGI进程处理Nginx发送的数据, 得到结果
  • fastCGI进程将结果发送给Nginx
  • Nginx将处理结果发送给客户端


3.3 演示1 nginx&&fastcgi

# 更改配置文件,添加fastCGI的支持
[root@lwh testcpp]# vim /usr/local/nginx/conf/nginx.conf
          location /fast/
          {
              include fastcgi_params;
              fastcgi_pass 192.168.140.1:55555;
          }
# 重新加载配置文件
[root@lwh conf]# nginx -s reload
# 在浏览器访问:192.168.184.134/fast/  
	# 192.168.184.134是虚拟机的IP nginx默认监听80端口
	# nginx找到 /fast/ 的location
	
# nginx收到了,转发到fastcgi_pass 192.168.140.1:55555;
	# 本机(WIndows)上的TCP socket工具设置了监听192.168.140.1:55555
	# nginx转发的数据到达后,就会显示到接收栏 

此时socket工具收到转发过来的数据(从浏览器封装成HTTP数据–>到nginx的web服务器–>转换成fastCGI格式数据–>转到本IP和Port)

fastCGI只启动了一个进程,通过socket接收和回复web服务器发送来的数据
数据是基于fastCGI标准编码过的
在这里插入图片描述

3.4 演示2:nginx,使用fcgi库编写程序,使用spawn-fcgi启动fcgi程序


由文章第1 部分和文章第二部分的配图 可以看出,fast-cgi程序和cgi程序的相似度很大,但又不完全相同。fcgi库的出现统一了两者。

fcgi是开发FastCGI程序常用的一个函数库:https://github.com/FastCGI-Archives/fcgi2.git

  • fcgi库把 socket数据收发 和 解析和封装FastCGI数据 封装成函数。方便开发者着眼于业务处理。
  • fcgi库在解析完FastCGI数据后会模拟CGI的规范,设置环境变量和重定向标准输入。(读取数据)
  • 利用fcgi编写的程序也可以当做cgi程序运行。(处理数据)
  • 运行完毕,把数据数据发送给进来时的socket(输出数据)(fastcgi程序把数据从标准输出 放到socket发送给nginx web服务器)
  • nginx web把数据发送给 来时的浏览器

这个fastcgi程序完成了一个返回客户端IP地址的功能。

/****
 * test_cgi.c
 * 测试刚刚安装的fastcgi
 * */
#include <stdio.h>
#include <stdlib.h>     //获取环境变量:环境变量是web收到后被fcgi库设置进去的
#include <fcgi_stdio.h> // fcgi的头文件都在 /usr/local/include/ 目录下
                        // fcgi的库文件都在 /usr/local/lib/ 目录下   -l fcgi
int main()
{
    while (FCGI_Accept() >= 0) //FCGI_Accept()内开个socket,等着的服务器过来
    {                          //while循环内,把自己当做是CGI程序来写代码即可
                               //while循环内,发送到标准输出的数据,被socket接收发给Nginx Web服务器
        printf("Content-Type:text\r\n\r\n");
        printf("clint ip is %s\r\n", getenv("REMOTE_ADDR"));
    }
    return 0;
}
测试:作为CGI程序已经OK了

[root@lwh testcpp]# make
[root@lwh testcpp]# REMOTE_ADDR=192.168.134.1 ./execute 
Content-Type:text

clint ip is 192.168.134.1
[root@lwh testcpp]# 
  • 上边的代码中并没有体现守护进程和socket收发数据,所以我们需要借助一个fastcgi程序的管理器帮助。
  • spawn-fcgi是一个通用的选择。
  • spawn-fcgi 帮助我们把fastcgi程序启动为守护进程并设置进去 监听IP和Port
  • 命令spawn-fcgi -a 127.0.0.1 -p 55555-f execute的意思是:按照守护模式启动execute程序,并且监听本地地址(127.0.0.1)的55555端口。

需求: 用户访问http://XXXXXXXXX/ip时,显示用户的IP
步骤:

  1. 使用fcgi库编写FastCGI程序(上边的例子)。编译成可执行文件execute
  2. 执行spawn-fcgi -a 127.0.0.1 -p 55555-f execute启动execute程序。
  3. 在nginx配置文件中增加如下配置后重启nginx(nginx -s reload)
          location /fast/
          {
              include fastcgi_params;
              fastcgi_pass 127.0.0.1:55555;
          }
  1. 在浏览器上访问nginx所在虚拟机的ip和nginx web监听的端口 192.168.184.134:80/fast/
    以下数据就是经过nginx中的fastcgi处理后 回复回来的
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

测试:fastcgi程序 作为守护进程和socket收发数据
[root@lwh testcpp]# spawn-fcgi -a 127.0.0.1 -p 55555 -f ./execute 
spawn-fcgi: child spawned successfully: PID: 17692


[root@lwh testcpp]# vim /usr/local/nginx/conf/nginx.conf
          location /fast/
          {
              include fastcgi_params;
              fastcgi_pass 127.0.0.1:55555;
          }
[root@lwh testcpp]# nginx  -s reload        

在浏览器上访问nginx所在虚拟机的ip和nginx web监听的端口 182.168.184.134:80/fast/
浏览器收到数据  clint ip is 192.168.184.1


3.5 fastcgi程序的开发技巧小结

  • 使用fcgi库时的三要素:

    • while (FCGI_Accept() >= 0)循环内写业务

    • getenvfread(buf, sizeof(buf), 1, stdin)获取用户的请求

    • printf向用户展示数据;数据格式是

      • 若干行回复数据头(最简形式Content-Type:text\r\n
      • 一个空行
      • 回复数据体
  • spawn-cgi启动fastcgi程序时要和nginx的fastcgi_pass配置项对应好

  • 良好的设计是:不同目的的请求用不同的FastCGI程序处理。



3.6 演示3:客户端(浏览器)向服务器发送了一个url:

http://www.baidu.com/login?user=zhang3&passwd=123456

  1. Nginx Web服务器, 处理不了动态数据, 但是可以将这些数据转发给其他的进程进行处理

    • 数据转发给程序员编写的fastCGI程序
    • 那么如何配置Nginx的数据转发
http://www.baidu.com/login?user=zhang3&passwd=123456
# 里边有用户提交的数据, nginx不能直接处理, 需要数据转发
# 需要添加对应的处理指令 - 也就是需要添加一个location

# location 指令
	# 指令: 去掉协议、ip/域名、端口号、尾部的文件名, ?和它后边的部分
	# 剩下的: /login

location /login
{
	# 处理数据转发, fastcgi进程
	fastcgi_pass IP:Port;
    include fastcgi.conf; # 包含一个配置文件, 里边是http相关的环境变量   /usr/local/nginx/conf/fastcgi.conf
}

# 说明
fastcgi_pass IP:Port
	# IP: 数据的接收者对应的主机的IP地址,
	# 如果是本机: localhost、127.0.0.1、192.168.1.101(本机IP)
	# 如果不是本机: 192.168.1.100
	# Port: 数据接收者监听的端口, 找一个空闲端口即可
include fastcgi.conf
	# fastcgi.conf, 和nginx.conf在同一级目录中
	# /usr/local/nginx/conf

#改完配置后要重新加载配置文件
nginx -s reload
  1. 编写fastCGI程序
一般情况下套接字通信时,是利用通信的文件描述符fd,然后read/recv、write/send
fastCGI程序中,直接利用getchar()/fread()putchar()/write(),对终端操作,fastcgi就可以和nginx通信
// 需要引用的头文件
#include "fcgi_config.h"
#include "fcgi_stdio.h"

// 函数: FCGI_Accept() - 阻塞函数
// 当fastcgi进程接收到数据之后, 该函数解除阻塞

// 根据环境变量名取出对应的值
char *getenv(const char *name); 


// 建议:每个fastCGI程序处理单一的功能
while(FCGI_Accept() >= 0)// 进入循环 进行逻辑处理
{
    // 1. 接收客户端发送的post数据,可以根据content-length长度接收数据
    size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
    	// fread(buf, 1, len, stdin);
        // ptr: 接收数据缓冲区地址
        // size: 接收的数据块的大小
        // nmemb: 要接收多少个数据块
        // stream: 文件指针
    
    // 1. 接收客户端发送的get数据
    // 通过环境变量 QUERY_STRING 将请求行的第二部分数据取出, ?后的一部分
    
    
    // 2. 逻辑处理 - 处理(登陆, 注册, 上传, 下载)
    // 3. 将处理结果发送给web服务器
    // -- 除了发送处理的结果, 还必须要组织一个content-type头, 对处理结果的数据格式进行描述
}

  1. 利用spawn-fcgi启动fastcgi程序
# shell命令
spawn-fcgi -a IP -p port -f fastcgi应用程序
	# IP port: 需要和nginx.conf总配置的fastcgi_pass中的IP Port对应
        # fastcgi_pass中是localhost 此处写127.0.0.1
        # fastcgi_pass中是127.0.0.1 此处写127.0.0.1
        # fastcgi_pass中是192.168.1.101 此处写192.168.1.101


4. 补充知识点

1. 客户端使用Post方式提交数据

  • Http协议规定 POST 提交的数据必须放在消息主体(entity-body)中,但协议并没有规定数据必 须使用什么编码方式。
  • 开发者完全可以自己决定消息主体的格式
  • 数据发送出去,还要服务端解析成功才有意义, 服务端通常是根据请求头(headers)中的 Content-Type 字段来获知请求中的消息主体是用何种方式编码,再对主体进行解析。
消息主体的常用格式
  • application/x-www-form-urlencoded
# 请求行
POST http://www.example.com HTTP/1.1
# 请求头
Content-Type: application/x-www-form-urlencoded;charset=utf-8
# 空行
# 请求数据(向服务器提交的数据)
title=test&user=kevin&passwd=32222
  • application/json
POST http://www.example.com HTTP/1.1
Content-Type: application/json;charset=utf-8
{"title":"test","sub":[1,2,3]}
  • text/xml
POST http://www.example.com HTTP/1.1
Content-Type: text/xml
<?xml version="1.0" encoding="utf8"?>
<methodcall>
    <methodname color="red">examples.getStateName</methodname>
    <params>
   		<value><i4>41</i4></value>
    </params>
</methodcall>

  • multipart/form-data
POST http://www.example.com HTTP/1.1
Content-Type: multipart/form-data
# 发送的数据
------WebKitFormBoundaryPpL3BfPQ4cHShsBz \r\n
Content-Disposition: form-data; name="file"; filename="qw.png"
Content-Type: image/png\r\n; md5="xxxxxxxxxx"
\r\n
.............文件内容................
.............文件内容................
------WebKitFormBoundaryPpL3BfPQ4cHShsBz--
Content-Disposition: form-data; name="file"; filename="qw.png"
Content-Type: image/png\r\n; md5="xxxxxxxxxx"
\r\n
.............文件内容................
.............文件内容................
------WebKitFormBoundaryPpL3BfPQ4cHShsBz--

2. strtol 函数

// 将数字类型的字符串 -> 整形数
long int strtol(const char *nptr, char **endptr, int base);
    // 参数nptr: 要转换的字符串 - 数字类型的字符串: "123", "0x12", "0776"
    // 参数endptr: 测试时候使用, 一般指定为NULL
    // 参数base: 进制的指定
        // 10 , nptr = "123456"
        // 8 , nptr = "0345"
        // 16, nptr = "0x1ff"
// 转换失败时,从出错位置开始打印        
char* p = "123abc";
char* ptErr = NULL;
strtol(p, &ptErr, 10);
	// 打印ptErr的值: "abc"

https://tool.oschina.net/

5. fastCGI复习

  • 是什么?
    • CGI - 公共网关接口, 一段运行在web服务器上的程序
  • 干什么?
    • 帮助web服务器处理用户提交的动态数据
  • 怎么干?
    • 需要程序员先安装fastCGI
    • 使用fastCGI提供的api进行编码 - 处理业务
while(FCGI_accept() >= 0)
{
	// 业务处理流程
}
  • fastCGI程序编写完成,如何启动?
    • 使用fastCGI管理器启动 - spawn-fcgi(需要安装)
spawn-fcgi -a IP -p 端口 -f fastCGI程序
# IP和端口意味着fastcgi程序可以接收该IP和Port对应的数据
  • fastCGI处理的数据是由nginx发送的, nginx如何发送数据的?
# 客户端向nginx发送了请求, 请求中有要处理的数据
http://www.baidu.com/login?username=tom&passwd=123
# 在nginx web服务器端需要根据该url将对应的处理指令提取出来
    # 去掉协议: http/https
    # 去掉域名/IP地址
    # 去掉尾部的文件名
    # 去掉剩余字符串中的?和其后边的部分
	# 最终得到的指令为: /login

http -> server -> 添加location
# 将要处理的数据转发 -> 对应的fastcgi进程
location /login
{
    fastcgi_pass IP:port; # fastCGI进程绑定的IP和端口
    include fastcgi.conf;
}

6 实战

1. 配置静态网页register.html

1.新建资源目录
/usr/local/nginx/login_server
2.
vim /usr/local/nginx/conf/nginx.conf
          location /
          {
              root  register; 
              index register.html;
          }
3. [root@lwh register]# vim register.html


<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="utf-8">
        <title>注册界面</title>
    </head>
    <body>
        <form action="reg/" method="post" accept-charset="UTF-8" enctype="text/plane">
         <input name="username" type="text" placeholder="用户名">
         <input name="password" type="password" placeholder="密码">
         <input type="submit" id="submit" value="提交">
    </body>

  1. 本地主机访问虚拟机,运行正常
    在这里插入图片描述


2. 配置动态网页(完成注册页面) register

register.html(同6.1)
<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="utf-8">
        <title>注册界面</title>
    </head>
    <body>
        <form action="reg/" method="post" accept-charset="UTF-8" enctype="text/plane">
         <input name="username" type="text" placeholder="用户名">
         <input name="password" type="password" placeholder="密码">
         <input type="submit" id="submit" value="提交">
    </body>
user_opt.h
#ifndef __USER_OPT_H__
#define __USER_OPT_H__
/****
 * 添加用户
 * 查询用户是否存在
*/

#include <stdio.h>

//-------------------添加用户-----------
void AddUser(const char *_name, const char *_password);

//-------------------查询用户是否存在-----------
//登录:登录时查询到就可以登录
//注册:不存在就可以注册
bool CheckUser(const char *_name, const char *_password_dgst);

// :vsplit user_opt.c
#endif
user_opt.cpp
#include "user_opt.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

//-------------------把用户和密码摘要 存到指定文件中-----------
void AddUser(const char *_name, const char *_password)
{
    char buff[1024] = {0};
    sprintf(buff, "./add_user.sh %s %s", _name, _password);
    system(buff);
}
/*
system()函数添加用户,通过调用脚本来实现
什么时候适合system()函数调用脚本: 不关心返回值时,一锤子买卖
*/


//-------------------查询用户是否存在-----------
//登录:登录时查询到就可以登录
//注册:不存在就可以注册
bool CheckUser(const char *_name, const char *_password_dgst)
{
    bool bRet = false;
    if (NULL == _password_dgst) //检测用户是否已经存在
    {
        if (fork() > 0)
        {
            int iStatus = 0; //记录子进程的退出码
            wait(&iStatus);
            if (0 == iStatus)
            {
                bRet = true; //用户已经存在
            }
        }
        else
        {
            execlp("./check_user.sh", "./check_user.sh", _name, NULL);
        }
    }
    return bRet;
}
/*
fork()+exec()形式调用脚本:关心脚本是否执行成功
*/
add_user.sh
#!/bin/bash

# 对不定长度的数据计算摘要,得出固定长度的签名
#本脚本 负责  把用户名和密码的摘要 存入指定文件

USERNAME=$1;
PASSWORD=$2;
PASSWORD=`echo -n ${PASSWORD} | openssl dgst -md5 -hex | awk '{print $NF}'`
echo "${USERNAME}:${PASSWORD}" >> userfile
check_user.sh
#!/bin/bash

# 检测user 是否存在
# -w 全字匹配
#  2>&1  > /dec/null 标准错误和标准输出,都重定向到 /dec/null
cat userfile | grep -w $1  2>&1  > /dev/null

# [root@lwh game]# ./check_user.sh abc
#[root@lwh game]# echo $?
#0 # 表示上条语句执行成功,即存在user=abc
#[root@lwh game]# ./check_user.sh abcddd
#[root@lwh game]# echo $?
#1 # 表示上条语句没有执行成功,即不存在user=abc
#[root@lwh game]# 
register_fcgi.cpp
// 处理注册请求
#include <string>
#include "user_opt.h"
#include <stdlib.h>
#include <fcgi_stdio.h> //fastcgi的头文件,其他放在头文件的最下面
using namespace std;
int main()
{
    while (FCGI_Accept() >= 0)
    {
        //获取数据
        char buff[1024] = {0};
        fread(buff, 1, sizeof(buff), stdin); //username=hello&password=12345678
        string orig_string(buff);

        //取出数据
        int and_pos = orig_string.find("&");
        string username = orig_string.substr(9, and_pos - 9);
        string password = orig_string.substr(and_pos + 10, orig_string.size() - and_pos - 10);

        //返回数据
        printf("Content-Type=text\r\n\r\n");
        if (true == CheckUser(username.c_str(), NULL))
        {
            // user exist
            printf("User Exist!\r\n");
        }
        else
        {
            AddUser(username.c_str(), password.c_str());
            // registr success
            printf("Register success!\r\n");
        }
    }

    return 0;
}
makefile
#---------------------------------------------------------
# 生成可执行文件 execute
PROJECT     = register_fastcgi
#---------------------------------------------------------
# .o文件
SrcSuf      = c
SrcSuf2     = cpp
SrcSuf3     = cc
ObjSuf      = o
LibSuf      = so
LibSuf2     = a
#---------------------------------------------------------

OBJFILES    =  ./user_opt.$(ObjSuf)       
OBJFILES    +=  ./register_fcgi.$(ObjSuf)       

#---------------------------------------------------------
# 头文件目录
INCLUDEPATH =  -I /usr/include/
INCLUDEPATH += -I /usr/local/include 


# 库目录
LIBPATH     =  -L /usr/local/lib/
LIBPATH     += -L /usr/lib/ 
#---------------------------------------------------------
CC          =   g++

# 编译选项
CFlag       =   $(INCLUDEPATH) -w -g -ggdb -fshort-wchar -std=c++11 -pthread

# 链接选项
LDFLAGS     +=  $(LIBPATH) 
LDFLAGS     +=  -l pthread          
#LDFLAGS     +=  -l hiredis
LDFLAGS     +=  -l fcgi    
#---------------------------------------------------------
.SUFFIXES: .$(SrcSuf) .$(ObjSuf) .$(LibSuf) .$(SrcSuf2) .$(LibSuf2) .$(SrcSuf3)

all:  $(PROJECT) clean
# 生成可执行文件
$(PROJECT):$(OBJFILES)
	@echo "creating $(PROJECT) start..."
	$(CC) $(LDFLAGS) $(OBJFILES) -o $(PROJECT) 
	@echo "creating $(PROJECT) end"
#---------------------------------------------------------
# .c 生成 .o 文件
.$(SrcSuf).$(ObjSuf):
	@echo "Compiling $(PROJECT) $<"
	$(CC) $(CFlag) -c $< -o $@ 
#---------------------------------------------------------
# .cpp 生成 .o 文件
.$(SrcSuf2).$(ObjSuf):
	@echo "Compiling $(PROJECT) $<"
	$(CXX) $(CFlag) -c $< -o $@
#---------------------------------------------------------
# .cc 生成 .o 文件
.$(SrcSuf3).$(ObjSuf):
	@echo "Compiling $(PROJECT) $<"
	$(CXX) $(CFlag) -c $< -o $@
#---------------------------------------------------------
# 删除 .o 文件
clean:
	@echo "Cleaning $(PROJECT) project files"
	@rm -f $(OBJFILES) core

#---------------------------------------------------------
# make cleanall 删除 .o 文件 execute文件
.PHONY:cleanobj
cleanobj:
	-rm -f $(OBJFILES) 
.PHONY:cleanexe
cleanexe:
	-rm -f $(PROJECT) 
.PHONY:cleanall
cleanall:cleanobj cleanexe
运行
脱离nginx运行本fastcgi程序

[root@lwh game]# echo 'username=hello&password=123456789' | ./register_fastcgi
Content-Type=text

Register success!
[root@lwh game]# echo 'username=hello&password=123456789' | ./register_fastcgi
Content-Type=text

User Exist!
[root@lwh game]# 

运行正常
结合nginx 运行本fastcgi程序 

启动fastcgi程序
[root@lwh game]# spawn-fcgi -a 127.0.0.1 -p 55555 -f ./register_fastcgi
spawn-fcgi: child spawned successfully: PID: 57820
[root@lwh game]# ps aux | grep register_fastcgi
root      57820  0.0  0.0  19076   900 ?        Ss   22:11   0:00 ./register_fastcgi
root      57841  0.0  0.0 110280   892 pts/0    S+   22:11   0:00 grep --color=auto register_fastcgi

改nginx的配置文件
[root@lwh game]# nginx 
[root@lwh conf]# vim nginx.conf
          location /
          {
              root  register; 
              index register.html;
          }
          location /reg/ 
          {
              include         fastcgi.conf;
              fastcgi_pass    127.0.0.1:55555;
          }
重新加载配置文件
[root@lwh conf]# nginx -s reload

运行

运行正常:见下图

访问192.168.184.134 (虚拟机地址)
在这里插入图片描述
输入 用户名 和 密码 点击 提交
在这里插入图片描述

再次提交数据
在这里插入图片描述
在这里插入图片描述

整体流程图!!!!!!

在这里插入图片描述

6.3 配置动态网页(完成登录页面) login

register.html(同6.1)
<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="utf-8">
        <title>注册界面</title>
    </head>
    <body>
        <form action="reg/" method="post" accept-charset="UTF-8" enctype="text/plane">
         <input name="username" type="text" placeholder="用户名">
         <input name="password" type="password" placeholder="密码">
         <input type="submit" id="submit" value="提交">
    </body>
user_opt.h
#ifndef __USER_OPT_H__
#define __USER_OPT_H__
/****
 * 查询用户是否存在
*/

#include <stdio.h>

//-------------------查询用户是否存在-----------
//登录:登录时查询到就可以登录
bool CheckUser(const char *_name, const char *_password_dgst);

// :vsplit user_opt.c
#endif
user_opt.cpp
#include "user_opt.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

//-------------------查询用户是否存在-----------
//登录:登录时查询到就可以登录
bool CheckUser(const char *_name, const char *_password_dgst)
{
    bool bRet = false;

    if (fork() > 0)
    {
        int iStatus = 0; //记录子进程的退出码
        wait(&iStatus);
        if (0 == iStatus)
        {
            bRet = true; //用户已经存在
        }
    }
    else
    {
        execlp("./check_user.sh", "./check_user.sh", _name, _password_dgst, NULL);
    }

    return bRet;
}
/*
fork()+exec()形式调用脚本:关心脚本是否执行成功
*/
check_user.sh
#!/bin/bash

# 检测 user 是否存在: 用户名和密码摘要要完全匹配!!!
# -w 全字匹配
#  2>&1  > /dec/null 标准错误和标准输出,都重定向到 /dec/null
if [ "$2" == "" ]
then
    cat ../game_reg_cgi/userfile | grep -w "$1"  2>&1  > /dev/null
else
    cat ../game_reg_cgi/userfile | grep -w "$1:$2"  2>&1  > /dev/null
fi


# [root@lwh game]# ./check_user.sh abc
# [root@lwh game]# echo $?
# 0   
# 表示上条语句执行成功,即存在user=abc

# [root@lwh game_login_cgi]# ./check_user.sh abc c4ca4238a0b923820dcc509a6f75849b
# [root@lwh game_login_cgi]# echo $? 
# 0 
# 用户存在且密码正确

# [root@lwh game_login_cgi]# ./check_user.sh abc c4ca4238a0b923820dcc509a6f75849
# [root@lwh game_login_cgi]# echo $? 
# 1 #密码不对

# [root@lwh game]# ./check_user.sh abcddd
# [root@lwh game]# echo $?
# 1 
# 表示上条语句没有执行成功,即不存在user=abc

login_fastcgi.cpp
// 处理登录请求
#include "user_opt.h"
#include <string>
#include <stdlib.h>
#include "CJsonObject.hpp"
using namespace std;

#include <fcgi_stdio.h>
int main()
{
    while (FCGI_Accept() >= 0)
    {
        string username;
        string password;

        //1.1 读取数据
        unsigned int json_length = atoi(getenv("CONTENT_LENGTH"));
        char *buf = (char *)calloc(1UL, json_length);
        fread(buf, 1, json_length, stdin);
        string json_string(buf);

        //1.2 取出json串中对应的值
        neb::CJsonObject json(json_string);
        json.Get("username", username);
        json.Get("password", password);

        //2.回复一个json串

        //构建回复的HTTP身体
        neb::CJsonObject reply;
        if (true == CheckUser(username.c_str(), password.c_str()))
        {
            //reply ok to client
            reply.Add("login_result", "OK");
        }
        else
        {
            //reply failed to client
            reply.Add("login_result", "Failed");
        }
        string reply_string = reply.ToFormattedString(); //转换为json格式的string

        //2.1 回复的HTTP头
        printf("Content-Type:application/json\r\n");
        printf("Content-Length:%d\r\n\r\n", reply_string.size());
        //2.2 回复的HTTP身体
        printf("%s\r\n", reply_string.c_str());

        free(buf);
    }
}

makefile
#---------------------------------------------------------
# 生成可执行文件 execute
PROJECT     = login_fastcgi
#---------------------------------------------------------
# .o文件
SrcSuf      = c
SrcSuf2     = cpp
SrcSuf3     = cc
ObjSuf      = o
LibSuf      = so
LibSuf2     = a
#---------------------------------------------------------
  
OBJFILES    +=  ./cJSON.$(ObjSuf)
OBJFILES    +=  ./CJsonObject.$(ObjSuf)    
OBJFILES    +=  ./login_fastcgi.$(ObjSuf)      
OBJFILES    +=  ./user_opt.$(ObjSuf)   

#---------------------------------------------------------
# 头文件目录
INCLUDEPATH =  -I /usr/include/
INCLUDEPATH += -I /usr/local/include 


# 库目录
LIBPATH     =  -L /usr/local/lib/
LIBPATH     += -L /usr/lib/ 
#---------------------------------------------------------
CC          =   g++

# 编译选项
CFlag       =   $(INCLUDEPATH) -w -g -ggdb -fshort-wchar -std=c++11 -pthread

# 链接选项
LDFLAGS     +=  $(LIBPATH) 
LDFLAGS     +=  -l pthread          
#LDFLAGS     +=  -l hiredis
LDFLAGS     +=  -l fcgi    
#---------------------------------------------------------
.SUFFIXES: .$(SrcSuf) .$(ObjSuf) .$(LibSuf) .$(SrcSuf2) .$(LibSuf2) .$(SrcSuf3)

all:  $(PROJECT) clean
# 生成可执行文件
$(PROJECT):$(OBJFILES)
	@echo "creating $(PROJECT) start..."
	$(CC) $(LDFLAGS) $(OBJFILES) -o $(PROJECT) 
	@echo "creating $(PROJECT) end"
#---------------------------------------------------------
# .c 生成 .o 文件
.$(SrcSuf).$(ObjSuf):
	@echo "Compiling $(PROJECT) $<"
	$(CC) $(CFlag) -c $< -o $@ 
#---------------------------------------------------------
# .cpp 生成 .o 文件
.$(SrcSuf2).$(ObjSuf):
	@echo "Compiling $(PROJECT) $<"
	$(CXX) $(CFlag) -c $< -o $@
#---------------------------------------------------------
# .cc 生成 .o 文件
.$(SrcSuf3).$(ObjSuf):
	@echo "Compiling $(PROJECT) $<"
	$(CXX) $(CFlag) -c $< -o $@
#---------------------------------------------------------
# 删除 .o 文件
clean:
	@echo "Cleaning $(PROJECT) project files"
	@rm -f $(OBJFILES) core

#---------------------------------------------------------
# make cleanall 删除 .o 文件 execute文件
.PHONY:cleanobj
cleanobj:
	-rm -f $(OBJFILES) 
.PHONY:cleanexe
cleanexe:
	-rm -f $(PROJECT) 
.PHONY:cleanall
cleanall:cleanobj cleanexe
运行
脱离nginx,在本机直接测试login_fastcgi程序

[root@lwh game_login_cgi]# echo '{"password": "c4ca4238a0b923820dcc509a6f75849b", "username": "abc"}' | CONTENT_LENGTH=67 ./login_fastcgi 
Content-Type:application/json
Content-Length:25
{
        "login_result": "OK"
}
[root@lwh game_login_cgi]# 

运行正常
结合nginx 运行register_fastcgi、login_fastcgi程序 

启动fastcgi程序
[root@lwh game_login_cgi]# spawn-fcgi -a 127.0.0.1 -p 55556 -f ./login_fastcgi
spawn-fcgi: child spawned successfully: PID: 112910

[root@lwh game_login_cgi]# nginx
[root@lwh game_login_cgi]# 

[root@lwh game_reg_cgi]# spawn-fcgi -a 127.0.0.1 -p 55555 -f ./register_fastcgi
spawn-fcgi: bind failed: Address already in use
[root@lwh game_reg_cgi]# ps aux|grep register_fastcgi
root      62310  0.0  0.0  19080   760 ?        Ss   02:38   0:00 ./register_fastcgi
root     115339  0.0  0.0 110284   900 pts/2    S+   16:44   0:00 grep --color=auto register_fastcgi
[root@lwh game_reg_cgi]# 


改nginx的配置文件
[root@lwh game]# nginx 
[root@lwh conf]# vim nginx.conf
          location /
          {
              root  register; 
              index register.html;
          }
          location /reg/ 
          {
              include         fastcgi.conf;
              fastcgi_pass    127.0.0.1:55555;
          }
		  
          location /login/
          {   
              include         fastcgi.conf;
              fastcgi_pass    127.0.0.1:55556;
          }
重新加载配置文件
[root@lwh conf]# nginx -s reload

运行

运行正常:见下图



测试:Qt窗口和nginx服务器之间json串传是否正常

192.168.184.134 (虚拟机地址)启动nginx和login_fastcgi程序
在Qt搭建的登录窗口中,输入 用户名 和 密码 点击 提交
nginx和login_fastcgi程序处理完毕回复给 Qt程序
Qt程序打印收到的json字符串 (login_fastci程序封装的HTTP数据身体)

在这里插入图片描述



测试:用户名或者密码错误时,弹出失败提示窗口

在这里插入图片描述



测试 点击注册按钮
Qt程序

  1. 点击注册 (跳到浏览器,并访问nginx的register.html网页)
    在这里插入图片描述
  2. 跳到浏览器 ,输入待注册的用户名和密码,提交(测试注册)
    (此时nginx的register_fastcgi负责处理)
    在这里插入图片描述
  3. 测试登录
    (此时nginx的login_fastcgi负责处理)
    在这里插入图片描述


  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值