资讯专栏INFORMATION COLUMN

【PHP7源码学习】2019-04-10 FastCGI协议2

Cympros / 3704人阅读

摘要:全部视频引入读这篇文章之前请先阅读源码学习协议我们知道,客户端之间通信的方式如下那么,我们今天详细解释一下图中的协议的部分。协议就是为了解决协议的相关问题而出现,是协议的升级版。在配置中指令中指定的值请求使用的协议,通常是或。

baiyan

全部视频:https://segmentfault.com/a/11...

引入

读这篇文章之前请先阅读【PHP源码学习】2019-04-09 FastCGI协议1

我们知道,客户端、nginx、PHP-FPM之间通信的方式如下:

那么,我们今天详细解释一下图中的FastCGI协议的部分。其实,最开始我们是使用CGI协议的,但是CGI程序的弊端十分明显,如需要新的进程进行数据处理,效率低下。FastCGI协议就是为了解决CGI协议的相关问题而出现,是CGI协议的升级版。

我们学习一个协议,最重要的就是它的格式与语法,看它如何组织所要传输数据的格式,让接收方能够更加方便地接收。那么,这个协议需要解决如下几个问题:

标识一个请求的开始与结束,让数据包在繁杂的TCP数据流中拥有清晰的边界,方便读取

传输其他附加参数(如定义在nginx中的fastcgi_param各项参数)

传输一个客户端发来请求的原始数据

针对上面一条提到在nginx配置文件中的其他附加参数,有如下一些形式,大家应该比较熟悉了:

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;#脚本文件请求的路径,也就是说当访问127.0.0.1/index.php的时候,需要读取网站根目录下面的index.php文件,如果没有配置这一配置项时,nginx不回去网站根目录下访问.php文件,所以返回空白
fastcgi_param QUERY_STRING $query_string;                        #请求的参数;如?app=123
fastcgi_param REQUEST_METHOD $request_method;                    #请求的动作(GET,POST)
fastcgi_param CONTENT_TYPE $content_type;                        #请求头中的Content-Type字段
fastcgi_param CONTENT_LENGTH $content_length;                    #请求头中的Content-length字段。

fastcgi_param SCRIPT_NAME $fastcgi_script_name;                  #脚本名称 
fastcgi_param REQUEST_URI $request_uri;                          #请求的地址不带参数
fastcgi_param DOCUMENT_URI $document_uri;                        #与$uri相同。 
fastcgi_param DOCUMENT_ROOT $document_root;                      #网站的根目录。在server配置中root指令中指定的值 
fastcgi_param SERVER_PROTOCOL $server_protocol;                  #请求使用的协议,通常是HTTP/1.0或HTTP/1.1。

fastcgi_param GATEWAY_INTERFACE CGI/1.1;                         #cgi 版本
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;              #nginx 版本号,可修改、隐藏

fastcgi_param REMOTE_ADDR $remote_addr;                          #客户端IP
fastcgi_param REMOTE_PORT $remote_port;                          #客户端端口
fastcgi_param SERVER_ADDR $server_addr;                          #服务器IP地址
fastcgi_param SERVER_PORT $server_port;                          #服务器端口
fastcgi_param SERVER_NAME $server_name;                          #服务器名,域名在server配置中指定的server_name

fastcgi_param PATH_INFO $path_info;                             #可自定义变量

-- PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;

那么,我们在PHP中即可打印出上面的服务环境变量。如:

echo $_SERVER["REMOTE_ADDR"]

带着以上几个问题,我们来由外到内一步步剖析,为什么FastCGI协议是这样设计的。

FastCGI的设计思想与结构

首先我们基于之前的客户端、nginx、PHP-FPM之间通信流程图,放大nginx与PHP-FPM之间通信的数据流:

为了解决我们之前谈到的三个问题,FastCGI把包分为多种类型,每种类型做它自己的事情。如图中的FCGI_BEGIN_REQUEST类型,负责标识请求的开始,FCGI_PARAMS类型负责发送nginx中配置的参数,FCGI_STDIN类型存储客户端发送的原始字节流数据。这样一次请求的所有数据才能够成功送达到PHP-FPM。我们看一下FastCGI数据包的所有类型:

#define FCGI_BEGIN_REQUEST       1                     //(web->fastcgi)请求开始数据包
#define FCGI_ABORT_REQUEST       2                     //(web->fastcgi)终止请求
#define FCGI_END_REQUEST         3                     //(fastcgi->web)请求结束
#define FCGI_PARAMS              4                     //(web->fastcgi)传递参数
#define FCGI_STDIN               5                     //(web->fastcgi)数据流传输数据
#define FCGI_STDOUT              6                     //(fastcgi->web)数据流传输数据
#define FCGI_STDERR              7                     //(fastcgi->web)数据流传输
#define FCGI_DATA                8                     //(web->fastcgi)数据流传输
#define FCGI_GET_VALUES          9                     //(web->fastcgi)查询fastcgi服务器性能参数
#define FCGI_GET_VALUES_RESULT  10                     //(fastcgi->web)fastcgi性能参数查询返回
#define FCGI_UNKNOWN_TYPE       11
#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)

我们从宏观层面看完了FastCGI包,我们深入每个包的内部结构。通过上一篇笔记的学习我们知道,TCP/IP等协议的数据包,通常都是数据包头部+包体的结构,头部字段通常是一些描述信息,包体才真正地存储数据,这里FastCGI协议也不例外:

在代码层面,它的结构如下:

typedef struct _fcgi_begin_request_rec {
    fcgi_header hdr; //包头部
    fcgi_begin_request body; //包体
} fcgi_begin_request_rec;
FastCGI数据包头部

FastCGI数据包头部结构定义如下:

typedef struct {
    unsigned char version;            // 协议版本号
    unsigned char type;               // 数据包类型
    unsigned char requestIdB1;        // 包唯一标识id的高8位
    unsigned char requestIdB0;        // 包唯一标识id的低8位
    unsigned char contentLengthB1;    // 记录内容长度高8位(body长度高8位)
    unsigned char contentLengthB0;    // 记录内容长度低8位(body长度低8位)
    unsigned char paddingLength;      // 补齐位长度(body补齐长度)
    unsigned char reserved;           // 字节补齐位
}Header;

通常情况下,每一个FastCGI数据包都有一个头部,大小为8个字节,用来记录当前数据包的一些辅助信息,如数据包类型(需要确认当前包属于刚才我们列举的哪种类型)、唯一标识包的id、还有包体的长度、以及字节对齐(确保是2的整数次幂)

虽然在通常情况下,每一种FastCGI类型的数据包都有相同结构的包头,但是它们之间包体部分的结构就不太一样了。

下面我们以一个请求从nginx到PHP-FPM的数据包流动方向(FCGI_BEGIN_REQUEST->FCGI_PARAMS->FCGI_STDIN)为例,讲解一下FastCGI协议的类型。

FCGI_BEGIN_REQUEST类型

FCGI_BEGIN_REQUEST类型的数据包代表一个请求数据包的开始

由于数据包头部的结构已经介绍完毕了,接下来我们看一下FCGI_BEGIN_REQUEST类型包体部分的结构,它是一个结构体:

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

FCGI_BEGIN_REQUEST的包体大小为8个字节,其中role字段是为了描述当前需要FastCGI服务器(即 PHP-FPM)充当的角色,有FCGI_RESPONDER,FCGI_AUTHORIZER 和FCGI_FILTER。

FCGI_RESPONDER:最常见的动态语言脚本处理角色,叫做响应器。
FCGI_AUTHORIZER:用于判断请求是否拥有访问权限,类似于HTTP请求中的认证功能,叫做授权器。
FCGI_FILTER:用于对一些特殊的数据进行处理并返回,包括添加数据头部与尾部等功能,叫做过滤器(官方对其没有过多的介绍,所以无法详细描述)。
大多数请求我们都是使用FCGI_RESPONDER角色进行请求传输,因为动态语言可以完全的替代其他2中角色的功能,所以授权器和过滤器的功能被大家给遗忘了。不过这不代表角色的设定是错误的,角色的设定很大一部分程度上给Fastcgi协议提供了快捷扩展的功能,保证了协议的可扩展性。
flags则是用于设置使用传输时复用通道,避免每次传输都需要新开一个socket通道来浪费时间和性能。
FCGI_PARAMS类型

在nginx配置文件中,配置的FastCGI的参数均以参数名-值的形式出现,那么可以用一种key-value对的结构来对其进行存储,而它确实也是这样设计的:

我们可以大体上看出,FCGI_PARAMS的包体以key-value对形式出现。整个数据包的存储数据为包头部、key的长度、value的长度、key数据、value数据的顺序出现。

