This commit is contained in:
boybook
2025-12-01 20:59:16 +08:00
parent 12738a142c
commit 760c2dd9ad
5535 changed files with 21070 additions and 2021 deletions

View File

@@ -0,0 +1,69 @@
---
front: https://nie.res.netease.com/r/pic/20220408/a5a602e2-e859-4a41-bf9b-3ada874fc9b7.png
hard: 入门
time: 5分钟
selection: true
---
# Apollo网络服架构
## 比较
### Java服
传统的Java服一般指使用Spigot等类似服务端的服务器玩家使用任意客户端都可以加入游戏。这种服务器纯靠服务器进行驱动如果不通过一些特殊手段无法强制客户端安装一些mod来增强游戏体验。
### Apollo
Apollo服务器不仅可以类似Java服那样使用服务器进行驱动还会同步发送相关与服务器匹配的Mod到客户端并且使用这些Mod和服务器进行双向通信来实现在触发某个事件后打开界面播放动画等效果。
需要注意的是在Java服中经常使用ProtocolLib或者net.minecraft.server(简称NMS)来绕过Spigot API来实现各种功能例如计分板title等等。而在Apollo的开发中并没有这些类似的协议API可供使用但是可以使用ModSDK来实现更强大的客户端表现功能。例如计分板功能可以使用ModSDK制作一个Hud来实现。这里需要Java服插件开发者们提前了解并转变思想。
## 架构
![](./images/structure-1.png)
如上图所示Apollo网络服拥有5种不同类型的服务器客户端(Client)和数据库(DB)
### 服务器
#### Proxy
Proxy即代理服务器所有玩家的连接都会通过代理服务器然后再分发到各个游戏服或大厅服中。功能和Java服的BungeeCord较为相似。只不过Proxy服务器无法像BungeeCord一样安装插件。
#### Game
Game即游戏服为玩家提供某个特定的玩法是玩家的主要载体。一个在线玩家只能存在于一个Game或Lobby中。
#### Lobby
Lobby即大厅服主要作为各个玩法间的中转站。玩家可以在大厅服中选择自己想要玩的玩法并提供跨服跳转的功能。其中Lobby服的接口和Game服基本一致。
#### Service
Service即功能服主要用作各个Game/Lobby服的后端服务器。可以使用Service服来集中处理一些其他服务器的数据。Service服可以主动/被动向各个服务器发送数据其他服务器也可以监听Service服的消息并调用回调完成一系列应答。
Service和Game的配合相对来说比较重要下面将举一个例子来介绍。
- gameA服务器制作了起床战争玩法。gameA服务器启动完成后就会通知Service服务器可以让玩家进入本服务器。
- 在gameA服务器上凑够了足够开局的玩家后gameA就会开始游戏逻辑并通知Service不要再匹配新的玩家进入该服务器。
- 在游戏结束后gameA将玩家重新送回大厅然后重置地图。
- 待重置地图完成后gameA再次通知Service本服务器已经可以让玩家加入了。
更多通信相关的内容将会在下一节进行说明。
#### Master
Master即控制服可以使用Master服来调用管理员指令完成一系列的运维操作。
### 客户端
客户端是玩家的终端每个玩家都可以看作一个Client。
### 数据库
数据库即之前所提到的数据的载体,具体详见[数据库的概念](../1-准备知识/3-数据库的概念.html)。

View File

