Heim  >  Artikel  >  Backend-Entwicklung  >  Detaillierte Erklärung des PHP-Ausgabepuffers

Detaillierte Erklärung des PHP-Ausgabepuffers

小云云
小云云Original
2018-03-21 11:32:071754Durchsuche

Jeder weiß, dass es in PHP eine sogenannte „Ausgabepuffer“-Schicht gibt. Dieser Artikel soll erklären, was genau es ist? Wie wird es intern in PHP implementiert? Und wie verwendet man es in einem PHP-Programm? Diese Ebene ist nicht kompliziert, wird aber oft missverstanden und viele PHP-Entwickler beherrschen sie nicht vollständig. Lassen Sie es uns heute gemeinsam herausfinden.

Was wir besprechen werden, basiert auf PHP 5.4 (und höher). Die OB-Schicht in PHP hat viele Änderungen erfahren, um genau zu sein, sie wurde an einigen Stellen komplett neu geschrieben Möglicherweise ist keines davon mit PHP 5.3 kompatibel.

Was ist ein Ausgabepuffer?

Der Ausgabestream von PHP enthält viele Bytes, bei denen es sich normalerweise um Texte handelt, die PHP ausgeben soll. Die meisten dieser Texte werden durch Echo-Anweisungen oder printf()-Funktionen ausgegeben. Es gibt drei Dinge, die Sie über Ausgabepuffer in PHP wissen müssen.

Der erste Punkt ist, dass jede Funktion, die etwas ausgibt, den Ausgabepuffer verwendet. Natürlich ist dies ein in PHP geschriebenes Programm. Wenn Sie eine PHP-Erweiterung schreiben, schreibt die von Ihnen verwendete Funktion (C-Funktion) die Ausgabe möglicherweise direkt in die SAPI-Pufferschicht, ohne die OB-Schicht zu durchlaufen. Informationen zur API-Dokumentation für diese C-Funktionen finden Sie in der Quelldatei main/php_output.h. Diese Datei liefert uns viele weitere Informationen, beispielsweise die Standardpuffergröße.

Das zweite, was Sie wissen müssen, ist, dass die Ausgabepufferschicht nicht die einzige Schicht ist, die zum Puffern der Ausgabe verwendet wird, sondern tatsächlich nur eine von vielen Schichten. Das Letzte, was Sie beachten müssen, ist, dass das Verhalten der Ausgabepufferschicht mit der von Ihnen verwendeten SAPI (Web oder CLI) zusammenhängt. Verschiedene SAPIs können unterschiedliche Verhaltensweisen haben. Schauen wir uns zunächst die Beziehung zwischen diesen Schichten anhand eines Bildes an:

Das obige Bild zeigt die logische Beziehung zwischen den drei Pufferschichten in PHP. Die beiden oben genannten Schichten sind das, was wir normalerweise als „Ausgabepuffer“ bezeichnen, und die letzte ist der Ausgabepuffer in SAPI. Dies sind alles Schichten in PHP, und Puffer erscheinen wieder, wenn die Ausgabebytes PHP verlassen und in die unteren Schichten der Computerarchitektur gelangen (Terminalpuffer, Fast-CGI-Puffer, Webserver-Puffer, Betriebssystempuffer, TCP/IP-Stack-Puffer). Denken Sie daran, dass – außer im Fall von PHP, das in diesem Artikel behandelt wird – grundsätzlich viele Teile einer Software Informationen behalten, bevor sie an den nächsten Teil weitergegeben werden, bis sie schließlich an den Benutzer weitergegeben werden.

Das SAPI der CLI ist etwas Besonderes, deshalb werde ich mich hier darauf konzentrieren. Die CLI erzwingt die Option output_buffer in der INI-Konfiguration auf 0, wodurch der standardmäßige PHP-Ausgabepuffer deaktiviert wird. In der CLI wird also standardmäßig das, was Sie ausgeben möchten, direkt an die SAPI-Schicht übergeben, es sei denn, Sie rufen die Klassenfunktion ob_() manuell auf. Und in der CLI wird der Wert von implicit_flush ebenfalls auf 1 gesetzt. Wir sind oft verwirrt über die Rolle von implicit_flush. Der Quellcode sagt alles: Wenn implicit_flush auf „open“ gesetzt ist (Wert ist 1), wird eine Ausgabe in den SAPI-Puffer geschrieben In der unteren Schicht werden die Daten sofort geleert (Flush bedeutet, dass die Daten in die untere Schicht geschrieben werden und der Puffer geleert wird). Mit anderen Worten: jederzeit, wenn Sie Daten in die CLI schreiben In SAPI wirft CLI SAPI die Daten sofort an die nächste Ebene, normalerweise die Standardausgabepipe. Dafür sind die beiden Funktionen write() und fflush() verantwortlich. Ganz einfach, richtig!

