🍓 第五章 使用树莓派架设服务器
📄 一句话概括

上一节的 Apache 只会「把已经写好的文件送出去」;本节装上 PHP,它就变成了「每次都现算一遍再送」。 这一节你会第一次看到服务器端代码长什么样、它和网页里的 HTML 有什么本质区别,以及新手最容易搞混的PHP 两套运行方式。

📝 本节和前面几节的关系

5-1 装好了 Apache(负责「接待客人」),本节装 PHP(负责「现场炒菜」)。 下一节(5-3)装数据库(负责「去仓库取料」)——这三样凑齐就是大名鼎鼎的 LAMP(Linux + Apache + MySQL/MariaDB + PHP)。 本课 1-5 里提过「Python 和 PHP 都是脚本语言」,本节就是 PHP 的第一次实操。

一、先说清「静态」和「动态」的区别

这是本节的概念地基,比命令重要得多。

静态网页 动态网页
服务器做什么 把文件原样发出去 先跑一段程序,把结果拼成 HTML 再发
文件后缀 .html .php(跑完后浏览器看不到 PHP 代码,只看到 HTML)
内容会变吗 不会,谁看都一样 会——看时间、看你是谁、看数据库里有什么
例子 你写的「这是我的服务器」 淘宝首页(每个人看到的商品都不一样)
💡 一句话记住

Apache 是「服务员」,PHP 是「厨师」。 客人点 /index.php,服务员不直接把菜端上去,而是把单子递给厨师,厨师做完再交回服务员端出去。 客人(浏览器)永远见不到厨房——这就是为什么你在浏览器里「查看源代码」永远看不到 PHP 代码,这也是 PHP 能安全地藏住数据库密码的原理。

二、PHP 是什么,它跑在哪里

PHP = Hypertext Preprocessor(超文本预处理器),一门专门为网页设计的服务器端脚本语言。

三个「不是」要先记牢:

它诞生的理由:1994 年有人想在自己的主页上放一个「访问计数」,用 C 写太麻烦,就随手写了个小程序——这就是 PHP 的起点。所以它的长项一直是:「在网页里嵌一段小程序,算点东西出来」,非常顺手。

PHP 代码长什么样:它直接嵌在 HTML 里,用 <?php ... ?> 圈起来:

<!DOCTYPE html>
<html>
<body>
  <h1>现在时间</h1>
  <p><?php echo date("Y-m-d H:i:s"); ?></p>
</body>
</html>

注意读法:<h1> 那些是普通的 HTML,浏览器拿到什么样就是什么样;只有 <?php ... ?> 中间那段会被服务器执行掉,执行结果替换掉原位置,再发出去。

三、装 PHP:一行命令,但有几个包要认清

sudo apt update
sudo apt install php libapache2-mod-php php-mysql -y

三个包分别管什么(别只背命令,要知为什么):

包 作用 少了它会怎样
php PHP 本体(含命令行解释器 php-cli) 什么都没有
libapache2-mod-php 把 PHP 接进 Apache 的接头 浏览器会直接下载 .php 文件(源码泄露!)
php-mysql PHP 连数据库的驱动 下一节的数据库连不上
⚠️ 中间那个包最关键,也最容易被忽略

libapache2-mod-php 是「Apache」和「PHP」之间的转接头。 没装它、或者装完没启用模块,你访问 info.php 时 Apache 不认识 .php,会把它当成一个未知文件发给你——浏览器就会弹下载框,或者把 PHP 源码当纯文本显示出来。 看到「下载 .php 文件」或「页面上直接显示源码」,第一个要查的就是这个模块。

装完重启并验证:

sudo systemctl restart apache2
php -v

php -v 期望输出第一行类似:

PHP 8.4.x (cli) (built: ...)
📝 版本号以你自己机器上的为准

Debian 13(Trixie)这一代配套的是 PHP 8.4(上一代 Bookworm 是 8.2)。 本文示例里的 8.4 只是举例——具体版本一律以 php -v 的实际输出为准,后面的路径也跟着它变。

四、新手最大的迷惑:PHP 有「两套」

这一节的知识点里,这一条最值钱。

