fastcgi
nginx和php-fpm是通过fastcgi协议进行通信的,php-fpm本身就是个socket服务端,nginx拿到url后,识别出.php结尾的地址后,会传给php-fpm,php-fpm通过预先启动的进程,调用php解释器
fastcgi消息有10种,其中有
fcgi_begin_request
fcgi_end_request
fcgi_params //环境变量,QUERY_STRING get后面的数据
fcgi_stdin //POST数据
fcgi_stdout //php-fpm返回给nginx的数据
fcgi_params key value格式 name长度value长度namevalue
长度小于128,使用一个字节
长度大于等于128,使用四个字节
首字节的第1位
0表示小于128 所以一个字节能表示最大的数字为2的7次方-1=127
1表示大于等于128, 四个字节能表示的最大的数字为2的31次方-1
例如name为QUERY_STRING
value为name=taek&age=20
那么实际内容为1216QUERY_STRINGname=taek&age=20
QUERY_STRING的长度为12,12小于128,所以一个字节就能装下了
12的类型为int,其2进制为
00000000 00000000 00000000 00001100
将12强制转为char类型, 就是00001100
int nameLen=32960;
char *name="abcdefg....";
int valueLen=3;
char *value="abc";
name的长度为32960, 那么就需要4个字节来装入,value的长度为3,一个字节就能装入
分配内存 char *p=(char*)malloc(4+1+128+3);
一.保存nameLen
32960的二进制为
第一个字节 第二个字节 第三个字节 第四个字节
00000000 00000000 10000000 11000000
1)先右移24位再与0x80做或运算,将最后一个字节的首位置1,并强转为char类型
00000000 00000000 00000000 00000000
即*p++ = (char) ( (nameLen>> 24) | 0x80) 保存的是10000000
2)再右移16位,强转为char
00000000 00000000 00000000 00000000
即*p++= (char )( nameLen>> 16) 保存00000000
3)再右移8位,强转为char类型
00000000 00000000 00000000 10000000
即 *p++=(char)(nameLen >>8)
4) 直接转为char
11000000
即 *p++=(char) nameLen;
p[0] 到p[3]为
10000000 00000000 10000000 11000000
二.保存valueLen
3的二进制为 00000000 00000000 00000000 00000011
直接强转为char
*p++ = (char) valueLen;
三.保存 name
while(*name!='\0'){
*p++=*name++;
}
四。保存value
while(*value!='\0'){
*p++=*value++;
}
取数据时, 发现p[0]为10000000,大于等于128, 说明占用四个字节,
int result =0;
result = p[0] 即 00000000 00000000 00000000 10000000
第一个字节为 result = p[0] << 24 & 0x7f
左移24位后为
100000000 00000000 00000000 00000000
再与0x7f做与运算 结果为
00000000 00000000 00000000 00000000
第二个字节为 *result |= p[1] << 16
左移16位后为
00000000 00000000 00000000 00000000
第三个字节为 00000000 00000000 00000000 10000000 左移8位结果为
00000000 00000000 100000000 00000000
*result |= p[2] << 8
第四个字节为*result |= p[3]
00000000 00000000 00000000 11000000
这四个值做或运算 即为
00000000 0000000 10000000 11000000
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;
消息头
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组件的字节数。可理解为消息体数据的长度,用两个字节表示,高字节放低地址,低字节放高地址,故每次发送的消息体长度最大不能超过2的16次方-1 = 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_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 服务器解析。
参考 https://yq.aliyun.com/articles/58999
http://redfoxli.github.io/fast-cgi-protocol.html 更细致