Страницы

Поиск по вопросам

понедельник, 25 ноября 2019 г.

Как устроена работа буферизации в php?


Существует 2 вида буферов в php:


Cистемный буфер
Пользовательский буфер


Так могут возникнуть проблемы с работой с буфером.
Для работы с системным буфером есть одна единственная функция - flush(). Для работ
с пользовательским буфером остальные функции с приставкой ob_(Функция ob_implicit_flush() - не относится к данному списку, она больше подходит к системному буферу).

Код для демонстрации:


  Внимание использована инструкция в файле .htaccess
  php_value output_buffering off


";
for ($i = 1; $i <= 10; $i++) {
    echo $i.":(".mt_rand(0,1000).") ";
    flush();
    sleep(1);
}
echo "
LVL of buffer: ".ob_get_level()."
"; header("Hello"); session_start(); header("Support title"); echo "

Thx!

"; ?> Этим примером я хотел показать что при использовании только системного буфера м можем выводить кусочками сообщение на экран. Минусом является то что если захотим отправить заголовки или сессию то будет ошибка, т.к после отправки первого сообщения на экран всевозможные заголовки были отправлены и повторная передача заголовков невозможна. Код для демонстрации - 2: Внимание использована инструкция в файле .htaccess php_value output_buffering 4096 "; for ($i = 1; $i <= 10; $i++) { echo $i.":(".mt_rand(0,1000).") "; flush(); sleep(1); } echo "
LVL of buffer: ".ob_get_level()."
"; header("Hello"); session_start(); header("Support title"); echo "

Thx!

"; ?> В этом примере мы не увидим кусочный вывод текста мы увидем его только когда исполнитс весь код. Даже flush() здесь не поможет. Здесь между прочим используется не только системный но и пользовательский буфер. Кстати вылезут ошибочки по поводу заголовков...тут конечно я не могу объяснить ситуацию из-за чего. Я в конце выскажу свои предположения и вопросы...stay here. Код для демонстрации - 3: Внимание использована инструкция в файле .htaccess php_value output_buffering 4096 "; for ($i = 1; $i <= 10; $i++) { echo $i.":(".mt_rand(0,1000).") "; ob_flush(); flush(); sleep(1); } echo "
LVL of buffer: ".ob_get_level()."
"; header("Hello"); session_start(); header("Support title"); echo "

Thx!

"; ?> В этом примере информация будет выводиться кусками. Этот пример как вы могли заметит отличается только дополнительной функцией ob_flush(). Она таки высвобождает накопленны данные в системный буфер. Следом вызывается flush() который информацию выводит прямо в браузер. Первый пример кода тому явное подтверждение. В конце будут отправлены ошибки о заголовках, оно и понятно данные были уже отправлены. Код для демонстрации - 4: Внимание использована инструкция в файле .htaccess php_value output_buffering off "; for ($i = 1; $i <= 10; $i++) { echo $i.":(".mt_rand(0,1000).") "; sleep(1); } echo "
LVL of buffer: ".ob_get_level()."
"; header("Hello"); session_start(); header("Support title"); echo "

Thx!