Standard-PHP-Ausgabepuffer

Wenn Sie eine andere SAPI als die CLI verwenden, wie PHP-FPM, verwenden Sie die folgenden drei pufferbezogenen INI-Konfigurationsoptionen:

  • output_buffering

  • implicit_flush

  • output_handler

Bevor wir die Bedeutung dieser Optionen klären, muss zunächst eines erklärt werden: Sie können ini_set() nicht verwenden, um die Werte dieser Optionen zur Laufzeit zu ändern. Die Werte dieser Optionen werden beim Start des PHP-Programms analysiert, bevor Skripte ausgeführt werden. Sie können also möglicherweise ini_set() verwenden, um ihre Werte während der Laufzeit zu ändern, die geänderten Werte werden jedoch nicht wirksam und alles wird zu spät sein, weil die Ausgabepufferschicht bereits aktiv ist. Sie können ihre Werte nur ändern, indem Sie die Datei php.ini bearbeiten oder beim Ausführen eines PHP-Programms die Option -d verwenden.

Standardmäßig setzt die PHP-Distribution output_buffering in php.ini auf 4096 Bytes. Wenn Sie keine php.ini-Datei verwenden (oder beim Starten von PHP nicht die Option -d verwenden), ist der Standardwert 0, wodurch der Ausgabepuffer deaktiviert wird. Wenn Sie den Wert auf „EIN“ setzen, beträgt die Standardgröße des Ausgabepuffers 16 KB. Wie Sie vielleicht schon vermutet haben, hat das Puffern der Ausgabe in einer Webanwendungsumgebung Leistungsvorteile. Die Standardeinstellung von 4k ist ein geeigneter Wert, was bedeutet, dass Sie zunächst 4096 ASCII-Zeichen schreiben und dann mit der darunter liegenden SAPI-Schicht kommunizieren können. Und in einer Webanwendungsumgebung ist die Methode, Nachrichten byteweise über einen Socket zu übertragen, nicht gut für die Leistung. Ein besserer Ansatz wäre, alles auf einmal oder zumindest Stück für Stück auf den Server zu übertragen. Je weniger Daten zwischen den Schichten ausgetauscht werden, desto besser ist die Leistung. Sie sollten immer Ausgabepuffer verfügbar halten und PHP kümmert sich um die Übertragung ihrer Inhalte an den Endbenutzer, nachdem die Anfrage abgeschlossen ist, ohne dass Sie etwas tun müssen.

implicit_flush wurde bereits erwähnt, als es um CLI ging. Bei anderen SAPIs ist implicit_flush standardmäßig auf „Off“ gesetzt, was die richtige Einstellung ist, da das Leeren der SAPI beim Schreiben neuer Daten wahrscheinlich nicht das ist, was Sie wollen. Für das FastCGI-Protokoll besteht der Löschvorgang darin, nach jedem Schreibvorgang ein FastCGI-Array-Paket (Paket) zu senden. Es wäre besser, wenn der FastCGI-Puffer vor dem Senden des Datenpakets gefüllt würde. Wenn Sie den SAPI-Puffer manuell leeren möchten, verwenden Sie die Funktion „flush()“ von PHP. Wenn Sie einmal schreiben und einmal aktualisieren möchten, können Sie die Option implicit_flush in der INI-Konfiguration festlegen oder die Funktion ob_implicit_flush() einmal aufrufen.

output_handler ist eine Rückruffunktion, die den Inhalt des Puffers ändern kann, bevor der Puffer geleert wird. PHP-Erweiterungen bieten viele Rückruffunktionen (Benutzer können auch ihre eigenen Rückruffunktionen schreiben, die weiter unten erläutert werden).

  • ob_gzhandler: Verwenden Sie ext/zlib, um die Ausgabe zu komprimieren

  • mb_output_handler: Verwenden Sie ext/mbstring, um die Zeichenkodierung zu konvertieren

  • ob_iconv_handler: Verwenden Sie ext/iconv, um die Zeichenkodierung zu konvertieren

  • ob_tidyhandler: Verwenden Sie ext/tidy, um den ausgegebenen HTML-Text zu organisieren

  • ob_[inflate/deflate]_handler: Verwenden Sie ext/http, um die Ausgabe zu komprimieren

  • ob_etaghandler: Verwenden Sie ext/http, um HTTP-Etag automatisch generieren

