Maison  >  Article  >  développement back-end  >  Explication détaillée du tampon de sortie de PHP

Explication détaillée du tampon de sortie de PHP

小云云
小云云original
2018-03-21 11:32:071752parcourir

Tout le monde sait qu'il existe ce qu'on appelle la couche "tampon de sortie" en PHP. Cet article est là pour expliquer de quoi il s’agit exactement ? Comment est-il implémenté en interne dans PHP ? Et comment l'utiliser dans un programme PHP ? Cette couche n’est pas compliquée, mais elle est souvent mal comprise, et de nombreux développeurs PHP ne la maîtrisent pas totalement. Voyons cela ensemble aujourd'hui.

Ce dont nous allons parler est basé sur PHP 5.4 (et supérieur). La couche OB en PHP a subi de nombreux changements depuis la version 5.4. Pour être précis, elle a été complètement réécrite à certains endroits. mai Aucun d'entre eux n'est compatible avec PHP 5.3.

Qu'est-ce qu'un tampon de sortie ?

Le flux de sortie de PHP contient de nombreux octets, qui sont généralement du texte que les programmeurs souhaitent que PHP génère. La plupart de ces textes sont générés par des instructions echo ou des fonctions printf(). Il y a trois choses que vous devez savoir sur les tampons de sortie en PHP.

Le premier point est que toute fonction qui génère quelque chose utilisera le tampon de sortie. Bien sûr, il s'agit d'un programme écrit en PHP. Si vous écrivez une extension PHP, la fonction que vous utilisez (fonction C) peut écrire la sortie directement dans la couche tampon SAPI sans passer par la couche OB. Vous pouvez en apprendre davantage sur la documentation de l'API pour ces fonctions C dans le fichier source main/php_output.h. Ce fichier nous fournit de nombreuses autres informations, telles que la taille du tampon par défaut.

La deuxième chose que vous devez savoir est que la couche tampon de sortie n'est pas la seule couche utilisée pour tamponner la sortie, c'est en fait juste une couche parmi tant d'autres. La dernière chose que vous devez retenir est que le comportement de la couche tampon de sortie est lié au SAPI que vous utilisez (web ou cli). Différents SAPI peuvent avoir des comportements différents. Examinons d'abord la relation entre ces couches à travers une image :

