FastCGI の概要
CGI の正式名称は「Common Gateway Interface」で、クライアントが Web ブラウザから Web サーバー上で実行されているプログラムにデータを要求できるようになります。 CGI は、クライアントとプログラムの間でデータを転送するための標準を記述します。 CGI の目的の 1 つは、どの言語にも依存しないことであるため、言語に標準の入力、出力、および環境変数がある限り、CGI はどの言語でも作成できます。 php、perl、tcl など。
FastCGI は寿命の長い CGI のようなもので、起動していれば毎回 fork する手間がかかりません。 CGI について重要なこと)、非常に批判されている fork-and-execute パターン)。 また、分散コンピューティングもサポートしています。つまり、FastCGI プログラムを Web サイト サーバー以外のホストで実行し、他の Web サイト サーバーからのリクエストを受け入れることができます。
FastCGI は、言語に依存しないスケーラブルなアーキテクチャの CGI オープン拡張機能であり、その主な動作は CGI インタープリター プロセスをメモリ内に保持し、より高いパフォーマンスを実現することです。 ご存知のとおり、CGI インタープリタの繰り返しロードが CGI パフォーマンス低下の主な原因です。CGI インタープリタがメモリ内に残り、FastCGI プロセス マネージャーのスケジューリングを受け入れる場合、良好なパフォーマンス、スケーラビリティ、フェイルオーバー機能などが提供されます。
一般に、FastCGI の全体的なワークフローは次のようになります。
Web サーバーの起動時に FastCGI プロセス マネージャー (IIS ISAPI または Apache モジュール) をロードします。FastCGI プロセス マネージャーは自身を初期化し、複数の CGI インタープリター プロセス (複数の php-cgi が表示されます) を開始し、Web サーバーからの接続を待ちます。 クライアント要求が Web サーバーに到達すると、FastCGI プロセス マネージャーが CGI インタープリターを選択して接続します。 Web サーバーは、CGI 環境変数と標準入力を FastCGI サブプロセス php-cgi に送信します。 FastCGI サブプロセスは処理を完了すると、同じ接続から標準出力とエラー情報を Web サーバーに返します。 FastCGI 子プロセスが接続を閉じると、リクエストが処理されます。次に、FastCGI 子プロセスは、(Web サーバーで実行されている) FastCGI プロセス マネージャーからの次の接続を待機して処理します。 CGI モードでは、php-cgi はこの時点で終了します。 PHP での CGI 実装PHP の CGI 実装の本質は、ソケット プログラミングを通じて TCP または UDP プロトコル サーバーを実装することです。起動時に、TCP/UDP プロトコル サーバーのソケット リスナーを作成し、関連する処理リクエストを受け取ります。これは単なるリクエストの処理であり、モジュールの初期化、sapi の初期化、モジュールのクローズ、sapi のクローズなどを追加すると、CGI のライフサイクル全体が構成されます。
TCP サーバー側では、通常、次の手順が実行されます:
ソケット関数を呼び出して、TCP のストリーミング ソケットを作成します。サーバーのローカル アドレスを以前に作成したアドレスと比較します。ソケット バインディング。クライアントがこのソケットに接続する複数の接続を待機するために、listen 関数を呼び出します。 accept 関数は、クライアントプロセスが接続を確立するために connect 関数を呼び出すまでブロック状態に入ります。クライアントとの接続が確立されると、サーバーはデータを処理した後、read_stream 関数を呼び出します。 write 関数を使用してクライアントに応答を送信します。TCP 上のクライアント/サーバー トランザクションのタイミング シーケンスを図 2.6 に示します。
PHP の CGI 実装は、cgi_main.c ファイルの main 関数から始まり、fastcgi で定義された初期化と監視が行われます。 c ファイルが呼び出されます。 TCP プロセスを比較して、PHP による TCP プロトコルの実装を見ていきます。PHP 自体もこれらのプロセスを実装しますが、一部のプロセスは main 関数の関数実装にカプセル化されます。 TCP 操作プロセスに対応して、PHP は最初にソケットを作成し、ソケットをバインドし、リスナーを作成します。
if (bindpath) { fcgi_fd = fcgi_listen(bindpath, 128); // 实现socket监听,调用fcgi_init初始化 ...}
fastcgi.c ファイルでは、fcgi_listen 関数は主にソケットの作成、バインド、およびリスニングの開始に使用されます。これで前のステップが完了しました。リストされた TCP プロセスの最初の 3 つの段階、
if ((listen_socket = socket(sa.sa.sa_family, SOCK_STREAM, 0)) < 0 || ... bind(listen_socket, (struct sockaddr *) &sa, sock_len) < 0 || listen(listen_socket, backlog) < 0) { ... }
サーバーの初期化が完了すると、プロセスは accept 関数を呼び出してブロック状態に入ります。メイン関数には次のコードが表示されます。
上記のコードは生成された子プロセスであり、ユーザーのリクエストを待ちます。 fcgi_accept_request 関数では、プログラムは accept 関数を呼び出して、新しく作成されたプロセスをブロックします。 ユーザーのリクエストが到着すると、fcgi_accept_request 関数はユーザーのリクエストを処理するかどうかを決定します。これにより、特定の接続リクエストがフィルターされ、制限された顧客からのリクエストが無視されます。プログラムがユーザーのリクエストを受け入れると、リクエスト情報が分析され、関連する変数が変更されます。対応する変数に書き込みます。 リクエストの内容を読み取るときに、safe_read メソッドが呼び出されます。以下に示すように: [main() ->fcgi_accept_request() ->fcgi_read_request() ->safe_read()]上記は、ユーザーのリクエストデータを読み取るサーバー側に対応します。
在请求初始化完成,读取请求完毕后,就该处理请求的PHP文件了。 假设此次请求为PHP_MODE_STANDARD则会调用php_execute_script执行PHP文件。 在此函数中它先初始化此文件相关的一些内容,然后再调用zend_execute_scripts函数,对PHP文件进行词法分析和语法分析,生成中间代码, 并执行zend_execute函数,从而执行这些中间代码。关于整个脚本的执行请参见第三节 脚本的执行。
在处理完用户的请求后,服务器端将返回信息给客户端,此时在main函数中调用的是fcgi_finish_request(&request, 1); fcgi_finish_request函数定义在fastcgi.c文件中,其代码如下:
int fcgi_finish_request(fcgi_request *req, int force_close){int ret = 1;if (req->fd >= 0) { if (!req->closed) { ret = fcgi_flush(req, 1); req->closed = 1; } fcgi_close(req, force_close, 1);}return ret;}
如上,当socket处于打开状态,并且请求未关闭,则会将执行后的结果刷到客户端,并将请求的关闭设置为真。 将数据刷到客户端的程序调用的是fcgi_flush函数。在此函数中,关键是在于答应头的构造和写操作。 程序的写操作是调用的safe_write函数,而safe_write函数中对于最终的写操作针对win和linux环境做了区分, 在Win32下,如果是TCP连接则用send函数,如果是非TCP则和非win环境一样使用write函数。如下代码:
#ifdef _WIN32if (!req->tcp) { ret = write(req->fd, ((char*)buf)+n, count-n);} else { ret = send(req->fd, ((char*)buf)+n, count-n, 0); if (ret <= 0) { errno = WSAGetLastError(); }}#elseret = write(req->fd, ((char*)buf)+n, count-n);#endif
在发送了请求的应答后,服务器端将会执行关闭操作,仅限于CGI本身的关闭,程序执行的是fcgi_close函数。 fcgi_close函数在前面提的fcgi_finish_request函数中,在请求应答完后执行。同样,对于win平台和非win平台有不同的处理。 其中对于非win平台调用的是write函数。
以上是一个TCP服务器端实现的简单说明。这只是我们PHP的CGI模式的基础,在这个基础上PHP增加了更多的功能。 在前面的章节中我们提到了每个SAPI都有一个专属于它们自己的sapi_module_struct结构:cgi_sapi_module,其代码定义如下:
/* {{{ sapi_module_struct cgi_sapi_module */static sapi_module_struct cgi_sapi_module = {"cgi-fcgi", /* name */"CGI/FastCGI", /* pretty name */php_cgi_startup, /* startup */php_module_shutdown_wrapper, /* shutdown */sapi_cgi_activate, /* activate */sapi_cgi_deactivate, /* deactivate */sapi_cgibin_ub_write, /* unbuffered write */sapi_cgibin_flush, /* flush */NULL, /* get uid */sapi_cgibin_getenv, /* getenv */php_error, /* error handler */NULL, /* header handler */sapi_cgi_send_headers, /* send headers handler */NULL, /* send header handler */sapi_cgi_read_post, /* read POST data */sapi_cgi_read_cookies, /* read Cookies */sapi_cgi_register_variables, /* register server variables */sapi_cgi_log_message, /* Log message */NULL, /* Get request time */NULL, /* Child terminate */STANDARD_SAPI_MODULE_PROPERTIES};/* }}} */
同样,以读取cookie为例,当我们在CGI环境下,在PHP中调用读取Cookie时, 最终获取的数据的位置是在激活SAPI时。它所调用的方法是read_cookies。
SG(request_info).cookie_data = sapi_module.read_cookies(TSRMLS_C);
对于每一个服务器在加载时,我们都指定了sapi_module,在第一小节的Apache模块方式中, sapi_module是apache2_sapi_module,其对应read_cookies方法的是php_apache_sapi_read_cookies函数, 而在我们这里,读取cookie的函数是sapi_cgi_read_cookies。 再次说明定义SAPI结构的理由:统一接口,面向接口的编程,具有更好的扩展性和适应性。
参考资料 http://www.fastcgi.com/drupal/node/2 http://baike.baidu.com/view/641394.htm这是TIPI项目第二章第三小节修改后的版本内容,虽然还有一些问题,但是较之前的版本还是有所进步, 至少我们在努力…
本文地址: PHP的CGI实现 文章出处: PHP源码阅读,PHP设计模式,PHP学习笔记,项目管理-胖胖的空间
转载请以链接形式注明原始出处和作者,谢绝不尊重版权者抄袭!