装完之后,你的系统里其实有两套 PHP 在工作:

CLI 那一套 Apache 模块那一套
给谁用 你在终端里敲 php xxx.php 浏览器访问 xxx.php
配置文件在哪 /etc/php/8.4/cli/php.ini /etc/php/8.4/apache2/php.ini
版本命令 php -v 看的是它 phpinfo() 看的才是它
扩展装在哪 /etc/php/8.4/mods-available/(两套共享) 同左
⚠️ 为什么必须知道这个

你在命令行里 php -v 显示一切正常,不代表浏览器那一套也正常。 两者只有 .ini 是分开的,扩展是共享的。 所以典型误判是:「我终端里测过没问题的啊?」 —— 因为那是另一套。 统一判据:浏览器这套的真相,只有一个地方能看到 —— phpinfo() 页面(下一节马上就用)。

装完就有一堆现成的 PHP 模块(Debian 用 phpenmod / phpdismod 开关,跟 Apache 的 a2enmod 是一个思路):

php -m           # 列出所有已加载的扩展(不看版本号的话,这条更快)

五、第一张 PHP 页面:info.php

在网页目录里新建(注意:这次用 sudo 是因为 5-1 讲过的权限):

sudo nano /var/www/html/info.php

内容只有一行:

<?php phpinfo(); ?>

保存(Ctrl + O → 回车 → Ctrl + X),然后在浏览器打开:

http://树莓派的IP/info.php

期望看到:一整页紫色的表格,包含 PHP 版本、加载的配置、已启用的扩展、环境变量等等。

这一页就是「Apache 那套 PHP」的体检报告。 你要看四样:

  1. 最上面那行 PHP Version —— 是不是你刚装的版本;
  2. Loaded Configuration File —— 告诉我它实际读了哪个 php.ini(就是上面表格里的路径,用它来验证你的判断);
  3. Server API —— 应该写着 Apache 2.0 Handler(证明是模块方式在跑);
  4. 往下找 $_SERVER 段 —— 能看到 Apache 传过来的各种信息。
🚨 看完立刻删掉它

phpinfo() 会把服务器上所有配置细节摊开给人看,包括路径、模块版本、编译参数——这东西等于把家底贴在门口,对攻击者是极好的情报。 验收完就删:

sudo rm /var/www/html/info.php

要用的时候再临时建一次。这是本节最该记住的一条安全习惯。

六、常用扩展:装一次,后面不折腾

PHP 本体很精简,很多能力要单独装扩展。这一课后面会用到这几个,建议一次装齐:

sudo apt install php-mbstring php-curl php-gd php-xml php-zip -y
sudo systemctl restart apache2
扩展 干什么 不装的后果
php-mbstring 正确处理多字节字符(中文!) 中文截断变乱码——跟 4-6 装字体是一个道理
php-curl PHP 主动去访问别的网址 调外部接口失败
php-gd 图片处理(缩略图、水印) 图片功能报错
php-xml 读写 XML/一部分 API 用它 很多库依赖它
php-zip 压缩包操作 上传 zip 解压失败
📝 为什么中文特别要注意

php-mbstring 这个名字里的 mb = multi-byte(多字节)。 一个汉字在 UTF-8 里占 3 个字节,而 PHP 里老式的 strlen() 是按字节算的——不装 mbstring,你以为在处理「10 个字」,实际在处理「30 个字节」,截一半就成乱码。 (这和我们写 C++ 笔记时踩过的那个坑一模一样:UTF-8 下按字节切分会把汉字切碎。)

七、动手 Demo:写一个「会变」的页面

目标:做一个每次刷新都会显示不同内容的页面——亲眼看见「动态」是什么意思。

sudo nano /var/www/html/hello.php

输入:

<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>PHP 测试</title></head>
<body>
  <h1>这个页面是服务器现算出来的</h1>
  <p>服务器时间:<?php echo date("Y-m-d H:i:s"); ?></p>
  <p>PHP 版本:<?php echo phpversion(); ?></p>
  <p>一个随机数:<?php echo rand(100, 999); ?></p>
  <p>你的浏览器是:<?php echo $_SERVER["HTTP_USER_AGENT"]; ?></p>
  <p>你的 IP 是:<?php echo $_SERVER["REMOTE_ADDR"]; ?></p>
