1   主题

主要是介绍fastcgi协议

2   cgi&&fastcgi&&php-fpm的关系

2.1   cgi

cgi 意思为 Common Gateway Interface(公共网关接口),它是一种规范,一种基于浏览器的输入、在Web服务器上运行的程序方法。
cgi只是一种协议,本身只能解析请求,返回结果,不会进程管理
php-cgi 是对cgi的一种实现,来处理具体的请求
官方文档:http://www.w3.org/CGI/

2.2   fastcgi

实现了cgi协议的程序对每个请求都会执行解析php.ini文件,初始化执行环境这些步骤,所以处理每个时间的时间会比较长。
作为cgi程序的优化,fastcgi会先启一个master,解析配置文件,初始化执行环境,然后再启动多个worker。
当请求过来时,master会传递给一个worker,然后立即可以接受下一个请求。这样就避免了重复的劳动。这就是fastcgi的对cgi进程的管理。

fastcgi也是一种协议,被设计用来支持常驻(long-lived)应用进程,是cgi的升级版
官方文档:http://www.fastcgi.com/devkit/doc/fcgi-spec.html (中文版可以参考 http://www.itcoder.me/?p=235 )

2.3   php-fpm

fastcgi虽然提高了性能,但是修改了php.ini配置文件后,没办法平滑重启,所以就诞生了php-fpm。
php-fpm是fastcgi的加强版,可以平滑重启cgi,限制cgi的运行时间,对cgi进行资源调度,其实就是类似windows上的任务管理器
在php 5.3.3 版本里加入php-fpm,之前的版本都是通过打补丁加入php-fpm的
官方文档:http://php-fpm.org/

2.4   总结

fastcgi是一种协议,对cgi协议的优化,php-fpm是fastcgi的一种实现,加强了对php-cgi的管理,php-cgi 就是cgi协议的实现,来处理具体的请求

3   fastcgi协议

3.1   与webserver的交互流程图

webserver                      php
    |                           |
    |     FCGI_BEGIN_REQUEST    |
    |-------------------------> |
    |                           |
    |       FCGI_PARAMS         |
    |-------------------------> |
    |                           |
    |       FCGI_STDIN          |
    |-------------------------> |
    |                           |
    |                           |
    |                           |
    |       FCGI_STDERR         |
    |<------------------------- |
    |                           |
    |                           |
    |       FCGI_STDOUT         |
    |<------------------------- |
    |                           |
    |                           |
    |     FCGI_END_REQUEST      |
    |<------------------------- |
    |                           |
    |                           |
    |                           |

具体的流程如下:

  1. 请求由webserver发起,发出一个包括FCGI_BEGIN_REQUEST标记的请求包,表示开始一个请求,在这个请求包里还可以定义是否使用长连接
  2. 再发送一个包含FCGI_PARAMS标记的请求包,FCGI_PARAMS表示请求的参数,可以是http的header,querystring,php中的$_SERVER里的变量,以及一个空的FCGI_PARAMS标记的请求包
  3. 再发送一个包含FCGI_STDIN标记的请求包,表示一个输入的开始,其实就是POST/PUT的内容。如果有发送内容,则需要再发一个空内容包含FCGI_STDIN标记的请求包。发送请求结束
  4. php处理完后,开始输出结果。有错误输出(FCGI_STDERR标示的包,不是必有的)和标准输出(FCGI_STDOUT标志的包)两种
  5. 最后php发送一个包含FCGI_END_REQUEST标记的请求包,表示这次请求结束

3.2   基本协议包结构

fastcgi协议是基于流的协议,8字节对齐,不用考虑字节序,但是需要考虑填充。一个包由固定8个字节的包头 + 数据body + 填充数据(如果数据的长度是8的整数倍,则不需要) 三个部分组成

包头如下(来自php-5.4.34 sapi/fpm/fpm/fastcgi.h)

typedef struct _fcgi_header {
    unsigned char version;          //表示版本,默认为1
    unsigned char type;             //表示请求包的类型,比如FCGI_BEGIN_REQUEST or FCGI_PARAMS or FCGI_STDIN 等等
    unsigned char requestIdB1;      //表示请求id,请求id是一个16位的整形,这里是高位
    unsigned char requestIdB0;      //表示请求id,请求id是一个16位的整形,这里是低位
    unsigned char contentLengthB1;  //表示body的长度,长度是一个16位的整形,这里是高位
    unsigned char contentLengthB0;  //表示body的长度,长度是一个16位的整形,这里是低位
    unsigned char paddingLength;    //表示填充内容的长度
    unsigned char reserved;         //保留字段,默认为0
} fcgi_header;

请求包的类型如下

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;

整个包的结构如下(来自官方文档:http://www.fastcgi.com/devkit/doc/fcgi-spec.html#S3.3)

typedef struct {
    unsigned char version;
    unsigned char type;
    unsigned char requestIdB1;
    unsigned char requestIdB0;
    unsigned char contentLengthB1;
    unsigned char contentLengthB0;
    unsigned char paddingLength;
    unsigned char reserved;
    unsigned char contentData[contentLength];
    unsigned char paddingData[paddingLength];
} FCGI_Record;

3.3   FCGI_BEGIN_REQUEST包

3.3.1   包头举例

FCGI_BEGIN_REQUEST类型的包头值为:

序列 0  1  2  3  4  5  6  7
数值 01 01 00 01 00 08 00 00

包头取值说明如下:

序列0(01)为version
序列1(01)为type,代表FCGI_BEGIN_REQUES,表示开始发送请求
序列2 3(00 01)代表2字节的请求id,默认取1即可
序列4 5(00 08) 代表2字节的body的长度,这里固定为8
序列6 (00) 代表1字节的填充数据的长度,这里固定为0
序列7 (00) 代表1字节的保留字段,固定为0

3.3.2   包体介绍

数据body是一个固定8字节的结构体,因为是8字节的,就没有填充数据了,具体如下(来自php-5.4.34 sapi/fpm/fpm/fastcgi.h)

typedef struct _fcgi_begin_request {
    unsigned char roleB1;
    unsigned char roleB0;
    unsigned char flags;
    unsigned char reserved[5];
} fcgi_begin_request;

role的可以取如下三个值,一般都取1

typedef enum _fcgi_role {
    FCGI_RESPONDER  = 1,
    FCGI_AUTHORIZER = 2,
    FCGI_FILTER     = 3
} fcgi_role;

flags取0表示本次请求完毕后即关闭链接

3.3.3   包体举例

一个数据body的例子:

序列 0  1  2  3  4  5  6  7
数值 00 01 00 00 00 00 00 00

取值说明如下:

序列0 1(00 01)表示是FCGI_RESPONDER类型,cgi应用程序的服务类型是响应器,即 webserver会把cgi应用程序产生的输出转发到客户端
序列2 (00) flags取0表示本次请求完毕后即关闭链接
序列3~7 (00) 保留字段,固定为0

3.4   FCGI_PARAMS包

3.4.1   包头举例

例子1:FCGI_PARAMS类型的一个包头值为:

序列 0  1  2  3  4  5  6  7
数值 01 04 00 01 00 68 00 00

包头取值说明如下:

序列0(01)为version
序列1(04)为type,代表FCGI_PARAMS类型的包
序列2 3(00 01)代表2字节的请求id,默认取1即可
序列4 5(00 68) 代表2字节的body的长度,这里为104(十六进制为x0068)
序列6 (00) 代表1字节的填充数据的长度,这里为0
序列7 (00) 代表1字节的保留字段,固定为0

例子2:空body的FCGI_PARAMS类型的包头(发送完FCGI_PARAMS包后,需要再发送一个空body的FCGI_PARAMS包):

序列 0  1  2  3  4  5  6  7
数值 01 04 00 01 00 00 00 00

包头取值说明如下:

序列0(01)为version
序列1(04)为type,代表FCGI_PARAMS类型的包
序列2 3(00 01)代表2字节的请求id,默认取1即可
序列4 5(00 00) 代表0字节的body的长度
序列6 (00) 代表1字节的填充数据的长度,这里为0
序列7 (00) 代表1字节的保留字段,固定为0

3.4.2   包体介绍

如上面描述的FCGI_PARAMS记录的是http的header,querystring等这种Name-Value Pairs

一个Name-Value Pairs 以 NameLen + ValueLen + NameData + valueData的形式记录,整个数据以下形式组成即:

NameLen1 + ValueLen1 + NameData1 + valueData1 + .... + NameLenN + ValueLenN + NameDataN + valueDataN

其中规定 NameLen或者ValueLen如果大于127,则要以4字节存储,最高位必须为1

所以包体的结构有以下四种可能性(http://www.fastcgi.com/devkit/doc/fcgi-spec.html#S3.4):

typedef struct {
    unsigned char nameLengthB0;  /* nameLengthB0  >> 7 == 0 */
    unsigned char valueLengthB0; /* valueLengthB0 >> 7 == 0 */
    unsigned char nameData[nameLength];
    unsigned char valueData[valueLength];
} FCGI_NameValuePair11;

typedef struct {
    unsigned char nameLengthB0;  /* nameLengthB0  >> 7 == 0 */
    unsigned char valueLengthB3; /* valueLengthB3 >> 7 == 1 */
    unsigned char valueLengthB2;
    unsigned char valueLengthB1;
    unsigned char valueLengthB0;
    unsigned char nameData[nameLength];
    unsigned char valueData[valueLength
            ((B3 & 0x7f) << 24) + (B2 << 16) + (B1 << 8) + B0];
} FCGI_NameValuePair14;

typedef struct {
    unsigned char nameLengthB3;  /* nameLengthB3  >> 7 == 1 */
    unsigned char nameLengthB2;
    unsigned char nameLengthB1;
    unsigned char nameLengthB0;
    unsigned char valueLengthB0; /* valueLengthB0 >> 7 == 0 */
    unsigned char nameData[nameLength
            ((B3 & 0x7f) << 24) + (B2 << 16) + (B1 << 8) + B0];
    unsigned char valueData[valueLength];
} FCGI_NameValuePair41;

typedef struct {
    unsigned char nameLengthB3;  /* nameLengthB3  >> 7 == 1 */
    unsigned char nameLengthB2;
    unsigned char nameLengthB1;
    unsigned char nameLengthB0;
    unsigned char valueLengthB3; /* valueLengthB3 >> 7 == 1 */
    unsigned char valueLengthB2;
    unsigned char valueLengthB1;
    unsigned char valueLengthB0;
    unsigned char nameData[nameLength
            ((B3 & 0x7f) << 24) + (B2 << 16) + (B1 << 8) + B0];
    unsigned char valueData[valueLength
            ((B3 & 0x7f) << 24) + (B2 << 16) + (B1 << 8) + B0];
} FCGI_NameValuePair44;

3.4.3   包体举例

例子1:配合上面那个包头的一个包体的例子,有104个字符:

序列 00  01  02  03  04  05  06  07  08  09  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  37  38  39
数值 \x0f\x33\x53\x43\x52\x49\x50\x54\x5f\x46\x49\x4c\x45\x4e\x41\x4d\x45\x2f\x68\x6f\x6d\x65\x2f\x75\x73\x65\x72\x73\x2f\x6c\x69\x68\x6f\x6e\x67\x62\x69\x6e\x2f\x77

序列 40  41  42  43  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69  70  71  72  73  74  75  76  77  78  79
数值 \x77\x77\x64\x61\x74\x61\x2f\x68\x74\x64\x6f\x63\x73\x2f\x74\x65\x73\x74\x2f\x69\x6e\x64\x65\x78\x2e\x70\x68\x70\x0e\x03\x52\x45\x51\x55\x45\x53\x54\x5f\x4d\x45

序列  80  81  82  83  84  85  86  87  88  89  90  91  92  93  94  95  96  97  98  99  100 101 102 103
数值  \x54\x48\x4f\x44\x47\x45\x54\x0e\x01\x43\x4f\x4e\x54\x45\x4e\x54\x5f\x4c\x45\x4e\x47\x54\x48\x30

包体取值说明如下:

序列0 (\x0f),十进制为15,不大于127,说明第一个Name-Value Pairs 里Name的长度用一个字节存储,该长度为15
序列1 (\x33), 十进制为51,不大于127, 说明第一个Name-Value Pairs 里Value的长度用一个字节存储,该长度为51
序列2~序列16(2+15-1),为第一个Name-Value Pairs 里Name的值,即\x53\x43\x52\x49\x50\x54\x5f\x46\x49\x4c\x45\x4e\x41\x4d\x45 代表着 SCRIPT_FILENAME
序列17~序列67(17+51-1),为第一个Name-Value Pairs 里Value的值,\x2f\x68\x6f\x6d\x65 .... \x2e\x70\x68\x70 代表着/home/users/lihongbin/wwwdata/htdocs/test/index.php
序列68 (\x0e),十进制为14,不大于127,说明第二个Name-Value Pairs 里Name的长度用一个字节存储,该长度为14
序列69 (\x03),十进制为51,不大于127, 说明第二个Name-Value Pairs 里Value的长度用一个字节存储,该长度为3
序列70~序列83(70+14-1),为第二个Name-Value Pairs 里Name的值,即\x52\x45\x51\x55\x45\x53\x54\x5f\x4d\x45\x54\x48\x4f\x44 代表着 REQUEST_METHOD
序列84~序列86(84+3-1),为第二个Name-Value Pairs 里Value的值,即\x47\x45\x54 代表着GET
序列87 (\x0e),十进制为14,不大于127,说明第三个Name-Value Pairs 里Name的长度用一个字节存储,该长度为14
序列88 (\x01),十进制为1,不大于127, 说明第三个Name-Value Pairs 里Value的长度用一个字节存储,该长度为1
序列89~序列102(89+14-1),为第三个Name-Value Pairs 里Name的值,即\x43\x4f\x4e\x54\x45\x4e\x54\x5f\x4c\x45\x4e\x47\x54\x48 代表着 CONTENT_LENGTH
序列103~序列103(103+1-1),为第三个Name-Value Pairs 里Value的值,即\x30 代表着 0

总之例子中的Name-Value Pairs如下所示:

‘SCRIPT_FILENAME’ => '/home/users/lihongbin/wwwdata/htdocs/test/index.php'
'REQUEST_METHOD'  => 'GET'
'CONTENT_LENGTH'  => 0

例子2,给一个Name-Value Pairs里Value的长度大于127,采用四个字节表示Value长度的例子:

序列 00  01  02  03  04  05  06  07  08  09  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  37  38  39
数值 \x06\x80\x00\x00\x96\x48\x54\x54\x50\x5f\x41\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30\x31\x32\x33\x34\x35\x36\x37\x38

序列 40  41  42  43  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69  70  71  72  73  74  75  76  77  78  79
数值 \x39\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30\x31\x32\x33\x34\x35\x36\x37\x38

序列 80  81  82  83  84  85  86  87  88  89  90  91  92  93  94  95  96  97  98  99  100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
数值 \x39\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30\x31\x32\x33\x34\x35\x36\x37\x38

序列 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
数值 \x39\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39

包体取值说明如下:

序列0 (\x06),十进制为6,不大于127,说明第一个Name-Value Pairs 里Name的长度用一个字节存储,该长度为6
序列1 (\x80),十进制为128,大于127,说明第一个Name-Value Pairs 里Value的长度用四个字节存储
    长度为(序列1 & 0x7f) << 24) + (序列2 << 16) + (序列3 << 8) + 序列4 = ((0x80 & 0x7f) << 24) + (0x00 << 16) + (0x00 << 8) + 0x96 = 150
序列5~序列10(5+6-1),为第一个Name-Value Pairs 里Name的值,即 \x48\x54\x54\x50\x5f\x41 代表着 HTTP_A
序列11~序列160(11+150-1),为第一个Name-Value Pairs 里Value的值
    即\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39 .... \x30\x31\x32\x33\x34\x35\x36\x37\x38\x39 代表着连续15个0123456789

3.5   FCGI_STDIN包

3.5.1   包头举例

例子1,FCGI_STDIN类型的包头值为:

序列 0  1  2  3  4  5  6  7
数值 01 05 00 01 00 03 05 00

包头取值说明如下:

序列0(01)为version
序列1(05)为type,代表FCGI_STDIN,表示开始数据部分
序列2 3(00 01)代表2字节的请求id,默认取1即可
序列4 5(00 03) 代表2字节的body的长度,这里为3,表示要发送的数据的长度为3
序列6 (05) 代表1字节的填充数据的长度,这里为5,表示要填充5字节的填充数据
序列7 (00) 代表1字节的保留字段,固定为0

例子2,空body的情况(在发送完数据后,需要再发送一个空body的FCGI_STDIN包),FCGI_STDIN类型的包头值为:

序列 0  1  2  3  4  5  6  7
数值 01 05 00 01 00 00 00 00

包头取值说明如下:

序列0(01)为version
序列1(05)为type,代表FCGI_STDIN,表示开始数据部分
序列2 3(00 01)代表2字节的请求id,默认取1即可
序列4 5(00 03) 代表2字节的body的长度,这里固定为0
序列6 (05) 代表1字节的填充数据的长度,这里固定为0
序列7 (00) 代表1字节的保留字段,固定为0

3.5.2   包体举例

对应上面例子1的包体:

序列 0  1  2  3  4  5  6  7
数值 61 3d 62 00 00 00 00 00

取值说明如下:

序列0~2 为真实数据部分,表示数据 a=b
序列3~7 为填充数据部分

3.6   FCGI_STDERR包

3.6.1   包头举例

例子1,FCGI_STDERR类型的包头值为:

序列 0  1  2  3  4  5  6  7
数值 01 07 00 01 00 73 05 00

取值说明如下:

序列0(01)为version
序列1(07)为type,代表FCGI_STDERR包,表示错误输出
序列2 3(00 01)代表2字节的请求id,默认取1即可
序列4 5(00 73) 代表2字节的body的长度,这里表示真实数据的长度为115
序列6 (05) 代表1字节的填充数据的长度,这里表示填充数据的长度为5
序列7 (00) 代表1字节的保留字段,固定为0

3.7   FCGI_STDOUT包

3.7.1   包头举例

例子1,FCGI_STDOUT类型的包头值为:

序列 0  1  2  3  4  5  6  7
数值 01 06 00 01 00 73 05 00

取值说明如下:

序列0(01)为version
序列1(06)为type,代表FCGI_STDOUT包,表示标准输出
序列2 3(00 01)代表2字节的请求id,默认取1即可
序列4 5(00 73) 代表2字节的body的长度,这里表示真实数据的长度为115
序列6 (05) 代表1字节的填充数据的长度,这里表示填充数据的长度为5
序列7 (00) 代表1字节的保留字段,固定为0

3.8   FCGI_END_REQUEST包

3.8.1   包头举例

例子1,FCGI_END_REQUEST类型的包头值为:

序列 0  1  2  3  4  5  6  7
数值 01 03 00 01 00 08 00 00

取值说明如下:

序列0(01)为version
序列1(06)为type,代表FCGI_END_REQUEST包,表示请求结束
序列2 3(00 01)代表2字节的请求id,默认取1即可
序列4 5(00 08) 代表2字节的body的长度,这里固定为8
序列6 (05) 代表1字节的填充数据的长度,这里固定为0
序列7 (00) 代表1字节的保留字段,固定为0

3.8.2   包体介绍

FCGI_END_REQUEST包的包体是由8字节组成,具体的结构体如下(http://www.fastcgi.com/devkit/doc/fcgi-spec.html#S5.5):

typedef struct {
    unsigned char appStatusB3;
    unsigned char appStatusB2;
    unsigned char appStatusB1;
    unsigned char appStatusB0;
    unsigned char protocolStatus;
    unsigned char reserved[3];
} FCGI_EndRequestBody;

appStatus 由四个字节组成,表示cgi通过调用系统退出返回的状态码

protocolStatus 的取值有以下几种情况(来自php-5.4.34 sapi/fpm/fpm/fastcgi.h)

typedef enum _fcgi_protocol_status {
    FCGI_REQUEST_COMPLETE   = 0,
    FCGI_CANT_MPX_CONN      = 1,
    FCGI_OVERLOADED         = 2,
    FCGI_UNKNOWN_ROLE       = 3
} dcgi_protocol_status;

3.8.3   包体举例

例子1,FCGI_END_REQUEST类型的包体值为:

序列 0  1  2  3  4  5  6  7
数值 00 00 00 00 00 6e 67 62

取值说明如下:

序列0~3 为appStatus
序列4 为protocolStatus,值为0,表示FCGI_REQUEST_COMPLETE,正常结束
序列5~7  为保留字段

4   模拟fastcgi协议

4.1   php代码

没有考虑超时之类的情况 -_-||,正常情况下可以使用

<?php
define('FCGI_REQUEST_ID', 1);
define('FCGI_VERSION_1', 1);
define('FCGI_BEGIN_REQUEST', 1);
define('FCGI_RESPONDER', 1);
define('FCGI_END_REQUEST', 3);
define('FCGI_PARAMS', 4);
define('FCGI_STDIN', 5);
define('FCGI_STDOUT', 6);
define('FCGI_STDERR', 7);

define('FCGI_HOST', '127.0.0.1');
define('FCGI_PORT', 9130);

get_main();
//post_main();

function get_main()
{
    $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    socket_connect($socket, FCGI_HOST, FCGI_PORT);

    $content_body = '';
    $fcgi_request_id = 1;
    $env = array(
        'SCRIPT_FILENAME' => '/home/users/lihongbin/wwwdata/htdocs/test/index.php',
        'REQUEST_METHOD'  => 'GET',
//        'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
        'CONTENT_LENGTH' => strlen($content_body),
//        'QUERY_STRING' => 'user_id=1&app_id=2000',
//        'REQUEST_URI' => '/rest/2.0/dev?user_id=1&app_id=2000',
    );

    send_request($socket, $content_body, $env, $fcgi_request_id);
    $result = get_response($socket);

    var_dump($result);
    socket_close($socket);
}

function post_main()
{
    $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    socket_connect($socket, FCGI_HOST, FCGI_PORT);

    $content_body = 'x=y';
    $fcgi_request_id = 1;
    $env = array(
        'SCRIPT_FILENAME' => '/home/users/lihongbin/wwwdata/htdocs/test/index.php',
        'REQUEST_METHOD'  => 'POST',
        'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
        'CONTENT_LENGTH' => strlen($content_body),
//        'QUERY_STRING' => 'user_id=1&app_id=2000',
//        'REQUEST_URI' => '/rest/2.0/dev?user_id=1&app_id=2000',
    );

    send_request($socket, $content_body, $env, $fcgi_request_id);
    $result = get_response($socket);

    var_dump($result);

    socket_close($socket);
}

function send_request($socket, $content_body, $env, $fcgi_request_id)
{
    $content_body_len = strlen($content_body);

    //step1 FCGI_BEGIN_REQUEST
    $begin_request_body = get_begin_request_body();
    $begin_request_header = get_header(FCGI_BEGIN_REQUEST, $fcgi_request_id, 8, 0);
    $record = $begin_request_header . $begin_request_body;
    socket_write($socket, $record);

    //step2 FCGI_PARAMS
    $params_body = '';
    //$env['CONTENT_LENGTH'] = $content_body_len;
    foreach($env as $key => $value)
    {
        $params_body .= get_params_key_len($key) . get_params_key_len($value) . $key . $value;
    }
    $params_body_len = strlen($params_body);
    $params_pad_body_len = get_pad_len($params_body_len);
    $params_header = get_header(FCGI_PARAMS, $fcgi_request_id, $params_body_len, $params_pad_body_len);
    $params_pad_body = get_pad_data($params_pad_body_len);
    $record = $params_header . $params_body . $params_pad_body;
    socket_write($socket, $record);

    $params_empty_body_header = get_header(FCGI_PARAMS, $fcgi_request_id, 0, 0);
    socket_write($socket, $params_empty_body_header);

    //step3 FCGI_STDIN
    if($content_body_len > 0)
    {
        $stdin_pad_body_len = get_pad_len($content_body_len);
        $stdin_header = get_header(FCGI_STDIN, $fcgi_request_id, $content_body_len, $stdin_pad_body_len);
        $record = $stdin_header . $content_body . get_pad_data($stdin_pad_body_len);
        socket_write($socket, $record);
    }
    $stdin_empty_header = get_header(FCGI_STDIN, $fcgi_request_id, 0, 0);
    socket_write($socket, $stdin_empty_header);
}

function get_response($socket)
{
    $result = array();
    $stdout_response_body = '';
    $stderr_response_body = '';
    while(1)
    {
        $header = socket_read($socket, 8);
        $header = unpack("Cversion/Ctype/nrequestId/ncontentLength/CpaddingLength/Creserved", $header);
        $response_body_len = $header['contentLength'];
        $response_pad_len = $header['paddingLength'];
        $type = $header['type'];

        $response_body = read_body($socket, $response_body_len);
        $response_pad_body = read_body($socket, $response_pad_len);

        if($type == FCGI_END_REQUEST)
        {
            $result['end']['response_header'] = $header;
            $result['end']['response_body'] = unpack("NappStatus/CprotocolStatus/C3reserved", $response_body);
            break;
        }
        else if($type == FCGI_STDOUT)
        {
            $stdout_response_body .= $response_body;
            $result['stdout']['last_response_header'] = $header;
        }
        else if($type == FCGI_STDERR)
        {
            $stderr_response_body .= $response_body;
            $result['stderr']['last_response_header'] = $header;
        }
    }

    if(empty($stdout_response_body) === false)
    {
        list($header, $body) = explode("\r\n\r\n", $stdout_response_body);
        $result['stdout']['response_body']['header'] = get_http_header_info($header);
        $result['stdout']['response_body']['body'] = $body;
    }

    if(empty($stderr_response_body) === false)
    {
        $result['stderr']['response_body'] = $stderr_response_body;
    }

    return $result;
}

function get_http_header_info($header)
{
    $arr = explode("\r\n", $header);
    foreach($arr as $item)
    {
        $arr_item = explode(":", $item);

        if(count($arr_item) == 2)
        {
            $key = trim($arr_item[0]);
            $value = trim($arr_item[1]);
            $arr_header[$key] = $value;
        }
    }
    return $arr_header;
}

function get_begin_request_body()
{
    return pack("nC6", FCGI_RESPONDER, 0, 0, 0, 0, 0, 0);
}

function get_header($type, $request_id, $content_len, $pad_len, $reserved = 0)
{
    return pack("C2n2C2", FCGI_VERSION_1, $type, $request_id, $content_len, $pad_len, $reserved);
}

function get_pad_len($body_len)
{
    $left = $body_len % 8;
    if ($left == 0)
    {
        return 0;
    }
    return 8 - $left;
}

function get_pad_data($pad_len)
{
    $data = '';
    for($i = 0; $i < $pad_len; ++$i)
    {
        $data .= chr(0);
    }
    return $data;
}

function get_params_key_len($key)
{
    $len = strlen($key);
    if($len > 0x7f)
    {
        $b0 = $len & 0xff;
        $b1 = ($len >> 8) & 0xff;
        $b2 = ($len >> 16) & 0xff;
        $b3 = ($len >> 24) & 0xff;

        $b3 = $b3 | 0x80;
        $bin = pack("C4", $b3, $b2, $b1, $b0);
    }
    else
    {
        $bin = pack("C", $len);
    }
    return $bin;
}

function read_body($socket, $len)
{
    $body = "";
    while($len)
    {
        $buf = socket_read($socket, $len);
        $len -= strlen($buf);
        $body .= $buf;
    }
    return $body;
}