基于:php
csdn1html
娄神的描述git
其实看上面两位大佬的博客就已经ojbk
了.写的目地主要是本身总结学习一下.github
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
之间通讯的规则(或者说是'语言')**web
和’任何协议
同样,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字节,则必须将之分为多个数据包来传输(编程中要注意这一点)网络
//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; }
相似于http
协议,在咱们发送完消息头以后,咱们就须要发送消息体了.那这里确定仍是会由于type
的不一样而有所不一样.app
这种消息是一中固定的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
)
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; }
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 指定了一个应用不能识别的角色
向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
#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>
运行截图:
见:csdn1
#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
就要知道了.....
type | record |
---|---|
1 | header(消息头) + 开始请求体(8字节) |
3 | header + 结束请求体(8字节) |
4 | header + name-value长度(8字节) + 具体的name-value |
5,6,7 | header + 具体内容 |
最后,附上个人webserver
项目地址,里边含有使用到的fastcgi
库.求star,求fork,哈哈哈哈...