这里它用了一个技巧。为了节省空间,当key或者value的数据长度小于等于127字节的时候,key和value的长度两个字段采用1个字节来表示;当大于128字节的时候,采用4个字节来表示。那么为什么选127作为分界线呢?因为127的二进制位01111111,从128开始,最高为为1,所以只需要判断最高位是否为1,就可以知道key或者value长度的字段占用1个字节还是4个字节。如果最高位为1,则占用4个字节;如果最高位为0,则占用1个字节。

FCGI_STDIN类型

FCGI_STDIN存储从客户端发出的原始数据,注意这里的数据是以字节流存储的,而并不是存在一个固定的结构体中:

至此,一次nginx到PHP-FPM的请求就完成了。

抓包示例
13:50:43.883594 IP VM_0_3_centos.33844 > VM_0_3_centos.cslistener: Flags [P.], seq 608546014:608546982, ack 2973795482, win 342, options [nop,nop,TS val 961901286 ecr 961901286], length 968
    0x0000:  0000 0000 0000 0000 0000 0000 0800 4500  ..............E.
    0x0010:  03fc de3d 4000 4006 5abc 7f00 0001 7f00  ...=@.@.Z.......
    0x0020:  0001 8434 2328 2445 acde b140 849a 8018  ...4#($E...@....
    0x0030:  0156 01f1 0000 0101 080a 3955 72e6 3955  .V........9Ur.9U
    0x0040:  72e6 0101 0001 0008 0000 0001 0000 0000  r...............
    0x0050:  0000 0104 0001 03a0 0000 0f35 5343 5249  ...........5SCRI
    0x0060:  5054 5f46 494c 454e 414d 452f 6461 7461  PT_FILENAME/data
    0x0070:  2f77 7777 2f68 7464 6f63 732f 6461 7461  /www/htdocs/data
    0x0080:  2f77 7777 2f68 7464 6f63 732f 736e 6f2f  /www/htdocs/sno/
    0x0090:  7075 626c 6963 2f69 6e64 6578 2e70 6870  public/index.php
    0x00a0:  0c00 5155 4552 595f 5354 5249 4e47 0e03  ..QUERY_STRING..
    0x00b0:  5245 5155 4553 545f 4d45 5448 4f44 4745  REQUEST_METHODGE
    0x00c0:  540c 0043 4f4e 5445 4e54 5f54 5950 450e  T..CONTENT_TYPE.
    0x00d0:  0043 4f4e 5445 4e54 5f4c 454e 4754 480b  .CONTENT_LENGTH.
    0x00e0:  0a53 4352 4950 545f 4e41 4d45 2f69 6e64  .SCRIPT_NAME/ind
    0x00f0:  6578 2e70 6870 0b01 5245 5155 4553 545f  ex.php..REQUEST_
    0x0100:  5552 492f 0c01 444f 4355 4d45 4e54 5f55  URI/..DOCUMENT_U
    0x0110:  5249 2f0d 2b44 4f43 554d 454e 545f 524f  RI/.+DOCUMENT_RO
    0x0120:  4f54 2f64 6174 612f 7777 772f 6874 646f  OT/data/www/htdo
    0x0130:  6373 2f64 6174 612f 7777 772f 6874 646f  cs/data/www/htdo
    0x0140:  6373 2f73 6e6f 2f70 7562 6c69 630f 0853  cs/sno/public..S
    0x0150:  4552 5645 525f 5052 4f54 4f43 4f4c 4854  ERVER_PROTOCOLHT
    0x0160:  5450 2f31 2e31 0e04 5245 5155 4553 545f  TP/1.1..REQUEST_
    0x0170:  5343 4845 4d45 6874 7470 1107 4741 5445  SCHEMEhttp..GATE
    0x0180:  5741 595f 494e 5445 5246 4143 4543 4749  WAY_INTERFACECGI
    0x0190:  2f31 2e31 0f0c 5345 5256 4552 5f53 4f46  /1.1..SERVER_SOF
    0x01a0:  5457 4152 456e 6769 6e78 2f31 2e31 312e  TWAREnginx/1.11.
    0x01b0:  390b 0f52 454d 4f54 455f 4144 4452 3131  9..REMOTE_ADDR11
    0x01c0:  332e 3232 372e 3234 392e 3132 370b 0552  3.227.249.127..R
    0x01d0:  454d 4f54 455f 504f 5254 3533 3931 330b  EMOTE_PORT53913.
    0x01e0:  0a53 4552 5645 525f 4144 4452 3137 322e  .SERVER_ADDR172.
    0x01f0:  3136 2e30 2e33 0b02 5345 5256 4552 5f50  16.0.3..SERVER_P
    0x0200:  4f52 5438 300b 0d53 4552 5645 525f 4e41  ORT80..SERVER_NA
    0x0210:  4d45 6772 6170 652e 7961 662e 636f 6d0f  MEgrape.yaf.com.
    0x0220:  0352 4544 4952 4543 545f 5354 4154 5553  .REDIRECT_STATUS
    0x0230:  3230 3009 0f48 5454 505f 484f 5354 3132  200..HTTP_HOST12
    0x0240:  322e 3135 322e 3232 392e 3232 310f 0a48  2.152.229.221..H
    0x0250:  5454 505f 434f 4e4e 4543 5449 4f4e 6b65  TTP_CONNECTIONke
    0x0260:  6570 2d61 6c69 7665 1209 4854 5450 5f43  ep-alive..HTTP_C
    0x0270:  4143 4845 5f43 4f4e 5452 4f4c 6d61 782d  ACHE_CONTROLmax-
    0x0280:  6167 653d 301e 0148 5454 505f 5550 4752  age=0..HTTP_UPGR
    0x0290:  4144 455f 494e 5345 4355 5245 5f52 4551  ADE_INSECURE_REQ
    0x02a0:  5545 5354 5331 0f79 4854 5450 5f55 5345  UESTS1.yHTTP_USE
    0x02b0:  525f 4147 454e 544d 6f7a 696c 6c61 2f35  R_AGENTMozilla/5
    0x02c0:  2e30 2028 4d61 6369 6e74 6f73 683b 2049  .0.(Macintosh;.I
    0x02d0:  6e74 656c 204d 6163 204f 5320 5820 3130  ntel.Mac.OS.X.10
    0x02e0:  5f31 355f 3029 2041 7070 6c65 5765 624b  _15_0).AppleWebK
    0x02f0:  6974 2f35 3337 2e33 3620 284b 4854 4d4c  it/537.36.(KHTML
    0x0300:  2c20 6c69 6b65 2047 6563 6b6f 2920 4368  ,.like.Gecko).Ch
    0x0310:  726f 6d65 2f37 352e 302e 3337 3730 2e31  rome/75.0.3770.1
    0x0320:  3030 2053 6166 6172 692f 3533 372e 3336  00.Safari/537.36
    0x0330:  0b76 4854 5450 5f41 4343 4550 5474 6578  .vHTTP_ACCEPTtex
    0x0340:  742f 6874 6d6c 2c61 7070 6c69 6361 7469  t/html,applicati
    0x0350:  6f6e 2f78 6874 6d6c 2b78 6d6c 2c61 7070  on/xhtml+xml,app
    0x0360:  6c69 6361 7469 6f6e 2f78 6d6c 3b71 3d30  lication/xml;q=0
    0x0370:  2e39 2c69 6d61 6765 2f77 6562 702c 696d  .9,image/webp,im
    0x0380:  6167 652f 6170 6e67 2c2a 2f2a 3b71 3d30  age/apng,*/*;q=0
    0x0390:  2e38 2c61 7070 6c69 6361 7469 6f6e 2f73  .8,application/s
    0x03a0:  6967 6e65 642d 6578 6368 616e 6765 3b76  igned-exchange;v
    0x03b0:  3d62 3314 0d48 5454 505f 4143 4345 5054  =b3..HTTP_ACCEPT
    0x03c0:  5f45 4e43 4f44 494e 4767 7a69 702c 2064  _ENCODINGgzip,.d
    0x03d0:  6566 6c61 7465 140e 4854 5450 5f41 4343  eflate..HTTP_ACC
    0x03e0:  4550 545f 4c41 4e47 5541 4745 7a68 2d43  EPT_LANGUAGEzh-C
    0x03f0:  4e2c 7a68 3b71 3d30 2e39 0104 0001 0000  N,zh;q=0.9......
    0x0400:  0000 0105 0001 0000 0000                 ..........

根据上一篇笔记我们学到的数据包结构,我们能够将数据包分解为以下结构(加粗的数字为首部长度,乘以4就是总字节数):

MAC帧头部(14字节):0000 0000 0000 0000 0000 0000 0800

IP头部(20字节):4500 03fc de3d 4000 4006 5abc 7f00 0001 7f00

TCP头部(32字节):8434(33844端口) 2328(9000端口) 2445 acde b140 849a 8018 0156 01f1 0000 0101 080a 3955 72e6 3955 72e6

接下来就是FastCGI协议数据包的部分了,首先应该是一个FCGI_BEGIN_REQUEST类型的数据包:

包头:0101 0001 0008 0000

version:01(FastCGI协议版本为1)

type:01(对应FCGI_BEGIN_REQUEST)

requestIdB1:00

requestIdB0:01(代表是1号数据包)

contentLengthB1:00

contentLengthB0:08(代表包体占用8个字节)

paddingLength:00(补齐位长度为0)

reserved:00(对齐位无效)

包体:0001 0000 0000 0000

roleB1:1(代表充当的是响应器角色)

roleB0:0

flags:0

reserved[5]:0(对齐位无效)

那么接下来应该是一个FCGI_PARAMS类型的数据包了:

包头:0104 0001 03a0 0000

version:01

type:04(对应FCGI_PARAMS)

requestIdB1:00

requestIdB0:01(代表是1号数据包)

contentLengthB1:03

contentLengthB0:a0(代表包体占用928个字节)

paddingLength:00(补齐位长度为0)

reserved:00(对齐位无效)

由于这个包体是非常长的,我们选择其中一个key-value对:

包体:0f 35

紧挨着包头的应该是存储key长度的字段,既然它最高位为0(0=0000),那么key的长度只需用1个字节存储,长度为15字节(0f)。然后紧挨着的应该是存储value长度的字段,它的最高位也为0(3=0011),故value的长度也需要1个字节存储,长度为53字节。

然后紧挨着的应该是key的内容:5343 5249 5054 5f46 494c 454e 414d 45,一共15字节,根据ASCII码翻译之后,其值为SCRIPT_FILENAME。再往下数53个字节,应该就是value的内容:2f 6461 7461 2f77 7777 2f68 7464 6f63 732f 6461 7461 2f77 7777 2f68 7464 6f63 732f 736e 6f2f 7075 626c 6963 2f69 6e64 6578 2e70 6870,其翻译后的值为/data/www/htdocs/data/www/htdocs/sno/public/index.php。

我们往下继续数,直至第928个字节,还有其他的各项参数,我们在此不再一一列举。然后就是FCGI_STDIN类型的数据包,存储着我们客户端的原始数据。我们再此就不再赘述,有兴趣的同学可以继续跟进一下。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/40540.html

相关文章

  • 【LNMPR源码学习】笔记汇总

    摘要:此文用于汇总跟随陈雷老师及团队的视频,学习源码过程中的思考整理与心得体会,此文会不断更新视频传送门每日学习记录使用录像设备记录每天的学习源码学习源码学习内存管理笔记源码学习内存管理笔记源码学习内存管理笔记源码学习基本变量笔记 此文用于汇总跟随陈雷老师及团队的视频,学习源码过程中的思考、整理与心得体会,此文会不断更新 视频传送门:【每日学习记录】使用录像设备记录每天的学习 PHP7...

    Barrior 评论0 收藏0
  • PHP7源码学习2019-04-09 FastCGI协议1

    摘要:所以,它就会将端口号还有一些额外的信息被称作首部,和应用层下发的数据部分进行封装,一起传给下一层即网络层。它们之间的通信,属于同一机器上不同端口号之间的通信。而协议传输的仅仅是无意义的字节流数据,接收方并不能正确读取数据的含义。 baiyan 全部视频:https://segmentfault.com/a/11... 计算机网络架构的分层与封装 我们经常谈到,计算机网络有多种体系架构...

    wh469012917 评论0 收藏0
  • Just for fun——windows上的php-fpm

    摘要:,配是通过一个类似的协议,升级版的的。在上有帮你管理进程,在似乎没有,这是有点令人悲伤的。检验一下然后开启然后配置中里文件在盘建立一个的文件夹,放入,开启测试写入访问应用我的项目就用了这个东西,,欢迎 fastcgi As we all know,nginx配php是通过fastcgi(一个类似http的协议,升级版的cgi)的。在linux上有php-fpm帮你管理进程,在windo...

    kel 评论0 收藏0

发表评论

0条评论

Cympros

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<