ホームページ > バックエンド開発 > PHPチュートリアル > phpの出力バッファの詳しい説明

phpの出力バッファの詳しい説明

小云云
リリース: 2023-03-21 20:22:02
オリジナル
1843 人が閲覧しました

PHP には「出力バッファ」層と呼ばれるものがあることは誰もが知っています。この記事では、それが一体何なのかを説明します。 PHP内ではどのように実装されているのでしょうか?そして、それをPHPプログラムでどのように使用するのでしょうか?この層は複雑ではありませんが、誤解されることが多く、多くの PHP 開発者は完全にマスターしていません。今日は一緒に考えてみましょう。

これから説明する内容は、PHP 5.4 (およびそれ以降) に基づいています。正確に言うと、PHP の OB レイヤーは完全に書き直されています。 5.3.

出力バッファとは何ですか?

PHP の出力ストリームには大量のバイトが含まれており、通常、これらのテキストはプログラマーが PHP に出力させたいテキストであり、これらのテキストのほとんどは echo ステートメントまたは printf() 関数によって出力されます。 PHP の出力バッファーについて知っておくべきことが 3 つあります。

最初のポイントは、何かを出力する関数は出力バッファーを使用するということです。もちろん、これは PHP で書かれたプログラムに関するものです。 PHP 拡張機能を作成している場合、使用する関数 (C 関数) は、OB 層を経由せずに、出力を SAPI バッファ層に直接書き込むことがあります。これらの C 関数の API ドキュメントについては、ソース ファイル main/php_output.h で確認できます。このファイルには、デフォルトのバッファ サイズなど、その他の多くの情報が含まれています。

2 番目に知っておく必要があるのは、出力バッファー層は出力のバッファリングに使用される唯一の層ではなく、実際には多くの層のうちの 1 つにすぎないということです。最後に覚えておく必要があるのは、出力バッファ層の動作は、使用している SAPI (Web または CLI) に関連しているということです。SA​​PI が異なれば動作も異なる可能性があります。まず、これらのレイヤー間の関係を図で見てみましょう:

上の図は、PHP の 3 つのバッファー層間の論理関係を示しています。上記の 2 つの層は通常「出力バッファ」として認識されるもので、最後の層は SAPI の出力バッファです。これらはすべて PHP の層であり、出力バイトが PHP から出てコンピューター アーキテクチャの下位層 (ターミナル バッファー、高速 CGI バッファー、Web サーバー バッファー、OS バッファー、TCP/IP スタック バッファー) に入るときに、バッファーが再び表示されます。一般原則として、この記事で説明する PHP の場合を除き、ソフトウェアの多くの部分は、最終的にユーザーに渡されるまで、次の部分に情報を渡す前に情報を保持します。

CLI の SAPI は少し特殊なので、ここではそれに焦点を当てます。 CLI は、INI 設定の output_buffer オプションを強制的に 0 に設定します。これにより、デフォルトの PHP 出力バッファが無効になります。そのため、CLI では、ob_() クラス関数を手動で呼び出さない限り、デフォルトで出力したい内容が SAPI 層に直接渡されます。 CLI では、implicit_flush の値も 1 に設定されます。 implicit_flush の役割についてよく混乱しますが、ソース コードがすべてを物語っています。implicit_flush が on (値が 1) に設定されている場合、出力は SAPI バッファ層に書き込まれるとすぐにフラッシュされます (これは、データが下位層に書き込まれ、バッファがクリアされることを意味します)。言い換えれば、CLI にデータを書き込むたびに、 SAPI では、CLI SAPI はすぐにデータを次の層にスローします。通常は、2 つの関数 write() と fflush() がこれを行います。シンプルですね!

デフォルトの PHP 出力バッファ

