欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

Fastcgi 协议解析及 getpost 使用实例

发布时间:2025/7/14 编程问答 42 豆豆
生活随笔 收集整理的这篇文章主要介绍了 Fastcgi 协议解析及 getpost 使用实例 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

前言:

基于:

csdn1

娄神的描述

其实看上面两位大佬的博客就已经ojbk了.写的目地主要是自己总结学习一下.

基础:

1.基础的 WebServer应该支持客户端请求静态文件和动态文件.
2. 浏览器是不能够解析动态的php文件的!那么我们编写服务器程序时候如果遇到请求.php动态文件时就应该将php文件翻译为html文件.
3. php-fpm就能够将php文件翻译为html文件.所以我们的webserver将通过进程间通信把php文件交给php-fpm,然后把php-fpm翻译过后的html文件发给客户端即可,(php-fpm)就等价于一个CGI 服务器.
4.那么我们如何才能让php-fpm帮我们解析我们想要翻译成.html文件的.php文件呢?通过**fastcgi协议,其实就是WebServer与php-fpm之间通信的规则(或者说是'语言')**

1. fastcgi 协议

(1) 请求头

   和’任何协议一样,fastcgi协议也有一个消息头或者叫做请求头.其格式是固定的.用以表示消息体的类型和信息.任意一个FastCGI数据包必须以一个8字节的消息头开始

typedef struct {unsigned char version; //FCGI版本信息,目前一般定义为1unsigned char type; //每次发送的消息的类型.相当于flag,具体表示见下面:unsigned char requestIdB1; //合起来表示本次请求的编号 IDunsigned char requestIdB0;unsigned char contentLengthB1; //合起来表示 body 长度unsigned char contentLengthB0;unsigned char paddingLength; //填充字节长度,填充长度不可超过255字节unsigned char reserved; //保留字节 } FCGI_Header; //消息头

type 字段分别是如下含义:

// FCGI_Header 中 type 的具体值 #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 /*传递参数,表明消息中包含的数据为某个name-value对 (web->fastcgi)*/#define FCGI_STDIN 5 /*POST 内容传递,从浏览器接收到的POST请求数据(表单提交等)以消息的形式发给php-fpm时, 这种消息的type就得设为5(web->fastcgi)*/#define FCGI_STDOUT 6 //正常响应内容,php-fpm给web服务器回的正常响应消息的type就设为6(fastcgi->web)#define FCGI_STDERR 7 //php-fpm给web服务器回的错误响应设为7(fastcgi->web)#define FCGI_DATA 8 //向CGI程序传递的额外数据(WEB->FastCGI) #define FCGI_GET_VALUES 9 // 向FastCGI程序询问一些环境变量(WEB->FastCGI) #define FCGI_GET_VALUES_RESULT 10 // 询问环境变量的结果(FastCGI->WEB) #define FCGI_UNKNOWN_TYPE 11 //通知 webserver 所请求 type 非正常类型 #define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE) // 未知类型,可能用作拓展

   requestIdB1 ,requestIdB0 合起来表示本次请求的编号,其中requestIdB1是请求编号的高八位,requestIdB0是请求编号的低八位。这个字段的存在允许Web服务器在一次连接中向FastCGI服务器发送多个不同的请求,只要使用不同的请求编号即可

   contentLengthB1`` contentLengthB0)合起来表示消息头后仍有多少字节的数据(数据长度),contentLengthB1表示其高八位,contentLengthB0表示其低八位。数据长度的表示范围在0~65535(即2^16-1)之间,因而若数据超过65535字节,则必须将之分为多个数据包来传输(编程中要注意这一点)

实例1:makeHeader函数的构造

//FCGI的版本 #define FCGI_VERSION_1 1FCGI_Header makeHeader(int type, int requestId,int contentLength, int paddingLength) {FCGI_Header header;header.version = FCGI_VERSION_1;header.type = (unsigned char)type;/* 两个字段保存请求ID */header.requestIdB1 = (unsigned char)((requestId >> 8) & 0xff);header.requestIdB0 = (unsigned char)(requestId & 0xff);/* 两个字段保存内容长度 */header.contentLengthB1 = (unsigned char)((contentLength >> 8) & 0xff);header.contentLengthB0 = (unsigned char)(contentLength & 0xff);/* 填充字节的长度 */header.paddingLength = (unsigned char)paddingLength;/* 保存字节赋为 0 */header.reserved = 0;return header; }

(2) 消息体:

类似于http协议,在我们发送完消息头之后,我们就需要发送消息体了.那这里肯定还是会因为type的不同而有所不同.

type == FCGI_BEGIN_REQUEST 1 一次请求的开始(web->fastcgi)

这种消息是一中固定的8字节结构,因此我们会推出消息头中的contentLengthBx在这种情况下肯定也是固定的.

