前言:
基于:
其实看上面两位大佬的博客就已经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版本信息,目前一般定义为1
unsigned char type; //每次发送的消息的类型.相当于flag,具体表示见下面:
unsigned char requestIdB1; //合起来表示本次请求的编号 ID
unsigned 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 1
FCGI_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 建立的 sockfd
int requestId_; //record 里的请求ID
int flag_; //用来标志当前读取内容是否为html内容
} FastCgi_t;
void FastCgi_init(FastCgi_t *c)
{
c->sockfd_ = 0; //与php-fpm 建立的 sockfd
c->flag_ = 0; //record 里的请求ID
c->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,为了发 body
FCGI_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 表示没有 body
sendParams(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。通过上面的介绍,我们可以总结出常见的记录格式
type | record |
---|---|
1 | header(消息头) + 开始请求体(8字节) |
3 | header + 结束请求体(8字节) |
4 | header + name-value长度(8字节) + 具体的name-value |
5,6,7 | header + 具体内容 |
最后,附上我的webserver
项目地址,里边含有使用到的fastcgi
库.求star,求fork,哈哈哈哈...