"; ?> Интересно, а для чего я этот код добавил? Никаких флюшей здесь нет, даже функций которые работают с буфером вывода. Все гораздо проще чем может показаться на первы взгляд. Дело в том что если мы запустим этот код он отработает как надо и выведет ошибки об уже отправленных заголовках...what? Выходит системный буфер не такой и буфер если он вызывает ошибку о заголовках, однако данные выведутся только тогда когда закончится скрипт. Вот так вот. Эпилог: Мои суждения: Т.к существует 2 буфера в php то получается один зависит от другого: чтобы вывест информацию со свежевыпеченного echo, мы должны сначала высвободить информацию с "нашего" буфера в "казенный" php буфер, после этого мы должны вызвать операцию для "казенного" php буфера на вывод. Системный буфер нельзя удалить, а пользовательский можно. Информация всегда будет попадать в системный буфер хочешь ты того или нет! Пользовательский буфер - это станция где люди ожидают свой самолет. Системный буфе - это самолет, который вмещает в себя людей. Заголовки - это купленные билеты (Вы ж не можете купить билет уже в самолете (Ну по крайней мере я ни разу такого не видел)) если самолет имеет время ожидания вы можете купить билет прямо на станции. Одно без другого жить не может, нет пользовательского буфера - нет времени для покупки билета, нет системного буфера - нет самолета. Выводом буфера будет являться то, когда самолет прибудет на станцию и высадит пассажиров Вопросы: Мой второй пример, мне не понятно по какой причине мне в конце работы скрипта ph пишет ошибку, по факту flush() не срабатывает ввиду того что мы находимся в пользовательском буфере. (Хотя возможно то что в системном буфере ничего нет и этим вызовом мы уже отправляем заголовки) Если опять же во втором примере мы заменим flush() => ob_flush(), мы получим ошибк об отправленных заголовках...why? Как я писал есть 2 буфера системный и пользовательский по логике в системном буфере лежит пользовательский и как вы могли видеть при наличии пользовательского буфера необходимо вызвать 2 флюша, а если у нас имелся только системный то хватало системного флюша. Если мы в файле .htaccess напишем php_value output_buffering on то размер буфер станет unlimited как сообщает php.ini, отнюдь если мы в скрипте вызовем функцию ob_get_level( то получим 0, если вы запускали код, который я писал выше то могли заметить если у нас буфер был заданного размера то мы получали 1, если мы его удаляли или писали off то он у нас был 0 (хочу заметить я знаю что могут быть вложенные буферы и значение может быть куда выше)...собственно почему у нас 0 если размер буфера on? Переосмысление (ответы на первый, второй и третий вопросы): Если данные попадают в системный буфер то заголовки будут отправлены, не важно вызвали вы flush() или нет, как следствие - варн об от отправленных заголовках* Если вы вызовете flush(), но в системный буфер до этого ничего не попадало, это будет означать что данные нужно вывести на экран немедленно из системного буфера, как следствие - отправк заголовков и варн* Если вы пишите ob_flush() в пользовательском буфере с уровнем < 2 (это равносильно тому, что если вы пишите echo в системном буфере), то вы получите варн* так как вы пробрасываете содержимое в системный буфер, а как я писал выше системный буфер если видит какие-то выводимые данные он начинает отправлять заголовки, в этом случае происходит то же самое. Если мы в файле .htaccess напишем php_value output_buffering on при вызове phpinfo( в php файле мы увидим в колонке Local Value - on. В итоге у нас буфер 0! Как так? Вс просто. Эта директива (output_buffering) не поддерживает такое значение. У этой директив есть несколько состояний: 0 - выключенный буфер, 1 - безлимитный буфер и любое число. Кроме того если мы напишем текст вроде: on, off, true, false, sampletext - то в Local Value мы увидим подобные значения, но они будут интерпетироваться как 0. Если мы будем писать текст через пробел то получим ошибку 500, например php_value output_buffering hello world. * Если вы отправляете заголовки после. ** php_value, php_flag, output_buffering - директивы. P.S Возможно я что-то не правильно понял, не стоит воспринимать это как истину, любом случае я смог понять только так, по-другому объяснить свои вопросы не удалось. В любом случае я жду объяснения от людей, которые соображают


Ответы

Ответ 1



Отвечу только на часть вашего вопроса, по крайней мере на текущий момент. Ваш код: "; for ($i = 1; $i <= 10; $i++) { echo $i.":(".mt_rand(0,1000).") "; sleep(1); } echo "
LVL of buffer: ".ob_get_level()."
"; header("Hello"); session_start(); header("Support title"); echo "

Thx!

