前言闲来无事,决定整理一下最近看到的一些东西,所以先写fastcgi协议。该协议是cgi协议的升级版。其实那时候的cgi太弱了,导致动态页面太多。消耗性能,所以开发了升级版如fastcgi协议。下面说一下这个协议的相关内容。CGI协议和Fastcgi协议介绍CGI协议介绍CGI协议的诞生就是为了解决HTTP协议和编程语言的连接问题,从而降低动态页面的开发难度。该协议避免了在所有编程语言开发动态页面时都需要开发一套HTTP解析库。那么,关于HTTP协议本身,其实有两部分:请求头和请求体。请求标头基本上以键值对的形式传输,例如Date:Sat,03Feb201800:14:03GMT。请求体是纯数据流,用于传输文件等数据。因此,CGI本身也提供了2种数据格式的输入:键值对输入和数据流输入。最初的CGI程序的输入方式极其简单,键值对数据的输入直接通过环境变量传递,数据流输入通过标准输入流(stdin)传递。CGI程序的返回也包括正常数据输出和错误数据输出两种。正常数据输出用于输出处理后的数据信息,主要是HTTP响应报文,错误数据输出用于程序分析。出错时返回给web服务器的错误信息,以便web服务器进行响应处理和日志记录功能。正常数据输出和错误数据输出在当时的CGI程序中也使用了标准输出流stdout和标准错误流stderr。可以说是非常简单了。下面是一个简单的CGI程序的小栗子:#!/bin/shecho"Content-Type:text/html\n\n"echo""echo""echo"你好!这是PATHvar:"echo$PATH这个CGI程序的主要作用是输出PATH环境变量,里面会包含请求头的相关信息(结果会在后面补充)。Fastcgi协议的引入,但是CGI程序的缺点非常明显:数据处理需要一个新的进程,数据传输方式不能分布式部署,进程的使用容易影响系统运行,并且为每个请求重新加载数据会消耗性能。于是乎,Fastcgi程序的出现解决了相关问题。Fastcgi程序保留了CGI程序的规范并对其进行了升级,主要是将输入输出方式从标准流迁移到socket传输。同时fastcgi协议还支持CGI程序的守护进程,可以提高Request的处理速度,同时提高稳定性。那么,Fastcgi协议、php-fpm、Nginx是什么关系呢?实际上,Nginx是一个只提供HTTP协议输入输出的Web服务器。php-fpm是一个Fastcgi服务器,只支持Fastcgi协议的输入输出。他们两个是直接通过Nginx从HTTP协议转换成Fastcgi协议传输给php-fpm处理的。Fastcgi协议详解协议的组成Fastcgi协议是由数据段一段一段的组成的。可以想象成一个舰队。每辆车装载不同的数据,但车队的顺序是固定的。输入的顺序是:请求开始描述,请求键值对,请求输入数据流。输出顺序为:错误输出数据流、正常输出数据流、请求结束说明。其中键值对、输入流、输出流、错误流数据与CGI程序相同,只是改变了传输方式。回到车队的描述,每辆车的结构也是统一的。前面有个发动机,发动机决定了你的车长什么样。因此,每个数据块都包含一个头信息,结构如下:typedefstruct{unsignedcharversion;//版本号unsignedchar类型;//记录类型unsignedcharrequestIdB1;//记录id高8位unsignedcharrequestIdB0;//记录id低8位unsignedcharcontentLengthB1;//记录内容长度高8位unsignedcharcontentLengthB0;//记录内容长度低8位unsignedcharpaddingLength;//填充位长度unsignedcharreserved;//truerecord头部用位填充}FCGI_Header;注释描述的很清楚:version是版本号,目前只有第一个版本。type作为键描述,用来描述数据的类型,比如键值对类型或者数据流类型,或者请求开始和请求结束,都是通过类型来描述的。requestId为记录ID,(B1代表高位,B0代表低位??,下同),记录ID主要避免同一个socket通道传输数据的正确性,也提高了传输效率。ContentLength是数据内容的长度。paddingLength用于将数据对齐到8字节,为解析和底层IO操作性能提供提示,所以paddingLength只是数据到8的余数,当然不会超过7。reserved用的作为保留位,主要是为了协议头对齐8字节。关于type的取值范围:#defineFCGI_BEGIN_REQUEST1#defineFCGI_ABORT_REQUEST2#defineFCGI_END_REQUEST3#defineFCGI_PARAMS4#defineFCGI_STDIN5#defineFCGI_STDOUT6#defineFCGI_STDERR7#defineFCGI_DATA8#defineFCGI_GET_VALUES9#defineFCGI_GET_VALUES_RESULT10#defineFCGI_UNKNOWN_TYPE11#defineFCGI_MAXTYPE(FCGI_UNKNOWN_TYPE)我们将大致按顺序介绍它们。当输入FCGI_BEGIN_REQUEST请求时,会有这类数据,是描述Fastcgi服务器当前需要扮演的角色和相关设置。数据结构为:typedefstruct{unsignedcharroleB1;//角色类型的高8位unsignedcharroleB0;//角色类型unsignedcharflags的低8位;//小红旗unsignedcharreserved[5];//完成位}FCGI_BeginRequestBody;官方在升级CGI的时候,也在Fastcgi协议中增加了多种角色,定义为:#defineFCGI_RESPONDER1#defineFCGI_AUTHORIZER2#defineFCGI_FILTER3其中,FCGI_RESPONDER是我们最常见的动态语言脚本处理角色,叫做响应者。FCGI_AUTHORIZER用于判断请求是否有访问权限,类似于HTTP请求中的认证功能,称为授权者。FCGI_FILTER用于处理和返回一些特殊的数据,包括添加数据头尾等功能,称为过滤器(官方对它没有过多介绍,无法详细描述)。大多数请求我们使用FCGI_RESPONDER角色,因为动态语言完全可以替代其他2个角色的功能,所以授权者和过滤器的功能被大家遗忘了。但是,这并不意味着角色的设置是错误的。角色的设置在很大程度上为Fastcgi协议提供了快速的扩展功能,保证了协议的可扩展性。Flags用于设置使用传输时的多路复用通道,避免每次传输都需要开启一个新的socket通道浪费时间和性能。FCGI_ABORT_REQUEST该类型主要是为web服务器提供主动结束通道的功能。场景是当web服务器需要尽快结束并关闭通道时,它会将这个请求发送给Fastcgi服务器,以便Fastcgi服务尽快处理数据并返回关闭通道。FCGI_END_REQUEST该类型用于描述响应数据输出完成后请求的响应结果,类似于HTTP响应报文的状态码,数据结构如下:typedefstruct{unsignedcharappStatusB3;无符号字符appStatusB2;无符号字符appStatusB1;无符号字符appStatusB0;无符号字符协议状态;unsignedcharreserved[3];}FCGI_EndRequestBody;其中appStatus类似于HTTP请求的状态码,主要用于描述数据处理情况,而protocolStatus主要用于描述请求通道。请求是否正常完成或拒绝完成等,赋值范围如下:#defineFCGI_REQUEST_COMPLETE0#defineFCGI_CANT_MPX_CONN1#defineFCGI_OVERLOADED2#defineFCGI_UNKNOWN_ROLE3区别如下:FCGI_REQUEST_COMPLETE:请求正常完成.FCGI_CANT_MPX_CONN:拒绝新请求。当Web服务器通过线路向应用程序发送并发请求时会发生这种情况,该线路旨在每条线路一次处理一个请求。FCGI_OVERLOADED:新请求被拒绝。当应用程序耗尽某些资源(例如数据库连接)时,就会发生这种情况。FCGI_UNKNOWN_ROLE:拒绝新请求。当Web服务器指定应用程序无法识别的角色时,会发生这种情况。但是,一般情况下,大家都只返回appStatus0,protocolStatus0的数据。这其实是因为官方对相关原因的描述不够充分。FCGI_PARAMS的结果主要用于传输键值对数据。毕竟英文翻译就叫parameters。其中该类型为约空间提供了4类结构:typedefstruct{unsignedcharnameLengthB0;/*nameLengthB0>>7==0*/unsignedcharvalueLengthB0;/*valueLengthB0>>7==0*/unsignedcharnameData[nameLength];unsignedcharvalueData[valueLength];}FCGI_NameValuePair11;typedefstruct{unsignedcharnameLengthB0;/*nameLengthB0>>7==0*/unsignedcharvalueLengthB3;/*valueLengthB3>>7==1*/unsignedcharvalueLengthB2;无符号字符值长度B1;无符号字符值长度B0;unsignedcharnameData[nameLength];unsignedcharvalueData[valueLength];}FCGI_NameValuePair14;typedefstruct{unsignedcharnameLengthB3;/*nameLengthB3>>7==1*/unsignedcharnameLengthB2;unsignedcharnameLengthB1;unsignedcharnameLengthB0;无符号字符值长度B0;/*valueLengthB0>>7==0*/unsignedcharnameData[nameLength];unsignedcharvalueData[valueLength];}FCGI_NameValuePair41;typedefstruct{unsignedcharnameLengthB3;/*nameLengthB3>>7==1*/unsignedcharnameLengthB2;unsignedcharnameLengthB1;unsignedcharnameLengthB0;无符号字符值长度B3;/*valueLengthB3>>7==1*/unsignedcharvalueLengthB2;无符号字符值长度B1;无符号字符值长度B0;无符号字符名称数据[名称长度];unsignedcharvalueData[valueLength];}FCGI_NameValuePair44;nameLength、valueLength、nameData和valueData,其中nameLength和valueLength用于描述长度,有1字节和4字节两种方案,构成了以上四种不同的结构,只需要判断第一个字符是否最高段的位为1,如果为1,则长度用4个字节描述,如果为0,则为1个字节。其中1个字节是char类型的大小,4个字节是int类型的大小,分析起来很方便。数据流类型中的FCGI_STDIN、FCGI_STDOUT、FCGI_STDERR、FCGI_DATA都是数据流传输,没有结构,内容中只有数据信息。很暴力,如图:FCGI_GET_VALUES这个类型主要用来查询fastcgiserver的相关性能参数。该结构体复用了FCGI_PARAMS类型的结构体,其中name设置为对应的值,值为空。之后fastcgi服务返回FCGI_GET_VALUES_RESULT类型的数据,并填入值。名称值类型包括:FCGI_MAX_CONNS:此应用程序将接受的最大并发传输连接数,例如“1”或“10”。FCGI_MAX_REQS:此应用程序将接受的最大并发请求数,例如“1”或“50”。FCGI_MPXS_CONNS:此应用程序将接受的多路复用传输连接的最大数量。Fastcgi协议实例0x0000:00000000000000000000000008004500.........E.0x0010:03dc535f40004006e5ba7f0000017f00..S_@.@......0x0020:0001ee2e23283093101a95fd16528018....#(0...R..0x0030:015601d100000101080a000443440004.V....CD..0x0040:43440101000100080000000100000000CD.....................0x0050:000001040001037f01000f1f53435249...............SCRI0x0060:50545f46494c454e414d452f686f6d65PT_FILENAME/home0x0070:2f6d6f62792f6e67696e782f68746d6c/moby/nginx/html0x0080:2f696e6465782e7068700c0051554552/index.php..quer......0x03c0:47457A682D434E2C7A683B713D302E39Gezh-Cn,ZH;;q=0.8.......0x03e0:00000105000100000000.......首先,08000101之前是我们fastcgi的数据包信息0101000100080000,我们一一对应:version:1,type:1,requestId:1,contentLength:8,padding:0,后面是FCGI_BEGIN_REQUEST包:role:1,flags:0,表示使用responder函数。然后分析请求头:version:1,type:4,requestId:1,contentLength:037f,padding:1,所以下面结构为FCGI_PARAMS,继续分析:nameLength:15,valueLength:31,nameData:SCRIPT_FILENAME,valueData:/home/moby/nginx/html/index.php等。消息示例POST请求消息0x0000:00000000000000000000000008004500.........E.0x0010:03740e6d400040062b157f0000017f00.t.m@.@.+.......0x0020:0001d5a423283da1e47f4aa248a38018....#(=...J.H...0x0030:0156016900000101080affffea15ffff.V.i.........0x0040:ea15010100000000000100000000...0x0050:00000104000102FD03000F1F53435249...........SCRI0X0060:50545F46494C454C454E/moby/nginx/html......0x0340:54505f434f4e4e454354494f4e6b6565TP_CONNECTIONkee0x0350:702d616c697665000000010400010000p-alive...0x0360:000001050001000c0400646964693d63............didi=c0x0370:687578696e6700000000010500010000户型.....0x0380:0000..响应信息:0x0000:000000000000000000000004080.........E.0x0010:05b430a740004006069b7f0000017f00..0.@.@...0x0020:00012328ee28d52b8b2b96bdf7d98018..#(.(.+.+......0x0030:016403a900000101080a8d0000000.d............0x0040:bb8d0106000105640400436f6e74656e......d..Conten0x0050:742d747970653a20746578742f68746dt型:。文本/htm0x0060:6c3b20636861727365743D5554462D38l;.CHARSET=utf-80x0070:0d0a0d0a0d0a41727261790a80.x20.x20......0....0....0....0....0....0.............................6461[用户].=>.www-da0x0090:74610a202020205b484f4d455d203d3eta.....[主页].=>......0x0580:337343322e333531350a202020205b74402.3....[0x0590:524551554553545F54494D455D203D3Erequest_time]。=>>>0x05A0:2031353135313638373837343430320A290A290A290A0000.151687444402.15168744402。00000000000000000000000000000000000000000000000000来..0x05c0:0000..等等,可以通过tcpdump工具抓取实例总结Fastcgi协议本身完成了CGI协议的升级,同时具有非常好的扩展性,但是自身功能的限制导致目前市面上的实现实例都具有非常好的协议功能。但了解它有助于熟悉和加深对网络数据传输的印象。如果对php/c++等方向感兴趣,对滴滴出行感兴趣,可以将简历发至739609084@qq.com,福利多多。