Der Inhalt im Puffer wird an die Rückruffunktion Ihrer Wahl übergeben (es kann nur eine verwendet werden), um die Inhaltskonvertierungsarbeit durchzuführen. Wenn also Sie möchten, dass PHP neben Benutzerinhalten auch den Ausgabepuffer-Callback an den Webserver überträgt. Eine Sache, die jetzt erwähnt werden muss, ist, dass sich die hier erwähnte „Ausgabe“ auf den Nachrichtenkopf (Header) und den Nachrichtentext (Body) bezieht. Der HTTP-Nachrichtenheader ist ebenfalls Teil der OB-Schicht.

Header und Nachrichtentext

Wenn Sie einen Ausgabepuffer (entweder Benutzer oder PHP) verwenden, möchten Sie möglicherweise die HTTP-Header und Nachrichten so senden, wie Sie es möchten. Sie wissen, dass jedes Protokoll Nachrichtenheader senden muss (daher werden sie „Header“ genannt), bevor Sie den Nachrichtentext senden. Wenn Sie jedoch die Ausgabepufferschicht verwenden, übernimmt PHP diese, ohne dass Sie sich darüber Gedanken machen müssen. Tatsächlich verwendet jede PHP-Funktion, die sich auf die Ausgabe von Nachrichtenheadern (header(), setcookie(), session_start()) bezieht, die interne Funktion sapi_header_op(), die nur den Inhalt in den Nachrichtenheaderpuffer schreibt. Wenn Sie dann Inhalte ausgeben, beispielsweise mit printf(), wird der Inhalt in den Ausgabepuffer geschrieben (vorausgesetzt, es gibt nur einen). Wenn der Inhalt dieses Ausgabepuffers gesendet werden muss, sendet PHP zuerst den Nachrichtenheader und dann den Nachrichtentext. PHP erledigt alles für Sie. Wenn Sie sich unwohl fühlen und es selbst tun möchten, bleibt Ihnen nichts anderes übrig, als den Ausgabepuffer zu deaktivieren.

Benutzerausgabepuffer

Für Benutzerausgabepuffer schauen wir uns zunächst ein Beispiel an, um zu sehen, wie es funktioniert und was Sie damit machen können. Auch hier gilt: Wenn Sie die standardmäßige PHP-Ausgabepufferschicht verwenden möchten, können Sie die CLI nicht verwenden, da diese Schicht deaktiviert ist. Das folgende Beispiel verwendet den Standard-PHP-Ausgabepuffer unter Verwendung der internen Webserver-SAPI von 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';

In diesem Beispiel wird die Standard-Ausgabepuffergröße beim Starten von PHP auf 32 Bytes festgelegt, nachdem das Programm ausgeführt wurde schreibt zuerst 31 Bytes hinein und wechselt dann in den Ruhezustand. Zu diesem Zeitpunkt ist der Bildschirm leer und es wird erwartungsgemäß nichts ausgegeben. Nach 2 Sekunden wird der Ruhezustand beendet und ein weiteres Byte wird in den Puffer geschrieben. Es aktualisiert sich sofort und übergibt die Daten an den Puffer der SAPI-Schicht auch sofort in die nächste Schicht gespült. Die Zeichenfolge „aaaaaaaaaa{31 a}b“ wird auf dem Bildschirm angezeigt und das Skript wird dann wieder in den Ruhezustand versetzt. Nach 2 Sekunden wird ein weiteres Byte ausgegeben. Zu diesem Zeitpunkt befinden sich 31 Nullbytes im Puffer, aber das PHP-Skript wurde ausgeführt, sodass der Puffer, der dieses 1 Byte enthält, sofort aktualisiert und auf dem Bildschirm ausgegeben wird. Zeichenfolge '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都会以正确的顺序发送它们。

Der Ausgabepuffer verfügt auch über einen Standardpuffer, der durch Festlegen von 3 INI-Konfigurationsoptionen gesteuert werden kann. Sie sollen verhindern, dass eine große Anzahl kleiner Schreibvorgänge auftritt, was zu einem zu häufigen Zugriff auf die SAPI-Schicht führt, die verbraucht wird Das Netzwerk ist sehr groß und nicht gut für die Leistung. PHP-Erweiterungen können auch Callback-Funktionen definieren und diesen Callback dann für jeden Puffer ausführen. Dafür gibt es bereits viele Anwendungen, wie zum Beispiel die Durchführung von Datenkomprimierung, HTTP-Header-Management und viele andere Dinge.

Verwandte Empfehlungen:

Detaillierte Erklärung, wie Sie Ihre Website durch Aktualisieren des PHP-Puffers beschleunigen können

Beispiel für einen Puffer in PHP Detaillierte Erklärung

PHP detailliertes Verständnis der Verwendung der Aktualisierungspufferfunktion

Das obige ist der detaillierte Inhalt vonDetaillierte Erklärung des PHP-Ausgabepuffers. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn