├── Makefile ├── README.md ├── fastcgi.h ├── fcgi.c ├── fcgi.h └── main.c /Makefile: -------------------------------------------------------------------------------- 1 | main:main.o fcgi.o 2 | cc main.o fcgi.o -o main -std=c99 3 | main.o:fcgi.h 4 | cc -c main.c -std=c99 5 | fcgi.o:fcgi.h 6 | cc -c fcgi.c -std=c99 7 | .PHONY:clean 8 | clean: 9 | rm *.o main 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### **Description** 2 | 3 | C language through FastCGI protocol, through php-fpm, php file parsed into html files. 4 | 5 | ### **How to use** 6 | 7 | **Preparation:** 8 | 9 | please make sure you have installed [php-fpm](https://php-fpm.org/). 10 | 11 | Testing environment:CentOS 7 12 | 13 | configuration file:/usr/local/php/etc/php-fpm.conf 14 | 15 | php file:my own user home directory ---/home/Tanswer/index.php 16 | 17 | If you want to run, you need to change the php file path of main.c. 18 | 19 | 20 | 1. Modify the configuration 21 | 22 | After the installation is complete, the default communication method for unix local domain socket communication, our example and php-fpm for **TCP** communication, so we should change the configuration, the ip address is set to **127.0.0.1**, listening port **9000**. As follows: 23 | 24 | ![enter image description here](http://test-1252727452.costj.myqcloud.com/github/2017-12-23%2013-36-24%20%E7%9A%84%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE.png) 25 | 26 | 27 | 2. restart php-fpm 28 | 29 | `sudo service php-fpm restart` 30 | 31 | 3. compiling and running 32 | ``` 33 | git clone git@github.com:Tanswer/FastCGI.git 34 | cd FastCGI && make 35 | ./main 36 | ``` 37 | found an error after running: 38 | `error:Unable to open primary script: /home/Tanswer/index.php (No such file or directory)` 39 | 40 | This is the issue of permissions, the permissions of index.php are: 41 | `-rw-r--r-- 1 Tanswer Tanswer 64 12月 23 13:16 index.php`. From the above default configuration file can be seen, the account that started the php-fpm process is www, and here we change it to Tanswer,as follows: 42 | 43 | ![enter image description here](http://test-1252727452.costj.myqcloud.com/github/2017-12-23%2013-54-15%20%E7%9A%84%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE.png) 44 | 45 | run again `./main`, the result is as follows: 46 | 47 | ![enter image description here](http://test-1252727452.costj.myqcloud.com/github/2017-12-23%2014-01-02%20%E7%9A%84%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE.png) 48 | 49 | 50 | ### **Reference material** 51 | 52 | - [fastcgi协议分析与实例](http://blog.csdn.net/shreck66/article/details/50355729) 53 | - [php-fpm - 启动参数及重要配置详解](http://www.4wei.cn/archives/1002061) 54 | -------------------------------------------------------------------------------- /fastcgi.h: -------------------------------------------------------------------------------- 1 | #ifndef FASTCGI_H 2 | #define FASTCGI_H 3 | 4 | typedef struct 5 | { 6 | unsigned char version; //版本 7 | unsigned char type; //操作类型 8 | unsigned char requestIdB1; //请求id 9 | unsigned char requestIdB0; 10 | unsigned char contentLengthB1; //内容长度 11 | unsigned char contentLengthB0; 12 | unsigned char paddingLength; //填充字节长度 13 | unsigned char reserved; //保留字节 14 | }FCGI_Header; //消息头 15 | 16 | //允许传送的最大数据 65536 17 | #define FCGI_MAX_LENGTH 0xffff 18 | 19 | //上述 FCGI_Header 长度 20 | #define FCGI_HEADER_LEN 8 21 | 22 | //FCGI的版本 23 | #define FCGI_VERSION_1 1 24 | 25 | // FCGI_Header 中 type 的具体值 26 | #define FCGI_BEGIN_REQUEST 1 //开始请求 27 | #define FCGI_ABORT_REQUEST 2 //异常终止请求 28 | #define FCGI_END_REQUEST 3 //正常终止请求 29 | #define FCGI_PARAMS 4 //传递参数 30 | #define FCGI_STDIN 5 //POST 内容传递 31 | #define FCGI_STDOUT 6 //正常响应内容 32 | #define FCGI_STDERR 7 //错误输出 33 | #define FCGI_DATA 8 34 | #define FCGI_GET_VALUES 9 35 | #define FCGI_GET_VALUES_RESULT 10 36 | #define FCGI_UNKNOWN_TYPE 11 //通知 webserver 所请求 type 非正常类型 37 | #define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE) 38 | 39 | 40 | //空的请求 ID 41 | #define FCGI_NULL_REQUEST_ID 0 42 | 43 | /*******************************************/ 44 | 45 | typedef struct 46 | { 47 | unsigned char roleB1; //webserver 所期望php-fpm 扮演的角色,具体取值下面有 48 | unsigned char roleB0; 49 | unsigned char flags; //确定 php-fpm 处理万一次请求之后是否关闭 50 | unsigned char reserved[5]; //保留字段 51 | }FCGI_BeginRequestBody; //开始请求体 52 | 53 | 54 | typedef struct 55 | { 56 | FCGI_Header header; //消息头 57 | FCGI_BeginRequestBody body; //开始请求体 58 | }FCGI_BeginRequestRecord; //完整消息--开始 59 | 60 | 61 | //webserver 期望 php-fpm 扮演的角色(想让php-fpm做什么) 62 | #define FCGI_KEEP_CONN 1 //如果为0则处理玩请求应用就关闭,否则不关闭 63 | #define FCGI_RESPONDER 1 //接受http关联的所有信息,并产生http响应,接受来自webserver的PARAMS环境变量 64 | #define FCGI_AUTHORIZER 2 //对于认证的会关联其http请求,未认证的则关闭请求 65 | #define FCGI_FILTER 3 //过滤web server 中的额外数据流,并产生过滤后的http响应 66 | 67 | 68 | /*******************************************/ 69 | 70 | typedef struct 71 | { 72 | unsigned char appStatusB3; //结束状态,0为正常 73 | unsigned char appStatusB2; 74 | unsigned char appStatusB1; 75 | unsigned char appStatusB0; 76 | unsigned char protocolStatus; //协议状态 77 | unsigned char reserved[3]; 78 | }FCGI_EndRequestBody; //结束消息体 79 | 80 | typedef struct 81 | { 82 | FCGI_Header header; //结束头 83 | FCGI_EndRequestBody body; //结束体 84 | }FCGI_EndRequestRecord; //完整结束消息 85 | 86 | 87 | //几种结束状态 88 | #define FCGI_REQUEST_COMPLETE 0 //正常结束 89 | #define FCGI_CANT_MPX_XONN 1 //拒绝新请求,单线程 90 | #define FCGI_OVERLOADED 2 //拒绝新请求,应用负载了 91 | #define FCGI_UNKNOWN_ROLE 3 //webserver 指定了一个应用不能识别的角色 92 | 93 | 94 | 95 | #define FCGI_MAX_CONNS "FCGI_MAX_CONNS" //可接受的并发传输线路的最大值 96 | 97 | #define FCGI_MAX_REQS "FCGI_MAX_REQS" //可接受并发请求的最大值 98 | 99 | #define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS" //是否多路复用,其状态值也不同 100 | 101 | /*******************************************/ 102 | 103 | typedef struct 104 | { 105 | unsigned char type; 106 | unsigned char reserved[7]; 107 | }FCGI_UnknownTypeBody; 108 | 109 | typedef struct 110 | { 111 | FCGI_Header header; 112 | FCGI_UnknownTypeBody body; 113 | }FCGI_UnKnownTypeRecord; 114 | 115 | 116 | #endif 117 | -------------------------------------------------------------------------------- /fcgi.c: -------------------------------------------------------------------------------- 1 | /* 2 | * @filename: fcgi.c 3 | * @author: Tanswer 4 | * @date: 2017年12月23日 00:00:09 5 | * @description: 6 | */ 7 | 8 | #include "fastcgi.h" 9 | #include "fcgi.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | 21 | static const int PARAMS_BUFF_LEN = 1024; //环境参数buffer的大小 22 | static const int CONTENT_BUFF_LEN = 1024; //内容buffer的大小 23 | 24 | static char *findStartHtml(char *content); 25 | static void getHtmlFromContent(FastCgi_t *c, char *content); 26 | 27 | void FastCgi_init(FastCgi_t *c) 28 | { 29 | c -> sockfd_ = 0; 30 | c -> flag_ = 0; 31 | c -> requestId_ = 0; 32 | } 33 | 34 | void FastCgi_finit(FastCgi_t *c) 35 | { 36 | close(c -> sockfd_); 37 | } 38 | 39 | void setRequestId(FastCgi_t *c, int requestId) 40 | { 41 | c -> requestId_ = requestId; 42 | } 43 | 44 | FCGI_Header makeHeader(int type, int requestId, 45 | int contentLength, int paddingLength) 46 | { 47 | FCGI_Header header; 48 | 49 | header.version = FCGI_VERSION_1; 50 | 51 | header.type = (unsigned char)type; 52 | 53 | /* 两个字段保存请求ID */ 54 | header.requestIdB1 = (unsigned char)((requestId >> 8) & 0xff); 55 | header.requestIdB0 = (unsigned char)(requestId & 0xff); 56 | 57 | /* 两个字段保存内容长度 */ 58 | header.contentLengthB1 = (unsigned char)((contentLength >> 8) & 0xff); 59 | header.contentLengthB0 = (unsigned char)(contentLength & 0xff); 60 | 61 | /* 填充字节的长度 */ 62 | header.paddingLength = (unsigned char)paddingLength; 63 | 64 | /* 保存字节赋为 0 */ 65 | header.reserved = 0; 66 | 67 | return header; 68 | 69 | } 70 | 71 | FCGI_BeginRequestBody makeBeginRequestBody(int role, int keepConnection) 72 | { 73 | FCGI_BeginRequestBody body; 74 | 75 | /* 两个字节保存期望 php-fpm 扮演的角色 */ 76 | body.roleB1 = (unsigned char)((role >> 8) & 0xff); 77 | body.roleB0 = (unsigned char)(role & 0xff); 78 | 79 | /* 大于0长连接,否则短连接 */ 80 | body.flags = (unsigned char)((keepConnection) ? FCGI_KEEP_CONN : 0); 81 | 82 | bzero(&body.reserved, sizeof(body.reserved)); 83 | 84 | return body; 85 | } 86 | 87 | 88 | int makeNameValueBody(char *name, int nameLen, 89 | char *value, int valueLen, 90 | unsigned char *bodyBuffPtr, int *bodyLenPtr) 91 | { 92 | /* 记录 body 的开始位置 */ 93 | unsigned char *startBodyBuffPtr = bodyBuffPtr; 94 | 95 | /* 如果 nameLen 小于128字节 */ 96 | if(nameLen < 128){ 97 | *bodyBuffPtr++ = (unsigned char)nameLen; //nameLen用1个字节保存 98 | } else { 99 | /* nameLen 用 4 个字节保存 */ 100 | *bodyBuffPtr++ = (unsigned char)((nameLen >> 24) | 0x80); 101 | *bodyBuffPtr++ = (unsigned char)(nameLen >> 16); 102 | *bodyBuffPtr++ = (unsigned char)(nameLen >> 8); 103 | *bodyBuffPtr++ = (unsigned char)nameLen; 104 | } 105 | 106 | /* valueLen 小于 128 就用一个字节保存 */ 107 | if(valueLen < 128){ 108 | *bodyBuffPtr++ = (unsigned char)valueLen; 109 | } else { 110 | /* valueLen 用 4 个字节保存 */ 111 | *bodyBuffPtr++ = (unsigned char)((valueLen >> 24) | 0x80); 112 | *bodyBuffPtr++ = (unsigned char)(valueLen >> 16); 113 | *bodyBuffPtr++ = (unsigned char)(valueLen >> 8); 114 | *bodyBuffPtr++ = (unsigned char)valueLen; 115 | } 116 | 117 | /* 将 name 中的字节逐一加入body中的buffer中 */ 118 | for(int i=0; i 0); 155 | 156 | bzero(&server_address, sizeof(server_address)); 157 | 158 | server_address.sin_family = AF_INET; 159 | server_address.sin_addr.s_addr = inet_addr(ip); 160 | server_address.sin_port = htons(9000); 161 | 162 | rc = connect(sockfd, (struct sockaddr *)&server_address, sizeof(server_address)); 163 | assert(rc >= 0); 164 | 165 | c -> sockfd_ = sockfd; 166 | } 167 | int sendStartRequestRecord(FastCgi_t *c) 168 | { 169 | int rc; 170 | FCGI_BeginRequestRecord beginRecord; 171 | 172 | beginRecord.header = makeHeader(FCGI_BEGIN_REQUEST, c->requestId_, sizeof(beginRecord.body),0); 173 | beginRecord.body = makeBeginRequestBody(FCGI_RESPONDER, 0); 174 | 175 | rc = write(c->sockfd_, (char *)&beginRecord, sizeof(beginRecord)); 176 | assert(rc == sizeof(beginRecord)); 177 | 178 | return 1; 179 | } 180 | 181 | 182 | int sendParams(FastCgi_t *c, char *name, char *value) 183 | { 184 | int rc; 185 | 186 | unsigned char bodyBuff[PARAMS_BUFF_LEN]; 187 | 188 | bzero(bodyBuff, sizeof(bodyBuff)); 189 | 190 | /* 保存 body 的长度 */ 191 | int bodyLen; 192 | 193 | /* 生成 PARAMS 参数内容的 body */ 194 | makeNameValueBody(name, strlen(name), value, strlen(value), bodyBuff, &bodyLen); 195 | 196 | FCGI_Header nameValueHeader; 197 | nameValueHeader = makeHeader(FCGI_PARAMS, c->requestId_, bodyLen, 0); 198 | 199 | int nameValueRecordLen = bodyLen + FCGI_HEADER_LEN; 200 | char nameValueRecord[nameValueRecordLen]; 201 | 202 | /* 将头和body拷贝到一块buffer 中只需调用一次write */ 203 | memcpy(nameValueRecord, (char *)&nameValueHeader, FCGI_HEADER_LEN); 204 | memcpy(nameValueRecord + FCGI_HEADER_LEN, bodyBuff, bodyLen); 205 | 206 | rc = write(c->sockfd_, nameValueRecord, nameValueRecordLen); 207 | assert(rc == nameValueRecordLen); 208 | 209 | return 1; 210 | } 211 | 212 | int sendEndRequestRecord(FastCgi_t *c) 213 | { 214 | int rc; 215 | 216 | FCGI_Header endHeader; 217 | endHeader = makeHeader(FCGI_PARAMS, c->requestId_, 0, 0); 218 | 219 | rc = write(c->sockfd_, (char *)&endHeader, FCGI_HEADER_LEN); 220 | assert(rc == FCGI_HEADER_LEN); 221 | 222 | return 1; 223 | } 224 | 225 | int readFromPhp(FastCgi_t *c) 226 | { 227 | FCGI_Header responderHeader; 228 | char content[CONTENT_BUFF_LEN]; 229 | 230 | int contentLen; 231 | char tmp[8]; //用来暂存padding字节 232 | int ret; 233 | 234 | /* 先将头部 8 个字节读出来 */ 235 | while(read(c->sockfd_, &responderHeader,FCGI_HEADER_LEN) > 0){ 236 | if(responderHeader.type == FCGI_STDOUT){ 237 | /* 获取内容长度 */ 238 | contentLen = (responderHeader.contentLengthB1 << 8) + (responderHeader.contentLengthB0); 239 | bzero(content, CONTENT_BUFF_LEN); 240 | 241 | /* 读取获取内容 */ 242 | ret = read(c->sockfd_, content, contentLen); 243 | assert(ret == contentLen); 244 | 245 | 246 | getHtmlFromContent(c, content); 247 | 248 | /* 跳过填充部分 */ 249 | if(responderHeader.paddingLength > 0){ 250 | ret = read(c->sockfd_, tmp, responderHeader.paddingLength); 251 | assert(ret == responderHeader.paddingLength); 252 | } 253 | } //end of type FCGI_STDOUT 254 | else if(responderHeader.type == FCGI_STDERR){ 255 | contentLen = (responderHeader.contentLengthB1 << 8) + (responderHeader.contentLengthB0); 256 | bzero(content, CONTENT_BUFF_LEN); 257 | 258 | ret = read(c->sockfd_, content, contentLen); 259 | assert(ret == contentLen); 260 | 261 | fprintf(stdout, "error:%s\n",content); 262 | 263 | /* 跳过填充部分 */ 264 | if(responderHeader.paddingLength > 0){ 265 | ret = read(c->sockfd_, tmp, responderHeader.paddingLength); 266 | assert(ret == responderHeader.paddingLength); 267 | } 268 | }// end of type FCGI_STDERR 269 | else if(responderHeader.type == FCGI_END_REQUEST){ 270 | FCGI_EndRequestBody endRequest; 271 | 272 | ret = read(c->sockfd_, &endRequest, sizeof(endRequest)); 273 | assert(ret == sizeof(endRequest)); 274 | } 275 | } 276 | 277 | return 1; 278 | } 279 | 280 | char *findStartHtml(char *content) 281 | { 282 | for(; *content != '\0'; content++){ 283 | if(*content == '<') 284 | return content; 285 | } 286 | return NULL; 287 | } 288 | 289 | void getHtmlFromContent(FastCgi_t *c, char *content) 290 | { 291 | /* 保存html内容开始位置 */ 292 | char *pt; 293 | 294 | /* 读取到的content是html内容 */ 295 | if(c->flag_ == 1){ 296 | printf("%s",content); 297 | } else { 298 | if((pt = findStartHtml(content)) != NULL){ 299 | c->flag_ = 1; 300 | for(char *i = pt; *i != '\0'; i++){ 301 | printf("%c",*i); 302 | } 303 | } 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /fcgi.h: -------------------------------------------------------------------------------- 1 | #ifndef FCGI_H 2 | #define FCGI_H 3 | 4 | #include "fastcgi.h" 5 | 6 | typedef struct 7 | { 8 | int sockfd_; //与php-fpm 建立的 sockfd 9 | int requestId_; //record 里的请求ID 10 | int flag_; //用来标志当前读取内容是否为html内容 11 | 12 | 13 | }FastCgi_t; 14 | 15 | void FastCgi_init(FastCgi_t *c); 16 | 17 | void FastCgi_finit(FastCgi_t *c); 18 | 19 | //设置请求Id 20 | void setRequestId(FastCgi_t *c, int requestId); 21 | 22 | //生成头部 23 | FCGI_Header makeHeader(int type, int request, 24 | int contentLength, int paddingLength); 25 | 26 | //生成发起请求的请求体 27 | FCGI_BeginRequestBody makeBeginRequestBody(int role, int keepConnection); 28 | 29 | //生成 PARAMS 的 name-value body 30 | int makeNameValueBody(char *name, int nameLen, 31 | char *value, int valueLen, 32 | unsigned char *bodyBuffPtr, int *bodyLen); 33 | 34 | //获取express_help.conf 配置文件中的 ip 地址 35 | char *getIpFromConf(void); 36 | 37 | //连接php-fpm,如果成功则返回对应的套接字描述符 38 | void startConnect(FastCgi_t *c); 39 | 40 | //发送开始请求记录 41 | int sendStartRequestRecord(FastCgi_t *c); 42 | 43 | //向php-fpm发送name-value参数对 44 | int sendParams(FastCgi_t *c, char *name, char *value); 45 | 46 | //发送结束请求消息 47 | int sendEndRequestRecord(FastCgi_t *c); 48 | 49 | //只读php-fpm 返回内容,读到的内容处理后期再添加 50 | int readFromPhp(FastCgi_t *c); 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * @filename: main.c 3 | * @author: Tanswer 4 | * @date: 2017年12月23日 01:44:32 5 | * @description: 6 | */ 7 | 8 | #include 9 | #include 10 | #include "fcgi.h" 11 | 12 | int main() 13 | { 14 | FastCgi_t *c; 15 | c = (FastCgi_t *)malloc(sizeof(FastCgi_t)); 16 | FastCgi_init(c); 17 | setRequestId(c,1); 18 | startConnect(c); 19 | sendStartRequestRecord(c); 20 | sendParams(c, "SCRIPT_FILENAME","/home/Tanswer/index.php"); 21 | sendParams(c, "REQUEST_METHOD","GET"); 22 | sendEndRequestRecord(c); 23 | readFromPhp(c); 24 | FastCgi_finit(c); 25 | return 0; 26 | } 27 | --------------------------------------------------------------------------------