用 PHP + Vue 制作 Minecraft 服务器状态页(Java Edition)

82次阅读
没有评论

很多时候我们只想要一个「够用、能部署、好维护」的状态页:告诉玩家服务器是否在线、当前在线人数、以及大概的延迟。

这个项目就是为此而生:后端用 PHP 直连 Minecraft Java Edition 服务器做一次 Server List Ping(1.7+) 探测,前端用 Vue 渲染一个简洁的状态卡片,并通过一个非常轻量的 JSON API 把数据串起来。

适用场景:你有一个(或少量)Minecraft Java 服务器,希望把状态展示在网页上;或者你想要一个能看懂、可改造的 Server List Ping 参考实现。


前端内容

  • 在线/离线:探测成功视为在线
  • 在线人数(当前/上限):从服务器返回的 players.online / players.max 里读取
  • 延迟(毫秒):通过一次 ping/pong 粗略估算 RTT
  • 部署简单:只需要 PHP 7.0+,把 public/ 设成站点根目录即可

目录结构速览

  • config.php:Minecraft 服务器地址、端口、超时等配置
  • public/index.php:前端页面(Vue)与交互逻辑(刷新、懒加载等)
  • public/api/status.php:JSON API,封装并返回探测结果,带一个很小的服务端缓存
  • src/MinecraftStatus.php:Server List Ping 的核心实现(TCP socket + VarInt 协议)

后端是怎么“问”Minecraft 服务器状态的?

Minecraft Java 的服务器列表信息,并不是一个 HTTP 接口,而是基于 TCP 的协议交互。这个项目在 src/MinecraftStatus.php 里实现了 Server List Ping(1.7+) 的基本流程:

  1. 通过 fsockopen($host, $port, ...) 建立 TCP 连接,并设置超时
  2. 发送 Handshake
  3. 发送 Status Request
  4. 读取服务端返回的 JSON(包含版本、描述、玩家等信息)
  5. 可选:发送 Ping 并读取 Pong,用于估算延迟

其中最容易踩坑的是 VarInt 编码(变长整数)和「写入/读取必须一次读够」的流式 IO。代码里把这些细节封装成了几个小函数:

  • writeVarInt() / readVarIntFromStream():处理 VarInt 编码/解码
  • writeAll():确保 socket 写完整
  • readExactly():确保 socket 读够指定长度

协议要点(结合代码理解)

  • Handshake 包里会写入:

    • packet id = 0x00
    • protocol version = -1(表示“任意版本”,用于尽量兼容)
    • server address(字符串)
    • server port(2 字节网络序)
    • next state = 1(status)
  • 状态请求包非常短:length=1packet id=0x00

  • 服务端响应 packet id=0x00,正文是一个 JSON 字符串,典型结构里会有:

    • players.onlineplayers.max
    • version.name

这也是为什么 API 层可以直接从 data.players.* 里读人数。


API:一个端点搞定

接口只有一个:

  • GET /api/status.php

返回示例(字段含义见 README):

{
  "ok": true,
  "online": true,
  "host": "example.com",
  "port": 25565,
  "playersOnline": 0,
  "playersMax": 20,
  "latencyMs": 42,
  "error": null
}

为什么同时有 ok 和 online?

  • ok:代表这次探测是否“跑完并解析到有效响应”
  • online:代表服务器是否在线(一般和 ok 同步)

当出现网络错误、读写失败、解析失败等,ok=false,同时在 error 里带上可读的原因,前端会把它展示成一条错误提示。


服务端小缓存:让刷新更友好

public/api/status.php 里实现了一个非常小的缓存策略:

  • 默认 cacheTtlSec = 10 秒(可以在 config.phpminecraft 配置下新增 cacheTtlSec 覆盖)
  • 缓存写到系统临时目录 sys_get_temp_dir() 下的一个 JSON 文件
  • 浏览器端缓存被强制禁用:响应头 Cache-Control: no-store

这意味着:

  • 用户狂点“刷新”时,不会每次都触发一次真实的 TCP 探测
  • 但浏览器不会缓存页面/接口,刷新行为仍然“即时可见”

如果你的服务器状态变化很频繁、或者你希望每次都真实探测,把 cacheTtlSec 设为 0 就行。


前端:懒加载 + 手动刷新

页面在 public/index.php 里完成(Vue 直接挂载在同一个文件中)。核心交互非常克制:

  • 首屏不立刻请求:用 IntersectionObserver 判断页面进入视口后再触发请求
  • 若浏览器支持 requestIdleCallback,会在空闲时发起请求(同时设置了超时兜底)
  • 用户也可以点击“刷新”按钮显式拉取最新状态
  • 请求使用 axios.get('./api/status.php', { params: { t: Date.now() } }),加一个时间戳参数避免被中间缓存误伤

这种设计的好处是:

  • 打开页面不会立刻“打扰”后端
  • 你可以把状态页嵌在其他页面里,只有真正滚到它附近时才探测

配置与本地运行

编辑 config.php,填入你自己的服务器地址:

return [
  'minecraft' => [
    'host' => 'mc.example.com',
    'port' => 25565,
    'timeoutMs' => 1500,
    // 'cacheTtlSec' => 10,
  ],
];

本地快速跑起来(仓库根目录):

php -S 127.0.0.1:8000 -t public

然后访问:

部署到 Nginx/Apache 时,把站点根目录指向 public/ 即可。


可扩展点(如果你想继续打磨)

这个项目刻意保持“轻量”,但扩展也很自然:

  • 展示 MOTD / 版本号:API 里已经拿到了服务端返回的 data,可以按需提取字段
  • 同时监控多个服务器:让 config.php 支持列表,然后 API 增加参数或多端点
  • 更稳健的错误分类:例如区分 DNS 失败、连接超时、读超时、协议异常

常见问题排查

  • 一直离线 / connect failed:先确认运行 PHP 的机器能否访问 host:port(防火墙、NAT、云安全组)
  • 在线人数显示为 -:说明探测成功但没读到 players 信息(某些服务端/代理可能返回结构不同)
  • 延迟为 null:ping/pong 不是强制流程,若读取失败会返回空;也可能是网络抖动/超时

结语

这个项目的价值不在于“功能堆满”,而在于它把 Minecraft Java 的 Server List Ping 用 PHP 清晰地跑通了:连接、VarInt 编码、状态 JSON、以及一个足够实用的 API 封装。

正文完
 0
评论(没有评论)

YanQS's Blog