它的本质是:**PHP 本身 不监听 任何网络端口。它是一个 被动执行者 (Passive Executor)

  • 核心流程
    1. Nginx/Apache 监听 80/443 端口,接收 TCP 连接,解析 HTTP 协议。
    2. Web 服务器将 HTTP 请求中的关键信息(URL、Header、Body)转化为 环境变量 (Environment Variables)标准输入 (STDIN)
    3. Web 服务器通过 FastCGI 协议(Socket 通信)将这些数据发送给 PHP-FPM
    4. PHP-FPM 唤醒一个 Worker 进程,初始化 Zend Engine。
    5. Zend Engine 读取环境变量和 STDIN,填充超全局数组 ($_GET, $_POST, $_SERVER)。
    6. PHP 脚本执行,通过 STDOUT 输出响应内容。
    7. PHP-FPM 将 STDOUT 内容打包回 FastCGI 响应,传给 Nginx。
    8. Nginx 发送 HTTP 响应给客户端。
  • 核心逻辑别以为 PHP 直接跟浏览器对话。PHP 只是在一个封闭的沙箱里,处理 Web 服务器喂给它的“标准化数据包”。它不知道 TCP,不知道 HTTP,它只知道“环境变量”和“输入流”。

如果把 HTTP 请求处理比作餐厅点餐

  • 客户端 (Browser):是 顾客
  • Nginx:是 服务员
    • 接待顾客,记录点了什么菜(解析 HTTP)。
    • 把菜单翻译成厨房能懂的格式(转换协议)。
  • PHP-FPM:是 厨房经理
    • 分配订单给具体的厨师(Worker 进程)。
  • PHP (Zend Engine):是 厨师
    • 不看顾客:厨师看不见顾客,也不懂外语(HTTP 协议)。
    • 只看单子:厨师只看贴在墙上的单子(环境变量 $_SERVER)和传进来的食材盒(STDIN $_POST)。
    • 做菜:执行 PHP 代码逻辑。
    • 出菜:把做好的菜放在出餐口(STDOUT)。
  • 核心逻辑厨师(PHP)只负责加工数据,不负责接待客户。服务员(Nginx)负责沟通内外。

一、Nginx 的角色:协议的翻译官

1. 接收与解析
  • 动作:Nginx 监听 Socket,接受 TCP 连接,读取字节流。
  • 解析:将字节流解析为 HTTP 结构:
    • Request Line: GET /index.php?id=1 HTTP/1.1
    • Headers: Host: example.com, User-Agent: ...
    • Body: name=alice&age=25 (如果是 POST)
2. 静态 vs. 动态判断
  • 静态文件.css, .js, .png。Nginx 直接读取磁盘返回,不调用 PHP
  • 动态文件.php。Nginx 识别后缀,决定将请求转发给后端 PHP 处理器。
3. 构造 FastCGI 参数

Nginx 不会直接把原始 HTTP 包扔给 PHP,而是提取关键信息,映射为 FastCGI 变量(本质上是 Key-Value 对):

location ~ \.php$ {
    fastcgi_pass   127.0.0.1:9000;
    fastcgi_index  index.php;
    
    # 关键映射:将 HTTP 信息转化为 PHP 能懂的环境变量
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    fastcgi_param  QUERY_STRING     $query_string;
    fastcgi_param  REQUEST_METHOD   $request_method;
    fastcgi_param  CONTENT_TYPE     $content_type;
    fastcgi_param  CONTENT_LENGTH   $content_length;
    
    include        fastcgi_params;
}

💡 核心洞察$_SERVER 数组里的内容,其实就是 Nginx 通过 fastcgi_param 传进来的这些 Key-Value 对。


二、FastCGI 协议桥接:进程间通信

1. 为什么需要 FastCGI?
  • CGI 缺陷:每个请求 fork 一个新进程,启动慢,资源消耗大。
  • FastCGI 改进
    • 常驻进程:PHP-FPM Worker 进程一直活着。
    • Socket 通信:Nginx 和 PHP-FPM 通过 TCP Socket (127.0.0.1:9000) 或 Unix Domain Socket (/tmp/php-fpm.sock) 通信。
    • 二进制协议:比文本 HTTP 更紧凑、高效。
2. 通信过程
  1. Nginx 建立连接到 PHP-FPM。
  2. Nginx 发送 FastCGI BEGIN_REQUEST 记录。
  3. Nginx 发送 PARAMS 记录(包含所有 fastcgi_param 定义的环境变量)。
  4. Nginx 发送 STDIN 记录(POST Body 数据)。
  5. Nginx 发送 END_REQUEST 标记。
  6. PHP-FPM 接收数据,唤醒 Worker。

三、PHP 内部初始化:从字节到超全局数组

当 PHP-FPM Worker 收到数据后,Zend Engine 开始工作:

1. 激活 SAPI (Server API)
  • PHP 有多种 SAPI:cli (命令行), fpm (FastCGI), apache2handler
  • 在 FPM 模式下,SAPI 模块负责解析 FastCGI 包。
2. 填充 $_SERVER
  • SAPI 将接收到的 PARAMS 键值对,直接填入 $_SERVER 超级全局数组。
    • REQUEST_METHOD -> $_SERVER['REQUEST_METHOD']
    • QUERY_STRING -> $_SERVER['QUERY_STRING']
    • SCRIPT_FILENAME -> $_SERVER['SCRIPT_FILENAME']
3. 解析 $_GET
  • PHP 读取 $_SERVER['QUERY_STRING']
  • 调用内置解析器,将 id=1&name=abc 解析为数组。
  • 填入 $_GET
4. 解析 $_POST
  • PHP 读取 STDIN 流中的数据。
  • 根据 Content-Type 决定解析方式:
    • application/x-www-form-urlencoded:类似 GET 解析。
    • multipart/form-data:解析文件上传边界,生成 $_FILES$_POST
    • application/json注意! PHP 不会 自动解析 JSON 到 $_POST。你需要手动 json_decode(file_get_contents('php://input'))
5. 执行脚本
  • Zend Engine 编译并执行 SCRIPT_FILENAME 指向的 PHP 文件。
  • 脚本中所有的 echo, print, var_dump 输出都被捕获到 输出缓冲区 (Output Buffer)
6. 返回响应
  • 脚本执行结束。
  • PHP-FPM 将输出缓冲区的内容封装为 FastCGI STDOUT 记录。
  • 发送回 Nginx。
  • Worker 进程重置状态,等待下一个请求。

四、认知牢笼:常见误区

1. 误区:“PHP 可以直接监听 80 端口。”
  • 真相
    • 原生 PHP (CLI/FPM) 不能
    • Swoole/Hyperf 可以,因为它们内置了 HTTP Server 实现,自己解析 TCP/HTTP,不再依赖 Nginx/FPM。
    • 对策:区分传统 PHP 和现代协程 PHP。
2. 误区:“$_POST 包含所有提交的数据。”
  • 真相
    • 如果 Content-Type 是 application/json$_POST空的
    • 对策:使用 file_get_contents('php://input') 获取原始 Body。
3. 误区:“Nginx 和 PHP 在同一台机器,所以没有网络开销。”
  • 真相
    • 即使使用 Unix Socket,仍有 上下文切换数据拷贝 开销。
    • 对策:高性能场景下,减少 Nginx-PHP 之间的数据传输量(如压缩、缓存)。
4. 误区:“PHP 知道客户端的 IP。”
  • 真相
    • PHP 看到的 IP 是 Nginx 的 IP(通常是 127.0.0.1)。
    • 真实 IP 在 X-Forwarded-ForX-Real-IP Header 中,由 Nginx 透传。
    • 对策:配置 Nginx proxy_set_header X-Real-IP $remote_addr;,并在 PHP 中读取 $_SERVER['HTTP_X_REAL_IP']
5. 误区:“PHP 脚本执行完,连接就断了。”
  • 真相
    • PHP-FPM 与 Nginx 的连接是 短连接(请求结束即断开)。
    • 但 Nginx 与客户端可以是 Keep-Alive 长连接。
    • 对策:理解连接的生命周期在不同层级是不同的。

🚀 总结:原子化“HTTP 请求接收”全景图

维度 关键点
本质 PHP 是被动的数据处理引擎,依赖 Web 服务器代理
核心协议 FastCGI (二进制, Socket 通信)
数据流转 HTTP -> Nginx (解析) -> FastCGI Params/STDIN -> PHP (填充超全局数组) -> 执行 -> STDOUT -> Nginx -> HTTP
关键映射 fastcgi_param 决定 $_SERVER 内容
身体部位 $_GET (Query String), $_POST (STDIN Body), $_SERVER (Headers/Env)
PHP 隐喻 Chef (PHP) cooks based on Order Ticket (Env Vars) from Waiter (Nginx)
公式 Request_Handling = (Nginx_Parsing × FastCGI_Transmission) ^ PHP_Execution

终极心法

HTTP 请求接收的本质,是“协议的层层剥离与转化”。
PHP 处于链条的末端,只关心数据,不关心传输。
理解这一链路,你才能明白为什么配置 Nginx 能影响 PHP 行为。
于代理中见解耦,于转化中见映射;以协议为尺,解黑盒之牛,于 Web 架构中,求通透之真。

行动指令

  1. 查看 Nginx 配置:找到 fastcgi_param 部分,对照 PHP 中的 $_SERVER,一一确认映射关系。
  2. 抓包分析:使用 tcpdump 或 Wireshark 抓取 127.0.0.1:9000 的流量,观察 FastCGI 二进制包结构。
  3. 测试 JSON:发送一个 JSON POST 请求,观察 $_POST 为空,而 php://input 有数据。
  4. 思维升级:记住,PHP 不是 Web 服务器,它是 Web 服务器的插件。尊重这种从属关系,才能设计出高效的架构。

更多推荐