L'image ci-dessus montre la relation logique entre les trois couches tampon en PHP. Les deux couches ci-dessus sont ce que nous appelons habituellement des « tampons de sortie », et la dernière est le tampon de sortie dans SAPI. Ce sont toutes des couches en PHP, et les tampons réapparaissent lorsque les octets de sortie quittent PHP et entrent dans les couches inférieures de l'architecture informatique (tampons de terminal, tampons fast-cgi, tampons de serveur Web, tampon du système d'exploitation, tampon de pile TCP/IP.). Rappelons qu'en principe général, sauf dans le cas de PHP évoqué dans cet article, de nombreuses parties d'un logiciel conserveront des informations avant de les transmettre à la partie suivante jusqu'à ce qu'elles soient finalement transmises à l'utilisateur.

Le SAPI de la CLI est un peu particulier, je vais donc me concentrer dessus ici. La CLI forcera l'option output_buffer dans la configuration INI à 0, ce qui désactive le tampon de sortie PHP par défaut. Ainsi, dans la CLI, par défaut, ce que vous souhaitez afficher sera transmis directement à la couche SAPI, sauf si vous appelez manuellement la fonction de classe ob_(). Et dans la CLI, la valeur de implicit_flush sera également définie sur 1. Nous sommes souvent confus quant au rôle de implicit_flush Le code source dit tout : lorsque implicit_flush est défini sur open (la valeur est 1), une fois qu'une sortie est écrite dans le tampon SAPI. couche, il sera vidé immédiatement (flush signifie écrire les données dans la couche inférieure et le tampon sera vidé). En d'autres termes : chaque fois que vous écrivez des données dans la CLI En SAPI, CLI SAPI envoie immédiatement les données à sa couche suivante, généralement le canal de sortie standard. Les deux fonctions write() et fflush() sont responsables de cela. Simple, n'est-ce pas !

Tampon de sortie PHP par défaut

Si vous utilisez un SAPI différent de la CLI, comme PHP-FPM, vous utiliserez les trois options de configuration INI suivantes liées au tampon :

  • output_buffering

  • implicit_flush

  • output_handler

Avant de clarifier la signification de ces options, une chose doit d'abord être expliquée. Vous ne pouvez pas utiliser ini_set() pour modifier les valeurs de ces options au moment de l'exécution. Les valeurs de ces options seront analysées au démarrage du programme PHP, avant d'exécuter des scripts, vous pourrez donc peut-être utiliser ini_set() pour modifier leurs valeurs pendant l'exécution, mais les valeurs modifiées ne prendront pas effet. et tout sera trop tard, car la couche tampon de sortie est déjà opérationnelle. Vous ne pouvez modifier leurs valeurs qu'en éditant le fichier php.ini ou en utilisant l'option -d lors de l'exécution d'un programme PHP.

Par défaut, la distribution PHP définira output_buffering sur 4096 octets dans php.ini. Si vous n'utilisez aucun fichier php.ini (ou n'utilisez pas l'option -d au démarrage de PHP), sa valeur par défaut sera 0, ce qui désactive le tampon de sortie. Si vous définissez sa valeur sur "ON", la taille du tampon de sortie par défaut sera de 16 Ko. Comme vous l'avez peut-être deviné, l'utilisation de tampons pour la sortie dans un environnement d'application Web présente des avantages en termes de performances. Le paramètre par défaut de 4k est une valeur appropriée, ce qui signifie que vous pouvez d'abord écrire 4 096 caractères ASCII, puis communiquer avec la couche SAPI ci-dessous. Et dans un environnement d'application Web, la méthode de transmission des messages octet par octet via socket n'est pas bonne pour les performances. Une meilleure approche serait de tout transférer sur le serveur en même temps, ou au moins morceau par morceau. Moins il y a d’échanges de données entre les couches, meilleures sont les performances. Vous devez toujours garder les tampons de sortie disponibles et PHP se chargera de transférer leur contenu à l'utilisateur final une fois la demande terminée sans que vous ayez quoi que ce soit à faire.

implicit_flush a déjà été mentionné en parlant de CLI. Pour les autres SAPI, implicit_flush est désactivé par défaut, ce qui est le paramètre correct, car vider le SAPI chaque fois que de nouvelles données sont écrites n'est probablement pas ce que vous souhaitez. Pour le protocole FastCGI, l'opération de vidage consiste à envoyer un paquet (paquet) de tableau FastCGI après chaque écriture. Il serait préférable que le tampon FastCGI soit rempli avant d'envoyer le paquet de données. Si vous souhaitez vider manuellement le tampon SAPI, utilisez la fonction flush() de PHP. Si vous souhaitez écrire une fois et actualiser une fois, vous pouvez définir l'option implicit_flush dans la configuration INI, ou appeler une fois la fonction ob_implicit_flush().

output_handler est une fonction de rappel qui peut modifier le contenu du tampon avant que le tampon ne soit vidé. Les extensions PHP fournissent de nombreuses fonctions de rappel (les utilisateurs peuvent également écrire leurs propres fonctions de rappel, qui seront abordées ci-dessous).

  • ob_gzhandler : utilisez ext/zlib pour compresser la sortie

  • mb_output_handler : utilisez ext/mbstring pour convertir l'encodage des caractères

  • ob_iconv_handler : utilisez ext/iconv pour convertir l'encodage des caractères

  • ob_tidyhandler : utilisez ext/tidy pour organiser le texte HTML de sortie

  • ob_[inflate/deflate]_handler : utilisez ext/http pour compresser la sortie

  • ob_etaghandler : utilisez ext/http pour générer automatiquement HTTP Etag

Le contenu du tampon sera transmis à la fonction de rappel de votre choix (une seule peut être utilisée) pour effectuer le travail de conversion de contenu, donc si vous souhaitez que PHP transmette au serveur Web En plus du contenu utilisateur, vous pouvez utiliser le rappel du tampon de sortie. Une chose qui doit être mentionnée maintenant est que la « sortie » mentionnée ici fait référence aux en-têtes du message (headers) et au corps du message (body). L'en-tête du message HTTP fait également partie de la couche OB.

En-têtes et corps du message

Lorsque vous utilisez un tampon de sortie (soit utilisateur, soit PHP), vous souhaiterez peut-être envoyer les en-têtes et les messages HTTP de la manière dont vous souhaitez le contenu. Vous savez que tout protocole doit envoyer des en-têtes de message avant d'envoyer le corps du message (c'est pourquoi ils sont appelés "en-têtes"), mais si vous utilisez la couche tampon de sortie, alors PHP les prendra en charge sans que vous ayez à vous en soucier. En fait, toute fonction PHP liée à la sortie des en-têtes de message (header(), setcookie(), session_start()) utilise la fonction interne sapi_header_op(), qui écrit uniquement le contenu dans le tampon d'en-tête du message. Ensuite, lorsque vous sortez du contenu, par exemple en utilisant printf(), le contenu sera écrit dans le tampon de sortie (en supposant qu'il n'y en ait qu'un seul). Lorsque le contenu de ce tampon de sortie doit être envoyé, PHP enverra d'abord l'en-tête du message, puis le corps du message. PHP fait tout pour vous. Si vous vous sentez mal à l'aise et souhaitez le faire vous-même, vous n'avez d'autre choix que de désactiver le tampon de sortie.

