目次
传统 CGI 工作原理分析
Web 服务器代码
CGI 程序(user.c)
FastCGI 工作原理分析
FastCGI 协议消息流程
FastCGI 头在 PHP 里的定义
字段解释
FCGI_BEGIN_REQUEST 在 PHP 里的定义
FCGI_END_REQUEST 在 PHP 中的定义
ホームページ バックエンド開発 PHPチュートリアル FastCGI プロトコルの分析と PHP での FastCGI の実装

FastCGI プロトコルの分析と PHP での FastCGI の実装

Jun 23, 2016 pm 01:16 PM

在讨论 FastCGI 之前,不得不说传统的 CGI 的工作原理,同时应该大概了解 CGI 1.1 协议

传统 CGI 工作原理分析

客户端访问某个 URL 地址之后,通过 GET/POST/PUT 等方式提交数据,并通过 HTTP 协议向 Web 服务器发出请求,服务器端的 HTTP Daemon(守护进程)将 HTTP 请求里描述的信息通过标准输入 stdin 和环境变量(environment variable)传递给主页指定的 CGI 程序,并启动此应用程序进行处理(包括对数据库的处理),处理结果通过标准输出 stdout 返回给 HTTP Daemon 守护进程,再由 HTTP Daemon 进程通过 HTTP 协议返回给客户端。

上面的这段话可能还是比较抽象,下面通过一次GET请求为例说明。

Web 服务器代码

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/types.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/in.h>#include <string.h>   #define SERV_PORT 9003char* str_join(char *str1, char *str2);char* html_response(char *res, char *buf);  int main(void){    int lfd, cfd;    struct sockaddr_in serv_addr,clin_addr;    socklen_t clin_len;    char buf[1024],web_result[1024];    int len;    FILE *cin;     if((lfd = socket(AF_INET,SOCK_STREAM,0)) == -1){        perror("create socket failed");        exit(1);    }         memset(&serv_addr, 0, sizeof(serv_addr));    serv_addr.sin_family = AF_INET;    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);    serv_addr.sin_port = htons(SERV_PORT);     if(bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)    {        perror("bind error");        exit(1);    }     if(listen(lfd, 128) == -1)    {        perror("listen error");        exit(1);    }        signal(SIGCLD,SIG_IGN);      while(1)    {        clin_len = sizeof(clin_addr);        if ((cfd = accept(lfd, (struct sockaddr *)&clin_addr, &clin_len)) == -1)        {            perror("接收错误\n");            continue;        }        cin = fdopen(cfd, "r");        setbuf(cin, (char *)0);        fgets(buf,1024,cin); //读取第一行        printf("\n%s", buf);        //============================ cgi 环境变量设置演示 ============================                // 例如 "GET /user.cgi?id=1 HTTP/1.1";        char *delim = " ";        char *p;        char *method, *filename, *query_string;        char *query_string_pre = "QUERY_STRING=";        method = strtok(buf,delim);         // GET        p = strtok(NULL,delim);             // /user.cgi?id=1         filename = strtok(p,"?");           // /user.cgi                if (strcmp(filename,"/favicon.ico") == 0)        {            continue;        }        query_string = strtok(NULL,"?");    // id=1        putenv(str_join(query_string_pre,query_string));        //============================ cgi 环境变量设置演示 ============================        int pid = fork();         if (pid > 0)        {            close(cfd);        }        else if (pid == 0)        {            close(lfd);            FILE *stream = popen(str_join(".",filename),"r");            fread(buf,sizeof(char),sizeof(buf),stream);            html_response(web_result,buf);            write(cfd,web_result,sizeof(web_result));            pclose(stream);            close(cfd);            exit(0);        }        else        {            perror("fork error");            exit(1);        }    }      close(lfd);          return 0;}char* str_join(char *str1, char *str2){    char *result = malloc(strlen(str1)+strlen(str2)+1);    if (result == NULL) exit (1);    strcpy(result, str1);    strcat(result, str2);      return result;}char* html_response(char *res, char *buf){    char *html_response_template = "HTTP/1.1 200 OK\r\nContent-Type:text/html\r\nContent-Length: %d\r\nServer: mengkang\r\n\r\n%s";    sprintf(res,html_response_template,strlen(buf),buf);        return res;}
ログイン後にコピー