@@ -0,0 +1,246 @@
# 服务器通信
服务器之间的信息交换是开发服务器非常重要的一个环节,本节将对部分通信相关的接口进行说明。
本节所使用的所有接口都可以在开发者文档上找到具体说明。
地址http://mc.163.com/dev/mcmanual/mc-dev/mcdocs/2-Apollo/4-SDK/8-%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%80%9A%E4%BF%A1.html
### 举例说明
#### 需求
制作一个每日签到系统
#### 通信流程
- 玩家登陆后Game/Lobby向Service发送玩家的uidService收到请求后查询玩家今天是否登陆过并返回数据给Game/Lobby。
- Game/Lobby收到数据后向Client发送某个打开UI的事件数据作为参数一同发送。
- Client收到打开UI的事件解析参数并调用ScreenNode创建UI显示到玩家客户端上。
- 玩家点击UI上的签到按钮Client向Game/Lobby发送签到事件。
- Game/Lobby收到签到事件向Service发送玩家签到事件。
- Service收到请求确认玩家当天没签到过并更新签到状态并回应Game/Lobby签到是否成功。
- Game/Lobby收到会用后调用回调根据参数判断签到是否成功成功的话给玩家发送签到奖励。
## 客户端UI
客户端UI相关的示例代码将会截取neteaseDaily插件的部分代码进行举例可以自行下载并在插件中找到对应代码进行学习。
客户端首先需要在初始化时使用下方代码监听Game/Lobby发来的打开界面请求。
然后将事件参数提供给ScreenNode做出相关解析。
dailyClientSystem.py
```python
# -*- coding: utf-8 -*-
import neteaseDailyScript.dailyConst as dailyConst
import client.extraClientApi as clientApi
ClientSystem = clientApi.GetClientSystemCls()
class DailyClientSystem(ClientSystem):
"""
该mod的客户端类
与服务端类通信
显示每日登录奖励的状态
"""
def __init__(self, namespace, systemName):
ClientSystem.__init__(self, namespace, systemName)
self.mPlayerId = clientApi.GetLocalPlayerId()
self.mDailyUINode = None
self.ListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), dailyConst.UiInitFinishedEvent, self, self.OnUiInitFinished)
self.ListenForEvent(dailyConst.ModName, dailyConst.ServerSystemName, dailyConst.DisplayDailyRewardEvent, self, self.OnDisplayDailyReward) # 服务端调用接口打开每日登录奖励界面
self.ListenForEvent(dailyConst.ModName, dailyConst.ServerSystemName, dailyConst.PlayerRecvEvent, self, self.OnPlayerRecv) # 玩家正常领取奖励后更新每日登录奖励界面
def Destroy(self):
self.mDailyUINode = None
self.UnListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), dailyConst.UiInitFinishedEvent, self, self.OnUiInitFinished)
self.UnListenForEvent(dailyConst.ModName, dailyConst.ServerSystemName, dailyConst.DisplayDailyRewardEvent, self, self.OnDisplayDailyReward)
self.UnListenForEvent(dailyConst.ModName, dailyConst.ServerSystemName, dailyConst.PlayerRecvEvent, self, self.OnPlayerRecv)
def OnUiInitFinished(self, *args):
# 注册UI 详细解释参照《UI API》
clientApi.RegisterUI(dailyConst.ModName, dailyConst.dailyUIName, dailyConst.dailyUIClsPath, dailyConst.dailyUIScreenDef)
# 创建UI 详细解释参照《UI API》
clientApi.CreateUI(dailyConst.ModName, dailyConst.dailyUIName, {"isHud": 1})
self.mDailyUINode = clientApi.GetUI(dailyConst.ModName, dailyConst.dailyUIName)
if self.mDailyUINode:
self.mDailyUINode.InitScreen()
else:
print '==== %s ====' % 'create UI: %s failed' % dailyConst.dailyUIScreenDef
def OnDisplayDailyReward(self, data):
print 'OnDisplayDailyReward', data
if self.mDailyUINode:
self.mDailyUINode.open(data)
#从服务器收到打开客户端UI的请求调用ScreenNode并传输数据data打开界面
def OnPlayerRecv(self, data):
print 'OnPlayerRecv', data
if self.mDailyUINode:
self.mDailyUINode.refresh(data)
```
在UI逻辑代码中编写点击领取按钮的相关逻辑代码。下方的代码就是确定领取的最关键的代码将请求发送到服务器。
可以看到其实最主要还是使用了ClientSystem#NotifyToServer函数,来传输请求到服务器。
dailyClientUI.py
```python
def recv(self):
print 'recv', self.m_valid_date
clientApi.GetSystem(dailyConst.ModName, dailyConst.ClientSystemName).NotifyToServer(
dailyConst.PlayerRecvEvent,
{'playerId': clientApi.GetLocalPlayerId(), 'valid': self.m_valid_date} # 传递服务端发送下来的日期过去验证,表示领取的是同一天的每日登录奖励
)
```
## 客户端和Game/Lobby通信
这个通信接口和开发Mod所使用的接口是同一个读者应该都对这个接口不陌生。
使用这个接口,客户端和服务器之间可以双向发送消息,具体使用方式如下。
```python
#服务端给客户端发送消息的示例
#client mod
class testClient(ClientSystem):
def __init__(self,namespace,systemName):
ClientSystem.__init__(self, namespace, systemName)
self.ListenForEvent('serverNamespace', 'serverSystem', 'PlayerJoinOKEvent', self, self.OnPlayerJoinOK)
def OnPlayerJoinOK(self, args):
#args的结果为{'uid':123, 'name':'nickname'}
print 'OnPlayerJoinOK', args
#game/lobby mod
class testServer(ServerSystem):
def __init__(self, namespace, systemName):
ServerSystem.__init__(self, namespace, systemName)
def testNotifyClient(self):
player = {}
player['uid'] = 123
player['name'] = 'nickname'
playerId = '456'
self.NotifyToClient(playerId, "PlayerJoinOKEvent", player)
```
```python
#客户端给服务端发送消息的示例
#client mod
class testClient(ClientSystem):
def __init__(self,namespace,systemName):
ClientSystem.__init__(self, namespace, systemName)
def testNotifyServer(self):
player = {}
player['uid'] = 123
player['name'] = 'nickname'
self.NotifyToServer("PlayerJoinEvent", data)
#game/lobby mod
class testServer(ServerSystem):
def __init__(self, namespace, systemName):
ServerSystem.__init__(self, namespace, systemName)
self.ListenForEvent('clientNamespace', 'clientSystem', 'PlayerJoinEvent', self, self.OnPlayerJoin)
def OnPlayerJoin(self, args):
#args结果为{'uid':123, 'name':'nickname'}
print 'OnPlayerJoin', args
```
## Service和Service/Master通信
此类通信通常用于不同服务器节点之间的数据传输,具体使用方法如下。
```python
#service同master通信示例.service同service通信与此类似这里不重复了
#service mod
class testService(ServiceSystem):
def __init__(self,namespace,systemName):
ServiceSystem.__init__(self, namespace, systemName)
#注册service方法注意一个事件只能注册一次否则后面监听函数会覆盖前面监听函数
self.RegisterRpcMethodForMod('PlayerJoinOKEvent', self.OnPlayerJoinOK)
def OnPlayerJoinOK(self, serverId, callbackId, args):
#args的结果为{'uid':123, 'name':'nickname'}
print 'OnPlayerJoinOK', args
response = {}
response['result'] = 1
self.ResponseToServer(serverId, callbackId, response)
#master mod
class masterServer(MasterSystem):
def __init__(self, namespace, systemName):
MasterSystem.__init__(self, namespace, systemName)
def OnCallback(self, suc, args):
#若成功suc=Trueargs= {'result' : 1}
#若超时则suc为False
if not suc:
print 'OnCallback timeout'
return
print 'OnCallback success', args
def testNotifyService(self):
player = {}
player['uid'] = 123
player['name'] = 'nickname'
self.RequestToServiceMod("idv_service", "PlayerJoinOKEvent", data, self.OnCallback, 2)
```
在上方的示例代码中调用了3个相关的接口下面将解释这3个接口的具体使用方法。
### RegisterRpcMethodForMod
service接口用来监听master发来的请求并将请求参数带入回调函数。
如上方代码所示service在收到**PlayerJoinOKEvent**后,将会触发**OnPlayerJoinOK**函数。
### ResponseToServer
service接口用来回应master的通信请求。
在上方代码中service服务器收到了请求进行处理后会将操作成功的结果再次发送回master服务器同时调用master的回调函数**OnCallback**。
如果请求没有在2秒钟内完成则会判定超时。
超时秒数可以在master的函数中进行配置可根据自己的需要自行调整。
### RequestToServiceMod
master接口用来发送数据到指定的service服务器。
其中第一个参数为module需要提前在service的mod.json中配置好module_names。
其他参数均没有什么需要特别注意的地方,参考官方文档,即可查询相关参数的具体含义。链接在本页顶部。
## Service和Lobby/Game通信
**Service和Lobby/Game**之间的通信方式,基本与**Service和Service/Master通信**一致。都是使用的```RegisterRpcMethodForMod```、```ResponseToServer```、```RequestToServiceMod```上方提到的这3个接口。demo也基本一致下方代码供参考。
```python
#service同game/lobby通信示例
#service mod
class testService(ServiceSystem):
def __init__(self,namespace,systemName):
ServiceSystem.__init__(self, namespace, systemName)
self.RegisterRpcMethodForMod("idv_service", 'PlayerJoinOKEvent', self.OnPlayerJoinOK)
def OnPlayerJoinOK(self, serverId, callbackId, args):
#args的结果为{'uid':123, 'name':'nickname'}
print 'OnPlayerJoinOK', args
#lobby mod
class lobbyServer(ServerSystem):
def __init__(self, namespace, systemName):
ServerSystem.__init__(self, namespace, systemName)
def testNotifyService(self):
player = {}
player['uid'] = 123
player['name'] = 'nickname'
self.RequestToServiceMod("idv_service", "PlayerJoinOKEvent", args)
```
除此之外Service和Lobby/Game之间还有更多接口可供调用。
具体可以点击<a href="../../../mcdocs/2-Apollo/4-SDK/8-服务器通信.html#service和game/lobby通信" rel="noopenner"> 这里 </a>查看。

