最早的Web服务器简单地响应浏览器发来的HTTP请求,并将存储在服务器上的HTML文件返回给浏览器,也就是静态html。随着互联网的不断发展,网站也越来越复杂,所以出现动态技术。但是服务器并不能直接运行 php,asp这样的文件,自己不能做,那就将任务外包给别人,但是要与第三做个约定,我给你发什么,你就得给我回什么,就是我把请求参数发送给你,然后我接收你的处理结果给客户端。那这个约定就是 common gateway interface,简称CGI。这个协议可以用vb,c,php,python 来实现。通常我们的CGI 程序是由服务器进程fork产生子进程,将任务直接抛给子进程做,然后处理完成后
子进程将结果返回给客户端。这样意味着每来一个请求动态网页的连接,就得专门为其开一个进程来处理,这样在并发量小的情况下,倒是可以接受,但是在并发量比较多的时候,计算机肯定是顶不住的。所以我们实现了CGI的增强版本。
快速通用网关接口(Fast Common Gateway Interface/FastCGI)是一种让交互程序与Web服务器通信的协议。FastCGI是早期通用网关接口(CGI)的增强版本。
FastCGI致力于减少网页服务器与CGI程序之间交互的开销,从而使服务器可以同时处理更多的网页请求。
与为每个请求创建一个新的进程不同,FastCGI使用持续的进程来处理一连串的请求。这些进程由FastCGI服务器管理,而不是web服务器。 当进来一个动态请求时,web服务器把环境变量和这个页面请求通过一个socket比如FastCGI进程与web服务器(都位于本地)或者一个TCP connection(FastCGI进程在远端的server farm)传递给FastCGI进程。然后处理完结果又返回给服务器,服务器再返回给客户端。交互流程如下:
一般FastCgi程序的服务进程是php-fpm服务进程,所以首先得安装php-fpm服务器,Ubuntu安装步骤很简单:
sudo apt-get install php-fpm
然后进入到配置文件,我的系统ubuntu18.10
sudo vim /etc/php/[版本号]/fpm/pool.d/www.conf
找到如下行,按照以下方式修改,也就是指定一个监听的IP+端口:
然后退出,制定php-fpm版本号,我的执行命令是php-fpm7.2。
我的测试是否成功:
telnet 127.0.0.1 9000
能连上表名成功安装。
安装成功了,下面我们来分析这个协议是如何工作的,并加以实践。
- FCGI_BEGIN_REQUEST
Web服务器与FastCgi可以通过网络套接字进行通信,也可以通过Unix域套接字进行通信。
先看消息头:
struct FCGI_Header {
unsigned char version;
unsigned char type;
unsigned char requestIdB1;
unsigned char requestIdB0;
unsigned char contentLengthB1;
unsigned char contentLengthB0;
unsigned char paddingLength;
unsigned char reserved;
};
上面命名见名知意,不解释是啥了,下面分析其作用吧。
version 一般设置为版本为1,type表示数据包类型。值的空间:
enum FCGI_Type {
// (WEBServer->FastCGI) 表示一次请求的开始
FCGI_BEGIN_REQUEST = 1,
// (WEBServer->FastCGI) 表示终止一次请求
FCGI_ABORT_REQUEST = 2,
// (FastCGI->WEBServer) 请求已被处理完毕
FCGI_END_REQUEST = 3,
// (WEBServer->FastCGI) 表示一个向CGI程序传递的环境变量
FCGI_PARAMS = 4,
// (WEB->FastCGI) 表示向CGI程序传递的标准输入
FCGI_STDIN = 5,
// (FastCGI->WEB) 表示CGI程序的标准输出
FCGI_STDOUT = 6,
// (FastCGI->WEBServer) 表示CGI程序的标准错误输出
FCGI_STDERR = 7,
// (WEBServer->FastCGI) 向CGI程序传递的额外数据
FCGI_DATA = 8,
// (WEB->FastCGI) 向FastCGI程序询问一些环境变量
FCGI_GET_VALUES = 9,
// (FastCGI->WEB) 询问环境变量的结果
FCGI_GET_VALUES_RESULT = 10,
// 未知类型,可能用作拓展
FCGI_UNKNOWN_TYPE = 11
};
requestIdB0和requestIdB1是合起来表示本次请求的编号,其中requestIdB1是请求标号的高八位,requestIdB0是请求的低八位,这个字段的存在允许Web服务器在一次连接中向FastCgi服务器发送多个不同的请求,只要使用不同编号的请求就行。然后contentLengthB1, contentLengthB0和起来表示消息头后仍有多少个字节的数据,contentLengthB1表示其高八位,B2表示其第八位,总共和起来数据字节大小刚好在0~65535之间,如果数据包太大超过65535就需要分多个数据包传输,第七个字节为填充长度,保证字节对齐。第八个字节为保留字段。目前还没用到过。下面是构造头的过程:
FCGI_Header FastCgi::makeHeader(int type,int requestId,int contentLength,int paddingLength)
{
FCGI_Header header;
header.version = FCGI_VERSION_1;
header.type = (unsigned char)type;
header.requestIdB1 = (unsigned char)((requestId >> 8) & 0xff); //用两个字段保存请求ID
header.requestIdB0 = (unsigned char)(requestId & 0xff);
header.contentLengthB1 = (unsigned char)((contentLength >> 8) & 0xff);//用两个字段保存内容长度
header.contentLengthB0 = (unsigned char)(contentLength & 0xff);
header.paddingLength = (unsigned char)paddingLength; //填充字节的长度
header.reserved = 0; //保留字节赋为0
return header;
}
消息体:
typedef struct
{
unsigned char roleB1; //web服务器所期望php-fpm扮演的角色,具体取值下面有
unsigned char roleB0;
unsigned char flags; //确定php-fpm处理完一次请求之后是否关闭
unsigned char reserved[5]; //保留字段
}FCGI_BeginRequestBody;
其中roleB0和roleB合起来表示web服务器请求CGI程序在程序中担当的角色。
具体取值:
enum FCGI_Role {
FCGI_RESPONDER = 1, // 响应器
FCGI_AUTHORIZER = 2, // 认证器
FCGI_FILTER = 3 // 过滤器
};
一般FastCgi程序做的就是响应器。第三个字节表示的是web服务器想让FastCgi处理完请求后的行为,比如发完请求关闭连接还是保持连接,1为不关闭,否则为关闭。其他字节还用不到。
- FCGI_END_REQUEST
消息题还是固定的八字节结构体,因此该类型的消息头中表示数据长度的字段也应当是固定的。格式被定义为
struct FCGI_EndRequestBody {
unsigned char appStatusB3;
unsigned char appStatusB2;
unsigned char appStatusB1;
unsigned char appStatusB0;
unsigned char protocolStatus;
unsigned char reserved[3];
};
前四字节表示CGI程序的退出状态,与之前的相同,此处是一个网络字节序,需要手动转换,第五子节表示FastCGI协议的状态码,取值如下:
enum FCGI_ProtocolStatus {
FCGI_REQUEST_COMPLETE = 0, // 请求正常完成
FCGI_CANT_MPX_CONN = 1, // FastCGI服务器不支持并发处理,请求已被拒绝
FCGI_OVERLOADED = 2, // FastCGI服务器耗尽了资源或达到限制,请求已被拒绝
FCGI_UNKNOWN_ROLE = 3 // FastCGI不支持指定的role,请求已被拒绝
};
后面的字节尚无作用。
- FCGI_PARAMS
消息体格式:
struct FCGI_ParamsBody {
unsigned char nameLengthB3;
unsigned char nameLengthB2;
unsigned char nameLengthB1;
unsigned char nameLengthB0;
unsigned char valueLengthB3;
unsigned char valueLengthB2;
unsigned char valueLengthB1;
unsigned char valueLengthB0;
unsigned char nameData[NAME_LENGTH]; // NAME_LENGTH与前四字节所表示数字相同
unsigned char valueData[VALUE_LENGTH]; // VALUE_LENGTH与第五到八字节所表示的数字相同
};
消息体格式的前四字节表示传给FastCGI的环境参数名的长度,第五到第八字节的则表示环境值的长度,在这八字节之后,先跟环境参数名,再跟相应的环境值。
下面是Web服务器向FCGI程序发送GET请求的流程:
下面是示例的代码,感谢fork和star!?
代码