"; ?> Ваш комментарий к нему: Интересно, а для чего я этот код добавил? Никаких флюшей здесь нет, даже функций, которые работают с буфером вывода. Все гораздо проще чем может показаться на первый взгляд. Дело в том что если мы запустим этот код он отработает как надо и выведет ошибки об уже отправленных заголовках...what? Выходит системный буфер не такой и буфер если он вызывает ошибку о заголовках, однако данные выведутся только тогда когда закончится скрипт. Вот так вот. Так вот. Не совсем понятно, почему вы в этом, четвертом примере решили (если я ва верно понял), почему не должно быть предупреждений насчет заголовков. Так или иначе вы в любом случае должны послать их самыми первыми, до посыла всяких там текстовых сообщений через echo - банально потому, что именно такого поведения от веб-сервера ожидают браузеры. И php тут просто уведомляет вас, что вы неверно пытаетесь сообщить браузеру что-либо через заголовки. Для понимания этого стоит обратиться к собственно протоколу HTTP, а именно к ег структуре. Ответ веб-сервера в общем случае состоит из трех частей: Starting line, Headers и Message Body. Первое отвечает за тип сообщения, второе за заголовки и третье собственно за то, что получит браузер в качестве ответа. Соответственно, когда приведенный код выше начинает выполняться, вне зависимост от того, играетесь ли вы с буфером или нет, веб-сервер, который работает с вашим интерпретаторо php, будь он apache'ем или чем угодно еще, при получении от php первого же текстовог сообщения в третьей строке вашего кода (а оно будет первым, данный код же будет выполняться последовательно), сформирует ответ браузеру, который будет состоять из трех перечисленных выше частей. И далее веб-браузер может всего лишь досылать что-либо в Message Body, но заголовки уже будут отправлены. Другими словами, если вы хотите что-то вывести в заголовках, то вам нужно сформироват их до первого любого вывода в браузер, или же выстроить логику своего приложения каким-либо иным образом, где функция header() не будет использоваться после любой функции, реализующей вывод чего бы то ни было браузеру посредством HTTP.

Ответ 2



Чтобы немного прояснить для себя и других, как работает буферизация в PHP, давайте заглянем в исходники. У меня исходники php-7.1.3. Заходим в корневую папку исходников и пытаемся найти в них реализацию функции ob_get_contents: $ fgrep -r 'PHP_FUNCTION(ob_get_contents)' . Находится такое: ./main/output.c:PHP_FUNCTION(ob_get_contents) Вот эта функция: PHP_FUNCTION(ob_get_contents) { if (zend_parse_parameters_none() == FAILURE) { return; } if (php_output_get_contents(return_value) == FAILURE) { RETURN_FALSE; } } (Попутно было замечено, что PHP-еретики в своих кодах используют табулятор вместо 4-х пробелов.) В том же файле находим функцию php_output_get_contents: PHPAPI int php_output_get_contents(zval *p) { if (OG(active)) { ZVAL_STRINGL(p, OG(active)->buffer.data, OG(active)->buffer.used); return SUCCESS; } else { ZVAL_NULL(p); return FAILURE; } } Что происходит? Выглядит так, как будто что-то (видимо, возвращаемая строка) формируетс на основе некоторого адреса в памяти и длины данных. То есть пользовательский буфер это просто кусок памяти. Вероятно, там еще есть стек меток для каждого вызова ob_start. Это уже сами ищите реализацию ob_start, кому интересно. Аналогичным образом можно найти реализацию функции flush и увидеть, что она работает через функцию конкретного применяемого SAPI. Я посмотрел SAPI для PHP как модуля Apache: static void php_apache_sapi_flush(void *server_context) { php_struct *ctx; request_rec *r; ctx = server_context; /* If we haven't registered a server_context yet, * then don't bother flushing. */ if (!server_context) { return; } r = ctx->r; sapi_send_headers(); r->status = SG(sapi_headers).http_response_code; SG(headers_sent) = 1; if (ap_rflush(r) < 0 || r->connection->aborted) { php_handle_aborted_connection(); } } Здесь используются структуры C API самого Apache, то есть и вывод предварительн должен делаться таким же образом. Как Apache буферизует выводимые данные - это уже его личное дело и к PHP не имеет никакого отношения. Результат таков, что "системная" и "пользовательская" буферизация в PHP - это абсолютн разные вещи, настолько разные, что их не стоило бы называть в одном списке, чтобы не вносить сумятицу, и, возможно, с той же целью, пользовательскую буферизацию вообще стоило бы назвать как-то по-другому.

Комментариев нет:

Отправить комментарий