View File

@@ -0,0 +1,106 @@
# 插件文件夹结构
基于之前的了解插件一共有4种类型分别是lobbyMod、gameMod、serviceMod、masterMod。
它们的文件夹结构不完全相同,本节将会就文件夹的内容和功能进行介绍。
## lobbyMod/gameMod
```
xxxMod
developer_mods ——服务器mod不会被传输到客户端
xxxModDev ——服务器mod根目录
mod.json ——插件的配置文件和基本信息
netease_require.json ——插件所用的依赖(可缺省)
scripts ——服务端脚本
behavior_packs
xxxModBeh ——客户端mod根目录
scripts ——客户端脚本
manifest.json ——资源版本信息
resource_packs
xxxModRes ——资源根目录
manifest.json ——资源版本信息
ui —— ui文件
... ——其他资源文件
worlds
level
db
level.dat ——地图全局数据
levelname.txt ——地图名
world_behavior_packs.json ——配置客户端需要下载的behavior mods
world_resource_packs.json ——配置客户端需要下载的resource mods
mod.sql ——数据表创建的SQL语句(可缺省)
readme.txt ——插件介绍
```
developer_mods和behavior_packs区别
- developer_mods控制服务端行为behavior_packs控制客户端行为。
- behavior_packs会下载给客户端developer_mods不会。
- developer_mods可以使用Apollo SDK全部接口和MOD SDK中服务端相关接口behavior_packs使用MOD SDK中客户端相关接口。
- behavior_packs必须包含manifest.json文件且需要在地图目录下world_behavior_packs.json文件中配置pack id和version。
world_behavior_packs.json和world_resource_packs.json文件可以不需要自己配置使用MCS部署插件时如果文件不合法将会自动进行修复。
### neteaseDaily举例
```
neteaseDaily
developer_mods ——服务器mod不会被传输到客户端
neteaseDailyDev ——服务器mod根目录
mod.json ——插件的配置文件和基本信息
neteaseDailyScript ——服务端脚本根目录
behavior_packs
neteaseDailyBehavior ——客户端mod根目录
neteaseDailyScript ——客户端脚本根目录
resource_packs
neteaseDailyRes
manifest.json ——资源版本信息
ui ——ui文件
textures ——材质文件
worlds
level
world_behavior_packs.json ——配置客户端需要下载的behavior mods
world_resource_packs.json ——配置客户端需要下载的resource mods
readme.txt ——插件介绍
```
## serviceMod/masterMod
```
xxxMod
developer_mods ——服务器mod
xxxModDev ——服务器mod根目录
mod.json ——插件的配置文件和基本信息
netease_require.json ——插件所用的依赖(可缺省)
scripts ——服务端脚本
mod.sql ——数据表创建的SQL语句(可缺省)
readme.txt ——插件介绍
```
serviceMod/masterMod和lobbyMod/gameMod相比少了behavior_packs,resource_packs和worlds。该插件只能被运行在服务端上。
### neteaseDailyService举例
```
neteaseDailyService
developer_mods ——服务器mod不会被传输到客户端
neteaseDailyDev ——服务器mod根目录
mod.json ——插件的配置文件和基本信息
neteaseDailyScript ——服务端脚本
mod.sql ——数据表创建的SQL语句
readme.txt ——插件介绍
```
### 插件目录生成工具
按照实际需求插件由若干个服务器mod组成可包含lobby/game大厅/游戏服mod、service功能服mod以及master控制服mod。
各类服务器mod的目录结构已在<a href="../../../mcguide/27-手机网络游戏/课程2Apollo基础知识/第3节服务器Mod.html" rel="noopenner"> 第3节服务器Mod </a>中说明。
开发者可下载[生成插件模板工具](https://g79.gdl.netease.com/template.zip),在实际应用中快捷生成插件目录。