一、Nginx作为web服务器处理请求
1.1 静态请求
客户端访问服务器的静态网页,不涉及任何数据的处理
1.2 动态请求
客户端会将数据提交给服务器,nginx处理不了,交给fastcgi进行处理,然后将数据返回给nginx。
二、HTTP协议复习
2.1 请求信息 - 客户端(浏览器)发送给服务器的数据格式
有四个部分:请求行,请求头,空行,请求数据
- 请求行:说明请求的类型,需要访问的资源,以及使用的HTTP版本
- 请求头:说明服务器需要使用的附加信息
- 空行:空行是必须要有的,即使没有请求数据
- 请求数据:也叫做主体,可以添加任意的其他数据
常见的请求方法有两种:get和post
Get方法提交数据:
Get方法很少有请求数据,因为Get方法将数据放在了请求行中
HTTP数据。Post方法提交数据:
Post方法基本都有请求数据,因为Post方法将数据放在了请求数据中
2.2 响应信息
也是有四部分:状态行,消息报头,空行,响应正文
- 状态行:包括了HTTP协议版本号,状态码,状态信息
- 消息报头:说明了客户端要使用的一些附加信息
- 空行:空行是必须要有的
- 响应正文:服务器返回给客户端的文本信息
2.3 HTTP状态码
状态代码有三位数字组成,第一个数字定义了响应的类别,一共分为五种类别:
- 1xx:指示信息 ------ 表示请求已经接收,继续处理
- 2xx:成功 ------ 表示请求已经被成功接收,理解,接受
- 3xx:重定向 ------ 要完成请求必须进行更进一步的操作
- 4xx:客户端错误 ------ 请求有语法错误或者请求无法实现
- 5xx:服务器端错误 ------ 服务器未能实现合法的请求
三、fastCGI
3.1 CGI
通用网关接口描述了客户端和服务器程序之间传输数据的一种标准,可以让一恶搞客户端从网页浏览器向执行在网络服务器上的程序请求数据。CGI独立于任何语言的,CGI程序可以使用任何脚本语言或者是完全独立的编程语言实现的,只要这个语言可以在这个系统上运行。
下面我们来看一看具体的流程:
- 用户通过浏览器访问服务器,发送了一个请求,请求的URL如上
- 服务器接收数据,对接收的数据进行解析
- nginx对于一些登录数据不知道如何处理,nginx将数据发送给CGI程序,服务器端会创建出一恶搞CGI进程
- CGI进程执行:加载配置,如果有需求加载配置文件获取数据,连接其他服务器,比如数据库,逻辑处理,得到结果,将结果发送给服务器;退出;
- 服务器将CGI处理结果发送给客户端
但是,这样有一个弊端:在服务器端CGI进程被频繁的创建和销毁,服务器开销大,效率低。
3.2 fastCGI
快速使用网关接口是通用网关接口的改进,描述了客户端和服务器端之间传输数据的一种标准。FastCGI致力于减少Web服务器与CGI进程之间互动的开销,从而使服务器可以同时处理更多的Web请求。与为每一个请求创建出一个新的进程不同,FastCGI使用持续的进程来处理一连串的请求。这些进程由Fast进程管理器管理,而不是Web服务器。
fastCGI和CGI的区别:
CGI就是所谓的短生存期应用程序,FastCGI就是所谓的长生存期应用程序,FastCGI就像是一个常驻型的CGI,他可以一直执行,不会每一次都要花费时间去fork一次。就好像长连接和短连接的区别。
下面,我们来看一看fastCGI请求的步骤:
- 用户通过浏览器访问服务器,发送了一个请求,请求的URL如上
- 服务器接收数据,对接收的数据进行解析
- nginx对于一些登录数据不知道如何处理,nginx将数据发送给了fastCGI程序,通过本地套接字和网络通信的方式
- fastCGI程序如何启动,不是有web服务器直接启动,而是通过一个fastCGI进程管理器启动
- fastCGI启动,会进行加载配置(可选),连接服务器(数据库),进行循环(服务器有请求进行处理,将处理结果发送给服务器(本地套接字,网络通信)),没有请求就会进行阻塞
- 服务器将fastCGI的处理结果发送给客户端
3.3 nginx && fastcgi
nginx不能像apache那样直接执行外部可执行程序,但是nginx可以作为代理服务器,将请求转发给后端服务器,这也是nginx的主要作用之一。其中nginx就支持FastCGI代理,接受客户端的请求然后将请求转发给后端fastCGI进程。
通过前面的介绍知道,fastCGI进程由FastCGI进程管理器管理的,而不是nginx。这样就需要一个FastCGI管理,管理我们编写的FastCGI程序,我们使用spawen-fcgi作为FastCGI进程管理器。
spawn-fcgi是一个通用的FastCGI进程管理器,简单小巧,原先是属于lighttpd的一部分,后来由于使用比较广泛,所以就迁移出来作为独立项目了。spawn-fcgi使用pre-fork模型,功能主要是打开监听端口,绑定地址,然后fork-and-exec创建我们编写的fastcgi应用程序进程,退出完成工作。fastCGI应用程序初始化,然后进入死循环侦听socket的连接请求。
根据查看给出的示例代码,我们可以发现是将标准输入和标准输出进行重定向。
比如说来一个HTTP请求:http://localhost/login?user=zhang3\&passwd=123456\&age=12\&sex=man
- 客户端访问,发送请求
- nginx web服务器无法处理用户提交的数据
- nginx需要将数据交给spawn-fcgi程序,这个spawn-fcgi是通信过程中的服务器角色,被动地接受数据,在spawn-fcgi启动的时候给其绑定IP和端口
- fastCGI程序是程序员写的可执行程序(login)使用spawn-fcgi进程管理器启动login程序,得到一个进程
3.3.1 nginx的数据转发
我们需要修改nginx的配置文件 nginx.conf,基本上使用nginx就需要修改配置文件。修改的配置文件如下所示:
cpp
// 通过请求的url http://localhost/login?user=zhang3&passwd=123456&age=12&sex=man
// 转换成为一个指令
// - 去掉指令
// - 去掉域名/IP + 端口
// - 如果尾部有文件名,需要去掉
// - 去掉 ?+ 后边的字符串
// - 剩下的就是服务器要处理的指令:/login
location /login
{
# 转发这个数据,fastCGI进程
fastcgi_pass 地址信息:端口;
# fastcgi.conf 和 nginx.conf在同一级目录: /usr/local/nginx/conf
# 这个文件中定义了一些http通信的时候用到的环境变量,nginx赋值的
include fastcgi.conf;
}
地址信息:
- localhost
- 127.0.0.1
- 192.168.1.100
端口:找到一个空闲的没有被占用的端口即可
3.3.2 swawn-fcgi如何启动
cpp
# 前提条件:我们的这个fastCGI程序已经编写完成了 - 可执行文件 login
spawn-fcgi -a IP地址 -p 端口 -f fastcgi可执行程序
- IP地址: 应该和nginx的 fast =cgi_pass 配置项对应
- nginx: localhost -> IP: 127.0.0.1
- nginx: 127.0.0.1 -> IP: 127.0.0.1
- nginx: 192.168.1.100 -> IP:192.168.1.100
- 端口:
应该和nginx的 fastcgi_pass 中的端口一致
3.3.3 fastCGI程序怎么写
cpp
// 需要包含的头文件
#include "fcgi_config.h" // 可选的
#include "fcgi_stdio.h" // 必须的,编译的时候找不到这个头文件
// 我们需要去找到这个头文件所在的目录
// 编写代码的流程
int main()
{
// FCGI_Accept()是一个阻塞函数,nginx给fastcgi程序发送数据的时候解除阻塞
while(FCGI_Accept() >= 0)
{
// 1. 接收数据
// 1.1 get方式提交数据 - 数据在请求行的第二部分
// user=zhang3&passwd=123456&age=12&sex=man
char *text = getenv("QUERY_STRING");
// 1.2 post方式提交数据
char *contentLength = getenv("CONTENT_LENGTH");
// 根据长度大小判断是否需要循环
// 2. 按照业务流程进行处理
// 3. 将处理结果发送给nginx
// 数据回发的时候,需要告诉nginx处理结果的格式 - 假设是html格式
printf("Content-type: text/html\r\n");
printf("<html>处理结果</html>");
}
}
四、相关的知识点
4.1 fastCGI的环境变量(在fastcgi.conf)
cpp
QUERY_STRING 请求的参数;如?app=123
REQUEST_METHOD 请求的动作(GET,POST)
CONTENT_TYPE 请求头中的Content-Type字段
CONTENT_LENGTH 请求头中的Content-length字段
4.2 客户端使用Post提交数据的常用方式
- HTTP协议规定POST提交的数据必须放在消息主体中,但是协议并没有规定数据必须使用什么编码方式
- 开发者完全可以自己决定消息主体的格式
- 数据发送出去,还要服务端解析成功才有意义,服务端通常是根据请求头中的Content-Type字段来获取请求中的消息主体是使用何种方式编码,再对主体进行解析
常用的四种方式:
介绍一下如何使用Content-Type。
4.3 sttrtol函数的使用
cpp
// 将数字类型的字符串 -> 整形数
long int strtol(const char* nptr, char **endptr, int base);
- 参数nptr:要转换的字符串 - 数字类型的字符串:"123" "0x12" "0776"
- 参数endptr:测试的时候使用,一般指定为NULL
- 参数base:进制的指定
- 10 , nptr = "123456", 如果是"0x12"就会出错
- 8 , nptr = "0345"
- 16, nptr = "0x1ff"
char* p = "123abc";
char* pt = NULL;
strtol(p, &pt, 10);
- 打印pt的值: "abc"