Tampons de sortie utilisateur

Pour les tampons de sortie utilisateur, regardons d'abord un exemple pour voir comment cela fonctionne et ce que vous pouvez en faire. Encore une fois, si vous souhaitez utiliser la couche tampon de sortie PHP par défaut, vous ne pouvez pas utiliser la CLI car cette couche est désactivée. L'exemple suivant utilise le tampon de sortie PHP par défaut, en utilisant le serveur Web interne de PHP SAPI :

/* 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';

Dans cet exemple, la taille du tampon de sortie par défaut est définie sur 32 lors du démarrage de PHP Bytes, une fois le programme exécuté, il y écrira d'abord 31 octets, puis entrera en état de veille. À ce stade, l'écran est vide et rien ne sera affiché, comme prévu. Après 2 secondes, le sommeil se termine et un autre octet est écrit. Cet octet remplit le tampon. Il se rafraîchira immédiatement et transmettra les données à l'intérieur du tampon de la couche SAPI car nous avons défini implicit_flush sur 1, donc le tampon de la couche SAPI est. également immédiatement rincé à la couche suivante. La chaîne 'aaaaaaaaaa{31 a}b' apparaîtra à l'écran, puis le script se mettra à nouveau en veille. Après 2 secondes, un autre octet est généré. À ce moment, il y a 31 octets nuls dans le tampon, mais le script PHP a été exécuté, donc le tampon contenant ce 1 octet sera immédiatement actualisé, qui sera affiché à l'écran. Chaîne '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都会以正确的顺序发送它们。

Le tampon de sortie dispose également d'un tampon par défaut, qui peut être contrôlé en définissant 3 options de configuration INI. Elles visent à empêcher qu'un grand nombre de petites opérations d'écriture ne se produisent, entraînant un accès trop fréquent à la couche SAPI, qui consommera. le réseau Très vaste et pas bon pour les performances. Les extensions PHP peuvent également définir des fonctions de rappel, puis exécuter ce rappel sur chaque tampon. Il existe déjà de nombreuses applications pour cela, telles que la compression de données, la gestion des en-têtes HTTP et bien d'autres choses.

Recommandations associées :

Explication détaillée de la façon d'accélérer votre site en actualisant le tampon PHP

Exemple de tampon en php Explication détaillée

php compréhension approfondie de l'utilisation de la fonction de tampon d'actualisation

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn