PHP에 "출력 버퍼" 레이어라는 것이 있다는 것은 누구나 알고 있습니다. 이 기사는 그것이 정확히 무엇인지 설명하기 위해 여기에 있습니다. PHP 내부적으로 어떻게 구현됩니까? 그리고 PHP 프로그램에서 어떻게 사용하나요? 이 계층은 복잡하지 않지만 오해를 받는 경우가 많으며 많은 PHP 개발자가 이를 완전히 익히지 못합니다. 오늘 함께 알아 봅시다.
우리가 논의할 내용은 PHP 5.4(이상)를 기반으로 합니다. PHP의 OB 레이어는 버전 5.4 이후로 많은 변화를 겪었습니다. 정확히 말하면 일부 부분은 PHP와 호환되지 않을 수 있습니다. 5.3.
PHP의 출력 스트림에는 일반적으로 프로그래머가 PHP에서 출력하기를 원하는 텍스트인 많은 바이트가 포함되어 있습니다. 이러한 텍스트는 대부분 echo 문이나 printf() 함수에 의해 출력됩니다. PHP의 출력 버퍼에 대해 알아야 할 세 가지가 있습니다.
첫 번째 요점은 무언가를 출력하는 모든 함수가 출력 버퍼를 사용한다는 것입니다. 물론 이것은 PHP로 작성된 프로그램에 관한 것입니다. PHP 확장을 작성하는 경우 사용하는 함수(C 함수)가 OB 계층을 거치지 않고 SAPI 버퍼 계층에 직접 출력을 쓸 수 있습니다. 소스 파일 main/php_output.h에서 이러한 C 함수에 대한 API 문서에 대해 알아볼 수 있습니다. 이 파일은 기본 버퍼 크기와 같은 기타 많은 정보를 제공합니다.
두 번째로 알아야 할 것은 출력 버퍼 레이어가 출력을 버퍼링하는 데 사용되는 유일한 레이어가 아니라 실제로 여러 레이어 중 하나일 뿐이라는 것입니다. 마지막으로 기억해야 할 점은 출력 버퍼 계층의 동작이 사용 중인 SAPI(웹 또는 CLI)와 관련되어 있으며 SAPI마다 동작이 다를 수 있다는 것입니다. 먼저 그림을 통해 이들 레이어 간의 관계를 살펴보겠습니다.
위 그림은 PHP의 세 가지 버퍼 레이어 간의 논리적 관계를 보여줍니다. 위의 두 레이어는 우리가 일반적으로 "출력 버퍼"로 인식하는 레이어이고 마지막 레이어는 SAPI의 출력 버퍼입니다. 이것들은 모두 PHP의 레이어이며 출력 바이트가 PHP를 떠나 컴퓨터 아키텍처의 하위 레이어(터미널 버퍼, fast-cgi 버퍼, 웹 서버 버퍼, 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는 즉시 데이터를 다음 계층(일반적으로 표준 출력 파이프)으로 보냅니다. 두 함수 write() 및 fflush()가 이 작업을 담당합니다. 간단해요!
PHP-FPM과 같이 CLI와 다른 SAPI를 사용하는 경우 다음 세 가지 버퍼 관련 INI 구성 옵션을 사용하게 됩니다.
기본적으로 PHP 배포판은 php.ini에서 output_buffering을 4096바이트로 설정합니다. php.ini 파일을 사용하지 않는 경우(또는 PHP를 시작할 때 -d 옵션을 사용하지 않는 경우) 기본값은 0이 되어 출력 버퍼를 비활성화합니다. 해당 값을 "ON"으로 설정하면 기본 출력 버퍼 크기는 16kb가 됩니다. 짐작하셨겠지만 웹 애플리케이션 환경에서 출력용 버퍼를 사용하면 성능상의 이점이 있습니다. 기본 설정인 4k가 적절한 값인데, 이는 먼저 4096개의 ASCII 문자를 작성한 다음 아래 SAPI 레이어와 통신할 수 있다는 의미입니다. 그리고 웹 애플리케이션 환경에서 소켓을 통해 메시지를 바이트 단위로 전송하는 방식은 성능에 좋지 않습니다. 더 나은 접근 방식은 모든 것을 한 번에 또는 적어도 하나씩 서버로 전송하는 것입니다. 레이어 간 데이터 교환이 적을수록 성능이 향상됩니다. 항상 출력 버퍼를 사용 가능하게 유지해야 하며, PHP는 요청이 완료된 후 사용자가 아무것도 하지 않고도 해당 내용을 최종 사용자에게 전송하는 작업을 처리합니다.
implicit_flush는 이전에 CLI에 대해 이야기할 때 언급된 적이 있습니다. 다른 SAPI의 경우 implicit_flush는 기본적으로 off로 설정되어 있으며, 이는 올바른 설정입니다. 새 데이터가 기록될 때마다 SAPI를 플러시하는 것은 아마도 원하는 것이 아닐 것이기 때문입니다. FastCGI 프로토콜의 경우 플러시 작업은 각 쓰기 후에 FastCGI 배열 패킷(패킷)을 보내는 것입니다. 데이터 패킷을 보내기 전에 FastCGI 버퍼를 채우는 것이 좋습니다. SAPI 버퍼를 수동으로 플러시하려면 PHP의 플러시() 함수를 사용하십시오. 한 번 쓰고 새로 고치려면 INI 구성에서 implicit_flush 옵션을 설정하거나 ob_implicit_flush() 함수를 한 번 호출하면 됩니다.
output_handler는 버퍼가 플러시되기 전에 버퍼의 내용을 수정할 수 있는 콜백 함수입니다. PHP 확장은 많은 콜백 함수를 제공합니다(사용자는 아래에서 설명하는 자신만의 콜백 함수를 작성할 수도 있습니다).
ob_gzhandler: ext/zlib를 사용하여 출력 압축
mb_output_handler: ext/mbstring을 사용하여 문자 인코딩 변환
ob_iconv_handler: ext/iconv를 사용하여 문자 인코딩 변환
ob_tidyhandler: ext/ tidy를 사용하여 출력 HTML 텍스트 정리
ob_[inflate/deflate]_handler: ext/http를 사용하여 출력 압축
ob_etaghandler: ext/http를 사용하여 자동으로 HTTP Etag
버퍼에서 콘텐츠는 원하는 콜백 함수(하나만 사용할 수 있음)에 전달되어 콘텐츠 변환 작업을 수행하므로 PHP가 웹 서버와 사용자에게 전송하는 콘텐츠를 얻으려면 다음을 수행하면 됩니다. 출력 버퍼 콜백을 사용하십시오. 지금 언급해야 할 한 가지는 여기서 언급된 "출력"이 메시지 헤더(headers)와 메시지 본문(body)을 참조한다는 것입니다. HTTP 메시지 헤더도 OB 계층의 일부입니다.
출력 버퍼(사용자 또는 PHP)를 사용할 때 원하는 방식으로 HTTP 헤더와 콘텐츠를 보내고 싶을 것입니다. 모든 프로토콜은 메시지 본문을 보내기 전에 메시지 헤더(따라서 "헤더"라고 불리는 이유)를 보내야 한다는 것을 알고 있지만 출력 버퍼 계층을 사용하면 걱정할 필요 없이 PHP가 이를 대신합니다. 실제로 메시지 헤더 출력과 관련된 모든 PHP 함수(header(), setcookie(), session_start())는 메시지 헤더 버퍼에만 내용을 쓰는 내부 sapi_header_op() 함수를 사용합니다. 그런 다음 printf()를 사용하여 콘텐츠를 출력하면 해당 콘텐츠가 출력 버퍼에 기록됩니다(단 하나의 버퍼만 있다고 가정). 이 출력 버퍼의 내용을 보내야 할 때 PHP는 먼저 메시지 헤더를 보낸 다음 메시지 본문을 보냅니다. PHP는 당신을 위해 모든 것을 수행합니다. 불편함을 느끼고 직접 해보고 싶다면 출력 버퍼를 비활성화하는 것 외에는 다른 선택이 없습니다.
사용자 출력 버퍼의 경우 먼저 예제를 통해 어떻게 작동하고 무엇을 할 수 있는지 살펴보겠습니다. 다시 말하지만 기본 PHP 출력 버퍼 계층을 사용하려는 경우 이 계층이 비활성화되어 있으므로 CLI를 사용할 수 없습니다. 다음 예에서는 PHP의 내부 웹 서버 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 레이어의 버퍼에 내부 데이터를 전달하므로 SAPI 레이어의 버퍼는 다음과 같습니다. 또한 즉시 다음 레이어로 플러시됩니다. 'aaaaaaaaaa{31 a}b'라는 문자열이 화면에 나타난 다음 스크립트가 다시 절전 모드로 전환됩니다. 2초 후에 또 다른 바이트가 출력됩니다. 이때 버퍼에는 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!