typedef struct {unsigned char roleB1; unsigned char roleB0;//合起来表示 webserver 所期望php-fpm 扮演的角色,具体取值下面有unsigned char flags; //确定 php-fpm 处理完一次请求之后是否关闭,flag=1,不关闭unsigned char reserved[5]; //保留字段 } FCGI_BeginRequestBody; //开始请求体//webserver 期望 php-fpm 扮演的角色(想让php-fpm做什么) #define FCGI_RESPONDER 1 //接受http关联的所有信息,并产生http响应,接受来自webserver的PARAMS环境变量#define FCGI_AUTHORIZER 2 //对于认证的会关联其http请求,未认证的则关闭请求#define FCGI_FILTER 3 //过滤web server 中的额外数据流,并产生过滤后的http响应

总的来讲,fastcgi协议中规定了三种角色,有:

enum FCGI_Role {FCGI_RESPONDER = 1, // 响应器,php-fpm接受我们的http所关联的信息,并产生响应FCGI_AUTHORIZER = 2, //认证器,php-fpm会对我们的请求进行认证,认证通过的其会返回响应,认证不通过则关闭请求FCGI_FILTER = 3 // 过滤器,过滤请求中的额外数据流,并产生过滤后的http响应 };

一般,我们的webserver 就把它当作响应器就行了(也就是说把该字段设为FCGI_RESPONDER)

实例2:与php-fpm的连接与第一次请求

typedef struct {int sockfd_; //与php-fpm 建立的 sockfdint requestId_; //record 里的请求IDint flag_; //用来标志当前读取内容是否为html内容} FastCgi_t;void FastCgi_init(FastCgi_t *c) {c->sockfd_ = 0; //与php-fpm 建立的 sockfdc->flag_ = 0; //record 里的请求IDc->requestId_ = 0; //用来标志当前读取内容是否为html内容 }void setRequestId(FastCgi_t *c, int requestId) {c->requestId_ = requestId; }int main() {FastCgi_t *c;c = (FastCgi_t *)malloc(sizeof(FastCgi_t));FastCgi_init(c);setRequestId(c, 1); /*1 用来表明此消息为请求开始的第一个消息*/startConnect(c); //略,就是与127.0.0.1 9000 建立了一个连接sendStartRequestRecord(c); //主要是这个函数!!!!sendParams(c, "SCRIPT_FILENAME", "/home/Shengxi-Liu/WebServer/wwwroot/php/Operation.php");sendParams(c, "REQUEST_METHOD", "POST");... }

sendStartRequestRecord(c) 第一次请求函数:

typedef struct {unsigned char roleB1; unsigned char roleB0;unsigned char flags; //确定 php-fpm 处理完一次请求之后是否关闭unsigned char reserved[5]; //保留字段 } FCGI_BeginRequestBody; //开始请求体typedef struct {FCGI_Header header; //消息头FCGI_BeginRequestBody body; //开始请求体 } FCGI_BeginRequestRecord; //完整消息--开始FCGI_BeginRequestBody makeBeginRequestBody(int role, int keepConnection) {FCGI_BeginRequestBody body;/* 两个字节保存期望 php-fpm 扮演的角色 */body.roleB1 = (unsigned char)((role >> 8) & 0xff);body.roleB0 = (unsigned char)(role & 0xff);/* 大于0长连接,否则短连接 */body.flags = (unsigned char)((keepConnection) ? FCGI_KEEP_CONN : 0);bzero(&body.reserved, sizeof(body.reserved));return body; }int sendStartRequestRecord(FastCgi_t *c) {int rc;FCGI_BeginRequestRecord beginRecord;beginRecord.header = makeHeader(FCGI_BEGIN_REQUEST, c->requestId_, sizeof(beginRecord.body), 0);beginRecord.body = makeBeginRequestBody(FCGI_RESPONDER, 0);rc = write(c->sockfd_, (char *)&beginRecord, sizeof(beginRecord));assert(rc == sizeof(beginRecord));return 1; }

type == FCGI_END_REQUEST 3  //请求处理完毕,正常结束(fastcgi->web)

8字节固定格式:

typedef struct {unsigned char appStatusB3; unsigned char appStatusB2;unsigned char appStatusB1;unsigned char appStatusB0;//合起来表示CGI程序的结束状态,0为正常,此处是一个网络字节序,需要手动转换unsigned char protocolStatus; //fastcgi协议状态,如下:unsigned char reserved[3]; } FCGI_EndRequestBody; //结束消息体 //几种结束状态 #define FCGI_REQUEST_COMPLETE 0 //正常结束#define FCGI_CANT_MPX_XONN 1 //拒绝新请求,单线程 #define FCGI_OVERLOADED 2 //拒绝新请求,应用负载了 #define FCGI_UNKNOWN_ROLE 3 //webserver 指定了一个应用不能识别的角色

type == FCGI_PARAMS 4 传递参数,表明消息中包含的数据为某个name-value对(web->fastcgi)

向php-fpm传递name-value对,可传递自己的,也可以传递fastcgi提供的.fasttcgi提供的name主要有如下这些:


name名含义
*SCRIPT_NAME要执行的CGI程序的名字
*REQUEST_METHOD信息传输方式(GET/POST/PUT等)
*QUERY_STRING查询字符串
CONTENT_LENGTH向CGI标准输入传递的信息长度(应当等于FCGI_STDIN消息contentLength字段之和)
CONTENT_TYPE向CGI标准输入传递的信息类型

其余更多的可参考娄神的boke

type == FCGI_STDIN 5  POST 内容传递,从浏览器接收到的POST请求数据(表单提交等)以消息的形式发给php-fpm时,这种消息的type就得设为5(web->fastcgi)

***实例3 : 完成 post 请求

#include <stdio.h> #include <stdlib.h> #include "fcgi.h" #include <sys/types.h> #include <sys/socket.h>int main() {FastCgi_t *c;c = (FastCgi_t *)malloc(sizeof(FastCgi_t));FastCgi_init(c);setRequestId(c, 1); /*1 用来表明此消息为请求开始的第一个消息*/startConnect(c);sendStartRequestRecord(c);sendParams(c, "SCRIPT_FILENAME", "/home/Shengxi-Liu/WebServer/wwwroot/php/Operation.php");sendParams(c, "REQUEST_METHOD", "POST");sendParams(c, "CONTENT_LENGTH", "17"); // 17 为body的长度 !!!!sendParams(c, "CONTENT_TYPE", "application/x-www-form-urlencoded");sendEndRequestRecord(c);/*FCGI_Header makeHeader(int type, int requestId,int contentLength, int paddingLength)*///设置type==5,为了发 bodyFCGI_Header t = makeHeader(FCGI_STDIN, c->requestId_, 17, 0); // 17 为body的长度 !!!!send(c->sockfd_, &t, sizeof(t), 0);/*发送正式的 body */send(c->sockfd_, "a=20&b=10&c=5&d=6", 17, 0); // 17 为body的长度 !!!!//制造头告诉 body 结束 FCGI_Header endHeader;endHeader = makeHeader(FCGI_STDIN, c->requestId_, 0, 0);send(c->sockfd_, &endHeader, sizeof(endHeader), 0);printf("end-----\n");readFromPhp(c);FastCgi_finit(c);return 0; }

Operation.php文件

<html> <body> <?php#预定义的 $_REQUEST 变量包含了 $_GET、$_POST 和 $_COOKIE 的内容。#$_REQUEST 变量可用来收集通过 GET 和 POST 方法发送的表单数据。$a=$_REQUEST["a"];$b=$_REQUEST["b"];$c=$_REQUEST["c"];$d=$_REQUEST["d"];$result =($a-$b)+($c*$d);echo $a.' - '.$b. ' + ' .$c. ' * ' .$d. " = $result"// echo '1';// var_dump($_REQUEST);// echo $a; ?> </body> </html>

运行截图:

***实例4 : 完成简单 get 请求

见:csdn1

***实例5: 完成带参数 get 请求

#include <stdio.h> #include <stdlib.h> #include "fcgi.h" #include <sys/types.h> #include <sys/socket.h>int main() {FastCgi_t *c;c = (FastCgi_t *)malloc(sizeof(FastCgi_t));FastCgi_init(c);setRequestId(c, 1); /*1 用来表明此消息为请求开始的第一个消息*/startConnect(c);sendStartRequestRecord(c);sendParams(c, "SCRIPT_FILENAME", "/home/Shengxi-Liu/WebServer/wwwroot/php/Operation.php");sendParams(c, "REQUEST_METHOD", "GET");sendParams(c, "CONTENT_LENGTH", "0"); // 0 表示没有 bodysendParams(c, "CONTENT_TYPE", "text/html");sendParams(c, "QUERY_STRING", "a=20&b=10&c=5&d=6");sendEndRequestRecord(c); //告诉cgi程序 head 有多长/*int sendEndRequestRecord(FastCgi_t *c){int rc;FCGI_Header endHeader;endHeader = makeHeader(FCGI_PARAMS, c->requestId_, 0, 0);rc = write(c->sockfd_, (char *)&endHeader, FCGI_HEADER_LEN);assert(rc == FCGI_HEADER_LEN);return 1;}*/printf("end-----\n");readFromPhp(c);FastCgi_finit(c);return 0; }

运行截图:

需要注意的是查询字符串(QUERY_STRING)必须放在sendEndRequestRecord(c);函数之前,想一想http协议是怎样处理带参数的get就要知道了.....

(3) 一个完整消息称为一个 record ,我们每次发送的单位就是record。通过上面的介绍,我们可以总结出常见的记录格式


typerecord
1header(消息头) + 开始请求体(8字节)
3header + 结束请求体(8字节)
4header + name-value长度(8字节) + 具体的name-value
5,6,7header + 具体内容

最后,附上我的webserver项目地址,里边含有使用到的fastcgi库.求star,求fork,哈哈哈哈...

转载于:https://www.cnblogs.com/Tattoo-Welkin/p/10335250.html

总结

以上是生活随笔为你收集整理的Fastcgi 协议解析及 getpost 使用实例的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。