CGI 程序(user.c)

#include <stdio.h>#include <stdlib.h>// 通过获取的 id 查询用户的信息int main(void){	//============================ 模拟数据库 ============================	typedef struct 	{		int  id;		char *username;		int  age;	} user;	user users[] = {		{},		{			1,			"mengkang.zhou",			18		}	};	//============================ 模拟数据库 ============================	char *query_string;	int id;	query_string = getenv("QUERY_STRING");		if (query_string == NULL)	{		printf("没有输入数据");	} else if (sscanf(query_string,"id=%d",&id) != 1)	{		printf("没有输入id");	} else	{		printf("用户信息查询<br>学号: %d<br>姓名: %s<br>年龄: %d",id,users[id].username,users[id].age);	}		return 0;}
ログイン後にコピー

将上面的 CGI 程序编译成 gcc user.c -o user.cgi ,放在上面web程序的同级目录。

FastCGI 工作原理分析

相对于 CGI/1.1 规范在 Web 服务器在本地 fork 一个子进程执行 CGI 程序,填充 CGI 预定义的环境变量,放入系统环境变量,把 HTTP body 体的 content 通过标准输入传入子进程,处理完毕之后通过标准输出返回给 Web 服务器。 FastCGI 的核心则是取缔传统的 fork-and-execute 方式,减少每次启动的巨大开销(后面以 PHP 为例说明),以常驻的方式来处理请求。

FastCGI 与传统 CGI 模式的区别在于 Web 服务器不是直接执行 CGI 程序了,而是通过 socket 与 FastCGI 响应器(FastCGI 进程管理器)进行交互,Web 服务器需要将 CGI 接口数据封装在遵循 FastCGI 协议包中发送给 FastCGI 响应器程序。正是由于 FastCGI 进程管理器是基于 socket 的,所以也是分布式的,Web服务器和CGI响应器服务器分开部署。

FastCGI 协议消息流程

在官方的介绍文档中列举了一些例子 http://www.fastcgi.com/devkit/doc/fcgi-spec.html#SB

下面我们从比较简单的流程入手理解,如下图所示

例如下面的例子

{FCGI_BEGIN_REQUEST,   1, {FCGI_RESPONDER, 0}}{FCGI_PARAMS,          1, "\013\002SERVER_PORT80\013\016SERVER_ADDR199.170.183.42 ... "}{FCGI_STDIN,           1, "quantity=100&item=3047936"}{FCGI_STDOUT,      	 1, "Content-type: text/html\r\n\r\n<html>\n<head> ... "}{FCGI_END_REQUEST,     1, {0, FCGI_REQUEST_COMPLETE}}
ログイン後にコピー

FastCGI 请求的各个阶段在 PHP 中的定义

typedef enum _fcgi_request_type {	FCGI_BEGIN_REQUEST		=  1, /* [in]                              */	FCGI_ABORT_REQUEST		=  2, /* [in]  (not supported)             */	FCGI_END_REQUEST		=  3, /* [out]                             */	FCGI_PARAMS				=  4, /* [in]  environment variables       */	FCGI_STDIN				=  5, /* [in]  post data                   */	FCGI_STDOUT				=  6, /* [out] response                    */	FCGI_STDERR				=  7, /* [out] errors                      */	FCGI_DATA				=  8, /* [in]  filter data (not supported) */	FCGI_GET_VALUES			=  9, /* [in]                              */	FCGI_GET_VALUES_RESULT	= 10  /* [out]                             */} fcgi_request_type;
ログイン後にコピー

FastCGI 头在 PHP 里的定义

typedef struct _fcgi_header {	unsigned char version;	unsigned char type;	unsigned char requestIdB1;	unsigned char requestIdB0;	unsigned char contentLengthB1;	unsigned char contentLengthB0;	unsigned char paddingLength;	unsigned char reserved;} fcgi_header;
ログイン後にコピー

字段解释

version 标识FastCGI协议版本。

type 标识FastCGI记录类型,也就是记录执行的一般职能。

requestId 标识记录所属的FastCGI请求。

contentLength 记录的contentData组件的字节数。

关于上面的 xxB1 和 xxB0 的协议说明:当两个相邻的结构组件除了后缀“B1”和“B0”之外命名相同时,它表示这两个组件可视为估值为B1<<8 + B0的单个数字。该单个数字的名字是这些组件减去后缀的名字。这个约定归纳了一个由超过两个字节表示的数字的处理方式。

比如协议头中 requestId 和 contentLength 表示的最大值就是 65535

#include <stdio.h>#include <stdlib.h>#include <limits.h>int main(){   unsigned char requestIdB1 = UCHAR_MAX;   unsigned char requestIdB0 = UCHAR_MAX;   printf("%d\n", (requestIdB1 << 8) + requestIdB0); // 65535}
ログイン後にコピー

FCGI_BEGIN_REQUEST 在 PHP 里的定义

typedef struct _fcgi_begin_request {	unsigned char roleB1;	unsigned char roleB0;	unsigned char flags;	unsigned char reserved[5];} fcgi_begin_request;
ログイン後にコピー

Web服务器发送FCGI_BEGIN_REQUEST记录开始一个请求。

字段解释

role 表示Web服务器期望应用扮演的角色。分为三个角色在 PHP 里也有定义

typedef enum _fcgi_role {	FCGI_RESPONDER	= 1,	FCGI_AUTHORIZER	= 2,	FCGI_FILTER		= 3} fcgi_role;
ログイン後にコピー

flags 组件包含一个控制线路关闭的位: flags & FCGI_KEEP_CONN :如果为0,则应用在对本次请求响应后关闭线路。如果非0,应用在对本次请求响应后不会关闭线路;Web服务器为线路保持响应性。

FCGI_END_REQUEST 在 PHP 中的定义

typedef struct _fcgi_end_request {    unsigned char appStatusB3;    unsigned char appStatusB2;    unsigned char appStatusB1;    unsigned char appStatusB0;    unsigned char protocolStatus;    unsigned char reserved[3];} fcgi_end_request;
ログイン後にコピー

appStatus 组件是应用级别的状态码。

protocolStatus 组件是协议级别的状态码; protocolStatus 的值可能是:

FCGI_REQUEST_COMPLETE:请求的正常结束。

FCGI_CANT_MPX_CONN:拒绝新请求。这发生在Web服务器通过一条线路向应用发送并发的请求时,后者被设计为每条线路每次处理一个请求。

FCGI_OVERLOADED:拒绝新请求。这发生在应用用完某些资源时,例如数据库连接。

FCGI_UNKNOWN_ROLE:拒绝新请求。这发生在Web服务器指定了一个应用不能识别的角色时。

protocolStatus 在 PHP 中的定义如下

typedef enum _fcgi_protocol_status {	FCGI_REQUEST_COMPLETE	= 0,	FCGI_CANT_MPX_CONN		= 1,	FCGI_OVERLOADED			= 2,	FCGI_UNKNOWN_ROLE		= 3} dcgi_protocol_status;
ログイン後にコピー

需要注意 dcgi_protocol_status 和 fcgi_role 各个元素的值都是 FastCGI 协议里定义好的,而非 PHP 自定义的。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

PHPのさまざまなエラータイプを説明します(通知、警告、致命的なエラー、解析エラー)。 PHPのさまざまなエラータイプを説明します(通知、警告、致命的なエラー、解析エラー)。 Apr 08, 2025 am 12:03 AM

PHPには4つの主要なエラータイプがあります。1。notice:わずかなものは、未定義の変数へのアクセスなど、プログラムを中断しません。 2。警告:通知よりも深刻で、ファイルを含むなど、プログラムを終了しません。 3。ファタラー:最も深刻なのは、機能を呼び出すなど、プログラムを終了します。 4。ParseError:構文エラーは、エンドタグの追加を忘れるなど、プログラムの実行を防ぎます。

PHPとPython:2つの一般的なプログラミング言語を比較します PHPとPython:2つの一般的なプログラミング言語を比較します Apr 14, 2025 am 12:13 AM

PHPとPythonにはそれぞれ独自の利点があり、プロジェクトの要件に従って選択します。 1.PHPは、特にWebサイトの迅速な開発とメンテナンスに適しています。 2。Pythonは、データサイエンス、機械学習、人工知能に適しており、簡潔な構文を備えており、初心者に適しています。

PHPでの安全なパスワードハッシュ(例:Password_hash、password_verify)を説明します。 MD5またはSHA1を使用してみませんか? PHPでの安全なパスワードハッシュ(例:Password_hash、password_verify)を説明します。 MD5またはSHA1を使用してみませんか? Apr 17, 2025 am 12:06 AM

PHPでは、Password_hashとpassword_verify関数を使用して安全なパスワードハッシュを実装する必要があり、MD5またはSHA1を使用しないでください。 1)password_hashセキュリティを強化するために、塩値を含むハッシュを生成します。 2)password_verifyハッシュ値を比較して、パスワードを確認し、セキュリティを確保します。 3)MD5とSHA1は脆弱であり、塩の値が不足しており、最新のパスワードセキュリティには適していません。

アクション中のPHP:実際の例とアプリケーション アクション中のPHP:実際の例とアプリケーション Apr 14, 2025 am 12:19 AM

PHPは、電子商取引、コンテンツ管理システム、API開発で広く使用されています。 1)eコマース:ショッピングカート機能と支払い処理に使用。 2)コンテンツ管理システム:動的コンテンツの生成とユーザー管理に使用されます。 3)API開発:RESTFUL API開発とAPIセキュリティに使用されます。パフォーマンスの最適化とベストプラクティスを通じて、PHPアプリケーションの効率と保守性が向上します。

HTTPリクエストメソッド(取得、投稿、配置、削除など)とは何ですか?それぞれを使用する必要がありますか? HTTPリクエストメソッド(取得、投稿、配置、削除など)とは何ですか?それぞれを使用する必要がありますか? Apr 09, 2025 am 12:09 AM

HTTPリクエストメソッドには、それぞれリソースを取得、送信、更新、削除するために使用されるGET、POST、PUT、および削除が含まれます。 1. GETメソッドは、リソースを取得するために使用され、読み取り操作に適しています。 2. POSTメソッドはデータの送信に使用され、新しいリソースを作成するためによく使用されます。 3. PUTメソッドは、リソースの更新に使用され、完全な更新に適しています。 4.削除メソッドは、リソースの削除に使用され、削除操作に適しています。

PHP:Web開発の重要な言語 PHP:Web開発の重要な言語 Apr 13, 2025 am 12:08 AM

PHPは、サーバー側で広く使用されているスクリプト言語で、特にWeb開発に適しています。 1.PHPは、HTMLを埋め込み、HTTP要求と応答を処理し、さまざまなデータベースをサポートできます。 2.PHPは、ダイナミックWebコンテンツ、プロセスフォームデータ、アクセスデータベースなどを生成するために使用され、強力なコミュニティサポートとオープンソースリソースを備えています。 3。PHPは解釈された言語であり、実行プロセスには語彙分析、文法分析、編集、実行が含まれます。 4.PHPは、ユーザー登録システムなどの高度なアプリケーションについてMySQLと組み合わせることができます。 5。PHPをデバッグするときは、error_reporting()やvar_dump()などの関数を使用できます。 6. PHPコードを最適化して、キャッシュメカニズムを使用し、データベースクエリを最適化し、組み込み関数を使用します。 7

PHP OOPで、self ::、parent ::、and static ::の違いを説明します。 PHP OOPで、self ::、parent ::、and static ::の違いを説明します。 Apr 09, 2025 am 12:04 AM

Phpoopでは、self ::は現在のクラスを指し、親::は親クラスを指し、静的::は後期静的結合に使用されます。 1.Self ::静的方法と一定の呼び出しに使用されますが、後期静的結合をサポートしていません。 2.Parent ::サブクラスには、親クラスのメソッドを呼び出すために使用され、プライベートメソッドにアクセスできません。 3.Static ::継承と多型に適した後期静的結合をサポートしますが、コードの読みやすさに影響を与える可能性があります。

PHPは、ファイルを安全に処理する方法をどのように処理しますか? PHPは、ファイルを安全に処理する方法をどのように処理しますか? Apr 10, 2025 am 09:37 AM

PHPは、$ \ _ファイル変数を介してファイルのアップロードを処理します。セキュリティを確保するための方法には次のものが含まれます。1。アップロードエラー、2。ファイルの種類とサイズを確認する、3。ファイル上書きを防ぐ、4。ファイルを永続的なストレージの場所に移動します。

See all articles