</body>
</html>

保存后,在浏览器打开 http://树莓派的IP/hello.php。

验收(三条都做到才算成功):

  1. 能看到「服务器时间」且时间秒数在走;
  2. 按 F5 刷新,随机数每次都变 —— 证明服务器在每次请求时都重新算了一遍;
  3. 在浏览器里「查看源代码」 —— 期望看到的是浏览器收到的那份 HTML(时间、随机数都是具体数字),绝对看不到 <?php 那几个字。

读结果:

八、30 秒实验:故意让它出错,学会看报错

PHP 出错时页面往往是白的(这叫「白屏」),新手在这最容易以为「什么都没发生」。真相在日志里:

# 一边盯着日志
sudo tail -f /var/log/apache2/error.log

另开一个终端,把 hello.php 里故意写错一句(比如把 echo 写成 echoo),保存,然后刷新浏览器。

期望在日志里看到类似:

PHP Parse error: syntax error, unexpected ... in /var/www/html/hello.php on line 8

看到带文件名和行号的 Parse error 就算成功。改回来,日志不再新增。

💡 这个实验的价值

你以后写 PHP 的绝大部分时间,都是在「刷新页面 → 看日志 → 改代码」这个循环里。 先把这条循环走通,比背语法重要。

九、常见误区

  1. 「PHP 是前端的,跟 JavaScript 一样在浏览器里跑」——错。PHP 完全在服务器上跑,浏览器只拿到它输出的 HTML。这也是为什么「查看源代码」永远看不到 PHP 逻辑。
  2. 「.php 文件应该能看到源码」——恰恰相反。能看到源码说明配置错了(模块没生效),属于安全问题,要马上修。
  3. 「页面白屏 = 没运行」——错。白屏几乎总是「运行了但报错了」。去看 error.log。
  4. 「php -v 正常就万事大吉」——错。那是 CLI 那一套。浏览器用的是 Apache 模块那一套,判据是 phpinfo()。(见第四节)
  5. 「info.php 建着方便,以后还要用」——危险。它是情报泄露页,用完立刻删。需要时再临时建。
  6. 「改完 php.ini 直接生效」——错。Apache 那套改完要 sudo systemctl restart apache2;CLI 那套下次执行自动生效。顺序反了会以为自己改错了。
⚠️ 老教程里已失效的写法
  • 装 php5 / php7 —— 包名里带版本号的老写法全失效了,现在统一叫 php,装到哪一代由系统仓库决定;
  • 改 /etc/php5/apache2/php.ini —— 路径里的版本号现在跟着实际版本走(本节是 /etc/php/8.4/apache2/php.ini),照抄老路径会 No such file;
  • 用短标签 <? ... ?> —— 老教程里写 <? echo $x; ?> 能跑,但短标签在新版默认关闭,照抄必然失败。一律写完整的 <?php ... ?>,这一条能省你半小时;
  • /etc/init.d/apache2 restart —— 老式服务管理写法,现在统一 systemctl。
自测三问(点开对答案) 1. 浏览器里「查看源代码」看不到 PHP 代码,说明了什么?(**说明 PHP 是在服务器上执行的,输出给浏览器的只有它生成的 HTML**。如果真看到了 PHP 源码,那是配置错误、属于源码泄露) 2. `php -v` 显示一切正常,但浏览器里的页面白屏——这两件事矛盾吗?(**不矛盾**。`php -v` 是 **CLI 那一套**,浏览器用的是 **Apache 模块那一套**,两套的 `php.ini` 是分开的。要去 `error.log` 或 `phpinfo()` 里查真正的那一套) 3. `libapache2-mod-php` 这个包的作用是什么?不装会有什么现象?(**它是 Apache 认领 `.php` 文件的接头**;不装的话 Apache 不认识 `.php`,会把它当普通文件发出去——**浏览器弹下载框,或者直接把 PHP 源码显示成纯文本**)

相关笔记