Minecraft 使用 客户端/服务器模型(Client-server model),即用户安装游戏客户端(Client)并连接到服务器(Server)以玩游戏。 Fabric 允许模组既可以针对 Minecraft 客户端,也可以同时针对 Minecraft 服务器。
客户端/服务器(client/server)的概念在 Minecraft 中不明确的,并可能指代物理或逻辑端。术语客户端/服务器有时用于区分的 Minecraft 的不同发行版(Minecraft 客户端和独立的 Minecraft 服务器),也就是“物理”端(“physical” sides)。然而,Minecraft 客户端也托管(hosts)着一个自己的内置服务器(integrated server)用于单人和本地局域网游戏,这意味着 Minecraft 客户端也包含了服务器的逻辑。因此,客户端/服务器也可以用来区分的游戏逻辑的不同部分被称为“逻辑”端(“logical” sides)。
这两种不同类型的端都有一个“客户端”和一个“服务器”。但逻辑客户端并不等于物理客户端,逻辑服务器也并不等于物理服务器。相反的,逻辑客户端是被托管在(hosted by)物理客户端上的,逻辑服务器也是被托管在物理服务器或物理客户端上的。
逻辑端是这两个 Minecraft 发行版的体系架构中的重要核心。因此理解逻辑端对任何使用 Fabric 的模组开发都至关重要。
物理端(Physical Sides)或环境(Environment)是指 Minecraft 游戏的两个发行版(两个 jar 文件):客户端(使用原版启动器启动的)和服务器(可从 https://minecraft.net 免费下载的)。物理端是指当前环境中可用的代码。
客户端和服务器环境是同一程序的精简发行版,仅包含必需的部分代码。
在 Fabric 中,你经常可以看到诸如 @Environment(EnvType.CLIENT)
之类的注解。这表明某些代码只会在一种环境中存在;这个例子中,意味着仅在客户端中存在。
在 Fabric 的 fabric.mod.json 和 mixin 配置中,客户端/服务器指代环境。
每个物理端都会提供由入口点和带有 net.minecraft.data.Main
入口点的数据生成器类使用的类。
逻辑端(Logical Sides)是负责实际游戏逻辑的。逻辑客户端进行渲染、将玩家输入发送到服务器、处理资源包并部分模拟游戏世界。服务器处理核心游戏逻辑、数据包(data packs)并维护游戏世界的真实状态。
客户端维护服务器世界的部分副本,其中包含例如以下对象的副本:
net.minecraft.world.World net.minecraft.entity.Entity net.minecraft.block.entity.BlockEntity
这些复制的对象使客户端和服务器能执行一些常见的游戏逻辑。客户端能够与这些对象进行交互,而服务器则负责保持它们的同步。通常,可以通过访问对象所在世界的 isClient
字段来区分逻辑客户端和逻辑服务端。这可以用来在服务器上执行需要授权的操作,例如生成实体,也用于在客户端上模拟操作。为了避免两个逻辑端之间的不同步,这样的技术是必需的。
有了之前对这些端是什么以及如何区分它们的知识,我们现在可以分别深入了解这些端的细节。
物理客户端(Physical Client)是指由原版启动器下载的 Minecraft jar 文件。它包含一个逻辑客户端和一个逻辑服务器(内置服务器)。它的入口点(entrypoint)是 net.minecraft.client.main.Main
。
一个物理客户端能够加载多个不同的世界,分别加载在单独的逻辑服务器内,但一次只能加载一个。
相比物理服务器(独立服务器)上的逻辑服务器,物理客户端上的逻辑服务器(内置服务器)能被物理客户端上的逻辑客户端控制(例如 F3 + T 会重新加载数据包(data packs),关闭客户端并同时关闭内置服务器)。它还可以将世界里捆绑的资源包加载到物理客户端上的逻辑客户端。
所有逻辑客户端相关的内容都是物理客户端独占的,所以你会看到在渲染、声音和其他逻辑客户端代码上有许多环境注解。
一些模组只针对物理客户端,例如 Liteloader、Optifine 和 Minecraft PvP 客户端(Badlion、Hyperium)。
物理服务器(Physical Server)是指 java 的专用服务器。相比物理客户端,它只有一个逻辑服务器(独立服务器)。它的入口点(entrypoint)是 net.minecraft.server.MinecraftServer
,且物理服务器在运行期间只能存在一个世界。要切换到另一个世界,就必须重启服务器。
物理服务器上的逻辑服务器与物理客户端上的略有不同,因为当物理服务器运行时只存在一个逻辑服务器实例。此外,物理服务器上的逻辑服务器能通过 Rcon 被远程控制,拥有名为 server.properties 的配置文件,也可以发送服务器资源包。
尽管存在这些差异,大部分模组都同时适用于物理客户端上的和物理服务器上的逻辑服务端,只要它们不使用逻辑客户端的内容。
由于它的单一世界和资源包发送特性,原版模组(数据包和资源包的组合)的安装在物理服务器上要比在物理客户端上容易得多,因为物理客户端会在连接到服务器时自动进行设置。
一些模组只针对物理服务器。例如 Bukkit 及其衍生物(Spigot、Paper、Cauldron、某某-Bukkit 混合产物)始终在物理服务器上运行。
逻辑客户端(Logical Client)是玩家的接口。渲染(LWJGL)、资源包、处理玩家输入和声音都在逻辑客户端上进行。它在物理服务器上不存在。
逻辑服务器(Logical Server)是执行大多数游戏逻辑的地方。数据包、世界更新、方块实体和实体刻的处理、怪物 AI、游戏/世界保存以及世界生成都在逻辑服务器上发生。
物理客户端上的逻辑服务器称为“内置服务器(Integrated Server)”,而物理服务器上的逻辑服务器称为“专用服务器(Dedicated Server)”(这也是物理服务器本身的名称)。
即使在物理服务器上,逻辑服务器也运行在它自己的主线程中并拥有一些工作线程。逻辑服务器的生存周期取决于托管它的物理端。在物理服务器上,逻辑服务器的生存周期与进程的生存周期一样长。在物理客户端上可以创建多个逻辑服务器,但一次只能存在一个逻辑服务器。当玩家打开本地世界时将创建一个新的逻辑服务器,并在玩家关闭本地世界时将其关闭。
大多数通用模组都针对逻辑服务器,因此在单人和多人游戏中都能运行。
在逻辑客户端和逻辑服务器之间唯一正确的数据交换方式是交互数据包(packets)。这些数据包(文档在 https://wiki.vg(英文)上)在逻辑客户端和逻辑服务器而不是物理端之间发送。mod 可以添加数据包(packets)从而在两个逻辑端之间传输自定义信息。连接自己的内置服务器的逻辑客户端使用内存来交换数据包(packets),其他情况下则通过网络协议来交换。
逻辑客户端发送 C2S(客户端到服务器,Client-To-Server)数据包(packets)到逻辑服务器。逻辑服务器发送 S2C(服务器到客户端,Server-To-Client)数据包(packets)到逻辑客户端。数据包(packets)通过网络线程中的 write 方法发送,通过网络线程中的 read 方法调用接收。
关于如何处理网络的更多详细信息,请参见这篇文章。
大多数情况下,针对物理服务器的模组也能在物理客户端上的逻辑服务器上运行。
但是,模组作者时常会抱有一些并不适用于内置服务器的误解,包括但不限于:
isClient
字段肯定是 false)制作在逻辑服务器上运行的模组时必须更正这些误解。
可能的物理端和逻辑端组合:
逻辑客户端 | 逻辑服务器 | |
---|---|---|
物理客户端 | 始终存在一个实例 | 在本地世界中存在;每次打开本地世界都创建新的实例 |
物理服务器 | 不存在 | 始终存在一个实例 |
最终,主要的困惑来源于物理客户端上存在着逻辑服务器这一事实。