FastCGI 简介
FastCGI是Web服务器和处理程序之间通信的一种协议,是CGI的一种改进方案,FastCGI像是一个常驻(long-lived)型的CGI,它可以一直执行,在请求到达时不会花费时间去fork一个进程来处理(这是CGI最为人诟病的fork-and-execute模式)。正是因为他只是一个通信协议,它还支持分布式的运算,所以 FastCGI 程序可以在网站服务器以外的主机上执行,并且可以接受来自其它网站服务器的请求。
FastCGI 是与语言无关的、可伸缩架构的 CGI 开放扩展,将 CGI 解释器进程保持在内存中,以此获得较高的性能。CGI 程序反复加载是 CGI 性能低下的主要原因,如果 CGI 程序保持在内存中并接受 FastCGI 进程管理器调度,则可以提供良好的性能、伸缩性、Fail-Over 特性等。
FastCGI 工作流程如下:
- FastCGI 进程管理器自身初始化,启动多个 CGI 解释器进程,并等待来自 Web Server 的连接。
- Web 服务器与 FastCGI 进程管理器进行 Socket 通信,通过 FastCGI 协议发送 CGI 环境变量和标准输入数据给 CGI 解释器进程。
- CGI 解释器进程完成处理后将标准输出和错误信息从同一连接返回 Web Server。
- CGI 解释器进程接着等待并处理来自 Web Server 的下一个连接。
图2.8 FastCGI 运行原理示举例示意图
FastCGI 与传统 CGI 模式的区别之一则是 Web 服务器不是直接执行 CGI 程序了,而是通过 Socket 与 FastCGI 响应器(FastCGI 进程管理器)进行交互,也正是由于 FastCGI 进程管理器是基于 Socket 通信的,所以也是分布式的,Web 服务器可以和 CGI 响应器服务器分开部署。Web 服务器需要将数据 CGI/1.1 的规范封装在遵循 FastCGI 协议包中发送给 FastCGI 响应器程序。
FastCGI 协议
可能上面的内容理解起来还是很抽象,这是由于第一对FastCGI协议还没有一个大概的认识,第二没有实际代码的学习。所以需要预先学习下 FastCGI 协议,不一定需要完全看懂,可大致了解之后,看完本篇再结合着学习理解消化。
下面结合 PHP 的 FastCGI 的代码进行分析,不作特殊说明以下代码均来自于 PHP 源码。
FastCGI 消息类型
FastCGI 将传输的消息做了很多类型的划分,其结构体定义如下:
typedef enum _fcgi_request_type {
FCGI_BEGIN_REQUEST = 1, /* [in] */
FCGI_ABORT_REQUEST = 2, /* [in] (not supported) */
FCGI_END_REQUEST = 3, /* [out] */
FCGI_PARAMS = 4, /* [in] environment variables */
FCGI_STDIN = 5, /* [in] post data */
FCGI_STDOUT = 6, /* [out] response */
FCGI_STDERR = 7, /* [out] errors */
FCGI_DATA = 8, /* [in] filter data (not supported) */
FCGI_GET_VALUES = 9, /* [in] */
FCGI_GET_VALUES_RESULT = 10 /* [out] */
} fcgi_request_type;
下图是一个比较常见消息传递流程
图2.9 FastCGI 消息传递流程示意图
最先发送的是FCGI_BEGIN_REQUEST
,然后是FCGI_PARAMS
和FCGI_STDIN
,由于每个消息头(下面将详细说明)里面能够承载的最大长度是65535,所以这两种类型的消息不一定只发送一次,有可能连续发送多次。
FastCGI 响应体处理完毕之后,将发送FCGI_STDOUT
、FCGI_STDERR
,同理也可能多次连续发送。最后以FCGI_END_REQUEST
表示请求的结束。需要注意的一点,FCGI_BEGIN_REQUEST
和FCGI_END_REQUEST
分别标识着请求的开始和结束,与整个协议息息相关,所以他们的消息体的内容也是协议的一部分,因此也会有相应的结构体与之对应(后面会详细说明)。而环境变量、标准输入、标准输出、错误输出,这些都是业务相关,与协议无关,所以他们的消息体的内容则无结构体对应。
由于整个消息是二进制连续传递的,所以必须定义一个统一的结构的消息头,这样以便读取每个消息的消息体,方便消息的切割。这在网络通讯中是非常常见的一种手段。
FastCGI 消息头
如上,FastCGI 消息分10种消息类型,有的是输入有的是输出。而所有的消息都以一个消息头开始。其结构体定义如下:
typedef 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;
} fcgi_header;
字段解释下:
version
标识FastCGI协议版本。type
标识FastCGI记录类型,也就是记录执行的一般职能。requestId
标识记录所属的FastCGI请求。contentLength
记录的contentData组件的字节数。
关于上面的xxB1
和xxB0
的协议说明:当两个相邻的结构组件除了后缀“B1”和“B0”之外命名相同时,它表示这两个组件可视为估值为B1<<8 + B0
的单个数字。该单个数字的名字是这些组件减去后缀的名字。这个约定归纳了一个由超过两个字节表示的数字的处理方式。
比如协议头中requestId
和contentLength
表示的最大值就是 65535。
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int main()
{
unsigned char requestIdB1 = UCHAR_MAX;
unsigned char requestIdB0 = UCHAR_MAX;
printf("%d\n", (requestIdB1 << 8) + requestIdB0); // 65535
}
你可能会想到如果一个消息体长度超过65535怎么办,则分割为多个相同类型的消息发送即可。
FCGI_BEGIN_REQUEST 的定义
typedef struct _fcgi_begin_request {
unsigned char roleB1;
unsigned char roleB0;
unsigned char flags;
unsigned char reserved[5];
} fcgi_begin_request;
字段解释:role
表示Web服务器期望应用扮演的角色。分为三个角色(而我们这里讨论的情况一般都是响应器角色)
typedef enum _fcgi_role {
FCGI_RESPONDER = 1,
FCGI_AUTHORIZER = 2,
FCGI_FILTER = 3
} fcgi_role;
而FCGI_BEGIN_REQUEST
中的flags
组件包含一个控制线路关闭的位:flags & FCGI_KEEP_CONN
:如果为0,则应用在对本次请求响应后关闭线路。如果非0,应用在对本次请求响应后不会关闭线路;Web服务器为线路保持响应性。
FCGI_END_REQUEST 的定义
typedef struct _fcgi_end_request {
unsigned char appStatusB3;
unsigned char appStatusB2;
unsigned char appStatusB1;
unsigned char appStatusB0;
unsigned char protocolStatus;
unsigned char reserved[3];
} fcgi_end_request;
字段解释:
appStatus
组件是应用级别的状态码。protocolStatus
组件是协议级别的状态码;protocolStatus
的值可能是:
- FCGI_REQUEST_COMPLETE:请求的正常结束。
- FCGI_CANT_MPX_CONN:拒绝新请求。这发生在Web服务器通过一条线路向应用发送并发的请求时,后者被设计为每条线路每次处理一个请求。
- FCGI_OVERLOADED:拒绝新请求。这发生在应用用完某些资源时,例如数据库连接。
- FCGI_UNKNOWN_ROLE:拒绝新请求。这发生在Web服务器指定了一个应用不能识别的角色时。
protocolStatus
在 PHP 中的定义如下
typedef enum _fcgi_protocol_status {
FCGI_REQUEST_COMPLETE = 0,
FCGI_CANT_MPX_CONN = 1,
FCGI_OVERLOADED = 2,
FCGI_UNKNOWN_ROLE = 3
} dcgi_protocol_status;
需要注意dcgi_protocol_status
和fcgi_role
各个元素的值都是 FastCGI 协议里定义好的,而非 PHP 自定义的。
消息通讯样例
为了简单的表示,消息头只显示消息的类型和消息的 id,其他字段都不予以显示。而一行表示一个数据包。下面的例子来自于官网
- {FCGI_BEGIN_REQUEST, 1, {FCGI_RESPONDER, 0}}
- {FCGI_PARAMS, 1, “\013\002SERVER_PORT80\013\016SERVER_ADDR199.170.183.42 … “}
- {FCGI_STDIN, 1, “quantity=100&item=3047936”}
- {FCGI_STDOUT, 1, “Content-type: text/html\r\n\r\n<html>\n<head> … “}
- {FCGI_END_REQUEST, 1, {0, FCGI_REQUEST_COMPLETE}}
配合上面各个结构体,则可以大致想到 FastCGI 响应器的解析和响应流程:首先读取消息头,得到其类型为FCGI_BEGIN_REQUEST
,然后解析其消息体,得知其需要的角色就是FCGI_RESPONDER
,flag
为0,表示请求结束后关闭线路。然后解析第二段消息,得知其消息类型为FCGI_PARAMS
,然后直接将消息体里的内容以回车符切割后存入环境变量。与之类似,处理完毕之后,则返回了FCGI_STDOUT
消息体和FCGI_END_REQUEST
消息体供 Web 服务器解析。
今天的文章fastcgi协议详解_fddi协议分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/72138.html