PHP-FPM など、CLI とは異なる SAPI を使用する場合は、次の 3 つのバッファ関連の INI 設定オプションを使用します:

  • output_buffering

  • implicit_flush

    output_handler

  • これらのオプションの意味を明確にする前に、実行時にこれらのオプションの値を変更するために ini_set() を使用できないことを説明する必要があります。これらのオプションの値は、PHP プログラムの起動時に、スクリプトを実行する前に解析されるため、実行時に ini_set() を使用して値を変更できる場合がありますが、変更された値は有効になりません出力バッファ層がすでに稼働しているため、すべてが遅すぎます。値を変更するには、php.ini ファイルを編集するか、PHP プログラムの実行時に -d オプションを使用する必要があります。
  • デフォルトでは、PHP ディストリビューションは php.ini の output_buffering を 4096 バイトに設定します。 php.ini ファイルを使用しない場合 (または、PHP の起動時に -d オプションを使用しない場合)、デフォルト値は 0 になり、出力バッファが無効になります。値を「ON」に設定すると、デフォルトの出力バッファ サイズは 16kb になります。ご想像のとおり、Web アプリケーション環境で出力にバッファーを使用すると、パフォーマンスが向上します。デフォルト設定の 4k は適切な値です。これは、最初に 4096 個の ASCII 文字を書き込んでから、その下の SAPI レイヤーと通信できることを意味します。また、Web アプリケーション環境では、ソケットを介してバイトごとにメッセージを送信する方法はパフォーマンスに良くありません。より良いアプローチは、すべてを一度にサーバーに転送するか、少なくとも部分的に転送することです。レイヤー間のデータ交換が少ないほど、パフォーマンスは向上します。常に出力バッファを利用可能な状態にしておかなければなりません。リクエストが完了した後、何もしなくても PHP がその内容をエンド ユーザーに転送します。

    implicit_flush については、CLI について話すときに以前に言及しました。他の SAPI の場合、implicit_flush はデフォルトでオフに設定されています。これは正しい設定です。新しいデータが書き込まれるたびに SAPI をフラッシュすることはおそらく望ましいことではないからです。 FastCGI プロトコルの場合、フラッシュ操作は各書き込み後に FastCGI 配列パケット (パケット) を送信することです。データ パケットを送信する前に FastCGI バッファーが満たされている方がよいでしょう。 SAPI バッファを手動でフラッシュする場合は、PHP の flash() 関数を使用します。 1 回の書き込みと 1 回のリフレッシュを行う場合は、INI 設定で implicit_flush オプションを設定するか、ob_implicit_flush() 関数を 1 回呼び出します。

    output_handler は、バッファがフラッシュされる前にバッファの内容を変更できるコールバック関数です。 PHP 拡張機能は、多くのコールバック関数を提供します (ユーザーは独自のコールバック関数を作成することもできます。これについては後述します)。

    • ob_g​​zhandler: ext/zlibを使用して出力を圧縮します

    • mb_output_handler: ext/mbstringを使用して文字エンコーディングを変換します

    • ob_iconv_handler: ext/iconvを使用して文字エンコーディングを変換します

    • ob_tidyhandler: use ext/ tiny 出力 HTML テキストを整理します

    • ob_[inflate/deflate]_handler: ext/http を使用して出力を圧縮します

    • ob_etaghandler: ext/http を使用して HTTP Etag を自動的に生成します

    バッファ内コンテンツは、コンテンツ変換作業を実行するために選択したコールバック関数 (1 つだけ使用できます) に渡されるため、PHP が Web サーバーとユーザーに送信するコンテンツを取得したい場合は、次のようにすることができます。出力バッファのコールバックを使用します。ここで言及する必要があるのは、ここで言う「出力」とはメッセージ ヘッダーとメッセージ本文を指すということです。 HTTP メッセージ ヘッダーも OB 層の一部です。

    ヘッダーとメッセージ本文

    出力バッファー (ユーザーまたは PHP のいずれか) を使用するときは、HTTP ヘッダーとコンテンツを希望どおりに送信したいと思うでしょう。どのプロトコルでも、メッセージ本文を送信する前にメッセージ ヘッダー (そのため「ヘッダー」と呼ばれる) を送信する必要があることはご存知でしょうが、出力バッファ層を使用する場合は、心配することなく PHP がメッセージ ヘッダーを引き継ぎます。実際、メッセージ ヘッダーの出力に関連するすべての PHP 関数 (header()、setcookie()、session_start()) は、コンテンツをメッセージ ヘッダー バッファーに書き込むだけの内部 sapi_header_op() 関数を使用します。その後、たとえば printf() を使用してコンテンツを出力すると、コンテンツは出力バッファー (1 つしかない場合) に書き込まれます。この出力バッファ内のコンテンツを送信する必要がある場合、PHP は最初にメッセージ ヘッダーを送信し、次にメッセージ本文を送信します。 PHP がすべてを行います。不快で自分でやりたい場合は、出力バッファを無効にする以外に選択肢はありません。

    ユーザー出力バッファー (ユーザー出力バッファー)

    ユーザー出力バッファーについては、まず例を見て、それがどのように機能し、それを使用して何ができるかを見てみましょう。繰り返しますが、デフォルトの PHP 出力バッファ層を使用する場合は、この層が無効になっているため、CLI は使用できません。次の例では、PHP の内部 Web サーバー SAPI を使用して、デフォルトの PHP 出力バッファを使用します。

    /* launched via php -doutput_buffering=32 -dimplicit_flush=1 -S127.0.0.1:8080 -t/var/www */echo str_repeat('a', 31);
    sleep(3);
    echo 'b';
    sleep(3);
    echo 'c';
    ログイン後にコピー

    この例では、PHP の起動時に、デフォルトの出力バッファ サイズが 32 バイトに設定され、プログラムの実行後に 31 バイトが書き込まれます。最初にそれが実行され、その後スリープ状態になります。この時点では、予想通り、画面は空で何も出力されません。 2 秒後、スリープが終了し、別のバイトがバッファに書き込まれます。これはすぐに更新され、内部のデータが SAPI 層のバッファに渡されます。また、すぐに次の層にフラッシュされます。文字列「aaaaaaaaaa{31 a}b」が画面に表示され、スクリプトは再びスリープ状態になります。 2 秒後にさらに 1 バイトが出力されます。この時点でバッファーには 31 バイトの null バイトがありますが、PHP スクリプトは実行されているため、この 1 バイトを含むバッファーはすぐに更新され、画面に出力されます。文字列「c」。

    从这个示例我们可以看到默认PHP输出缓冲区是如何工作的。我们没有调用任何跟缓冲区相关的函数,但这并不意味这它不存在,你要认识到它就存在当前程序的运行环境中(在非CLI模式中才有效)。

    OK,现在开始讨论用户输出缓冲区,它通过调用ob_start()创建,我们可以创建很多这种缓冲区(至到内存耗尽为止),这些缓冲区组成一个堆栈结构,每个新建缓冲区都会堆叠到之前的缓冲区上,每当它被填满或者溢出,都会执行刷新操作,然后把其中的数据传递给下一个缓冲区。

    ob_start(function($ctc) { static $a = 0; return $a++ . '- ' . $ctc . "\n";}, 10);
    ob_start(function($ctc) { return ucfirst($ctc); }, 3);echo "fo";
    sleep(2);echo 'o';
    sleep(2);echo "barbazz";
    sleep(2);echo "hello";/* 0- FooBarbazz\n 1- Hello\n */
    ログイン後にコピー
    在此我代替原作者讲解下这个示例。我们假设第一个ob_start创建的用户缓冲区为缓冲区1,第二个ob_start创建的为缓冲区2。按照栈的后进先出原则,任何输出都会先存放到缓冲区2中。

    缓冲区2的大小为3个字节,所以第一个echo语句输出的字符串'fo'(2个字节)会先存放在缓冲区2中,还差一个字符,当第二echo语句输出的'o'后,缓冲区2满了,所以它会刷新(flush),在刷新之前会先调用ob_start()的回调函数,这个函数会将缓冲区内的字符串的首字母转换为大写,所以输出为'Foo'。然后它会被保存在缓冲区1中,缓冲区1的大小为10。

    第三个echo语句会输出'barbazz',它还是会先放到缓冲区2中,这个字符串有7个字节,缓冲区2已经溢出了,所以它会立即刷新,调用回调函数得到的结果为'Barbazz',然后被传递到缓冲区1中。这个时候缓冲区1中保存了'FooBarbazz',10个字符,缓冲区1会刷新,同样的先会调用ob_start()的回调函数,缓冲区1的回调函数会在字符串前面添加行号,以及在尾部添加一个回车符,所以输出的第一行是'o- FooBarbazz'。

    最后一个echo语句输出了字符串'hello',它大于3个字符,所以会触发缓冲区2刷新,因为此时脚本已执行完毕,所以也会立即刷新缓冲区1,最终得到的第二行输出为'1- Hello'。

    输出缓冲区的内部实现

    自5.4版后,整个缓冲区层都被重写了(由Michael Wallner完成)。之前的代码很垃圾,很多事情都做不了,并且有很多bug。这篇文章会给你提供更多相关信息。所以PHP 5.4才会对这部分进行重新,现在的设计更好,代码也更整洁,添加了一些新特性,跟5.3版的不兼容问题也很少。赞一个!

    其中最赞的一个特性是扩展可以声明它自己的输出缓冲区回调与其他扩展提供的回调冲突。在此之前,这是不可能的,之前如果要开发使用输出缓冲区的扩展,必须先搞清楚所有其他提供了缓冲区回调的扩展可能带来的影响。

    下面是一个简单的示例,它展示了怎样注册一个回调函数来将缓冲区中的字符转换为大写,这个示例的代码可能不是很好,但是足以满足我们的目的:

    #ifdef HAVE_CONFIG_H
    #include "config.h"#endif
    #include "php.h"#include "php_ini.h"#include "main/php_output.h"#include "php_myext.h"static int myext_output_handler(void **nothing, php_output_context *output_context){    char *dup = NULL;
        dup = estrndup(output_context->in.data, output_context->in.used);
        php_strtoupper(dup, output_context->in.used);
        output_context->out.data = dup;
        output_context->out.used = output_context->in.used;
        output_context->out.free = 1;    return SUCCESS;
    }
    PHP_RINIT_FUNCTION(myext)
    {
        php_output_handler *handler;
        handler = php_output_handler_create_internal("myext handler", sizeof("myext handler") -1, myext_output_handler, /* PHP_OUTPUT_HANDLER_DEFAULT_SIZE */ 128, PHP_OUTPUT_HANDLER_STDFLAGS);
        php_output_handler_start(handler);    return SUCCESS;
    }
    zend_module_entry myext_module_entry = {
        STANDARD_MODULE_HEADER,    "myext",
        NULL, /* Function entries */
        NULL,
        NULL, /* Module shutdown */
        PHP_RINIT(myext), /* Request init */
        NULL, /* Request shutdown */
        NULL, /* Module information */
        "0.1", /* Replace with version number for your extension */
        STANDARD_MODULE_PROPERTIES
    };
    #ifdef COMPILE_DL_MYEXTZEND_GET_MODULE(myext)#endif
    ログイン後にコピー

    陷阱

    大部分陷阱都已经揭示出来了。有一些是逻辑的问题,有一些是隐藏的。逻辑方面,最明显的是你不应该在输出缓冲区回调函数内调用任何缓冲区相关的函数,也不要在回调函数中输出任何东西。

    相对不太明显的是有些PHP的内部函数也使用了输出缓冲区,它们会叠加到其他的缓冲区上,这些函数会填满自己的缓冲区然后刷新,或者是返回里面的内容。print_r()、highlight_file()和highlight_file::handle()都是这类函数。你不应该在输出缓冲区的回调函数中使用这些函数。这种行为会导致未定义的错误,或者至少得不到你期望的结果。

    总结

    输出层(output layer)就像一个网,它会把所有从PHP”遗漏“的输出圈起来,然后把它们保存到一个大小固定的缓冲区中。当缓冲区被填满了的时,里面的内容会刷新(写入)到下一层(如果有的话),或者是写入到下面的逻辑层:SAPI缓冲区。开发人员可以控制缓冲区的数量、大小以及在每个缓冲区层可以执行的操作(清除、刷新和删除)。这种方式非常灵活,它允许库和框架设计者可以完全控制它们自己输出的内容,并把它们放到一个全局的缓冲区中。对于输出,我们需要知道任何输出流的内容和任何HTTP消息头,PHP都会以正确的顺序发送它们。

    出力バッファにはデフォルトのバッファもあり、3 つの INI 設定オプションを設定することで制御できます。これらは、大量の小さな書き込み操作が発生して、大量の消費が発生する SAPI 層へのアクセスが頻繁になるのを防ぐためのものです。ネットワークが不安定になり、パフォーマンスが良くありません。 PHP 拡張機能では、コールバック関数を定義し、各バッファーでこのコールバックを実行することもできます。これには、データ圧縮、HTTP ヘッダー管理などのアプリケーションがすでに多数あります。

    関連する推奨事項:

    PHP バッファを更新してサイトを高速化する方法の詳細な説明

    php のバッファ例の詳細な説明

    phpリフレッシュ バッファ関数の使用法を深く理解する

以上がphpの出力バッファの詳しい説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート