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处理流程
- web服务器收到客户端(浏览器)的请求Http Request,启动CGI程序,并通过环境变量、标准输入传递数据
- CGI进程启动解析器、加载配置(如业务相关配置)、连接其它服务器(如数据库服务器)、逻辑处理等
- CGI进程将处理结果通过标准输出、标准错误,传递给web服务器
- 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_ADDR | client端的host名称 |
REMOTE_HOST | client端的IP位址 |
REMOTE_USER | client端送出来的使用者名称 |
REMOTE_METHOD | client端发出请求的方法(如get、post) |
SCRIPT_NAME | CGI程序所在的虚拟路径,如/cgi-bin/echo |
SERVER_NAME | server的host名称或IP地址 |
SERVER_PORT | 收到request的server端口 |
SERVER_PROTOCOL | 所使用的通讯协定和版本编号 |
SERVER_SOFTWARE | server程序的名称和版本 |
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
- 客户端给服务器发送http请求, 要求登陆
- 服务器接收客户端请求, 服务器无法处理登陆操作
- 服务器要处理请求, 先创建一个子进程, 目的的帮助服务器解析服务器无法解析的数据
- Cgi程序是由程序员写好之后提供给web服务器的
- cgi程序先进行初始化
- 连接数据库
- web服务器将要处理的数据发送给cgi程序
- cgi程序进行数据库查询
- 将查询结果发送给web服务器
- 服务器将CGI进程销毁
- web服务器将结果发送给客户端
- 使用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处理流程
- Web 服务器启动时载入初始化FastCGI执行环境。 例如IIS、ISAPI、apache mod_fastcgi、nginx ngx_http_fastcgi_module、lighttpd mod_fastcgi。
- FastCGI进程管理器自身初始化,启动多个CGI解释器进程并等待来自Web服务器的连接。启动FastCGI进程时,可以配置以ip和UNIX 域socket两种方式启动。
- 当客户端请求到达Web 服务器时, Web 服务器将请求采用socket方式转发给FastCGI主进程,FastCGI主进程选择并连接到一个CGI解释器。Web 服务器将CGI环境变量和标准输入发送到FastCGI子进程。
- FastCGI子进程完成处理后将标准输出和错误信息从同一socket连接返回Web 服务器。当FastCGI子进程关闭连接时,请求便处理完成。
- FastCGI子进程接着等待并处理来自Web 服务器的下一个连接。
fastcgi执行流程
客户端向服务器端发送登录请求
服务器收到登录请求无法处理, 服务器端启动fastCGI进程管理器
在fastCGI进管理器中管理了一个CGI程序
- 进入循环的意思?
- 循环处理服务器发送的请求
- 如果服务器没有发送请求消息, 循环阻塞
fastCGI处理完毕之后, 将结果发送给web服务器
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_INTERFACE | cgi 版本 |
SERVER_SOFTWARE | nginx 版本号,可修改、隐藏 |
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进程
- 需要指定监听的端口和IP地址, 并对其绑定
- 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
步骤:
- 使用fcgi库编写FastCGI程序(上边的例子)。编译成可执行文件execute
- 执行
spawn-fcgi -a 127.0.0.1 -p 55555-f execute
启动execute程序。 - 在nginx配置文件中增加如下配置后重启nginx(nginx -s reload)
location /fast/
{
include fastcgi_params;
fastcgi_pass 127.0.0.1:55555;
}
- 在浏览器上访问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)
循环内写业务 -
用
getenv
和fread(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
-
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
- 编写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头, 对处理结果的数据格式进行描述
}
- 利用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>
- 本地主机访问虚拟机,运行正常
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程序
- 点击注册 (跳到浏览器,并访问nginx的register.html网页)
- 跳到浏览器 ,输入待注册的用户名和密码,提交(测试注册)
(此时nginx的register_fastcgi负责处理)
- 测试登录
(此时nginx的login_fastcgi负责处理)