Skip to content

Commit 705bb5f

Browse files
committed
[修改]1. 修改README
1 parent a0413b1 commit 705bb5f

File tree

1 file changed

+256
-34
lines changed

1 file changed

+256
-34
lines changed

README.md

Lines changed: 256 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,296 @@
11
# GameFrameX介绍:
22

3-
GameFrameX 是基于GeekServer是一个开源的[分区分服](https://mp.weixin.qq.com/s?__biz=MzI3MTQ1NzU2NA==&mid=2247483884&idx=1&sn=3547c769a300f1d82cc04e9b1852c6d5&chksm=eac0cd9fddb7448997e38a74e2d26bde259cd2127583e31bc488511bc1fdcd9f35caff27d4a3&scene=21#wechat_redirect)
4-
的游戏服务器框架,采用C# .Netcore开发,开发效率高,性能强,跨平台,并内置不停服热更新机制。可以满足绝大部分游戏类型的需求,特别是和Unity3D协同开发更佳。
5-
__设计理念:大道至简,以简化繁__
6-
7-
# 程序集说明
3+
GameFrameX 是基于 `GeekServer` 是一个开源的[分区分服](https://mp.weixin.qq.com/s?__biz=MzI3MTQ1NzU2NA==&mid=2247483884&idx=1&sn=3547c769a300f1d82cc04e9b1852c6d5&chksm=eac0cd9fddb7448997e38a74e2d26bde259cd2127583e31bc488511bc1fdcd9f35caff27d4a3&scene=21#wechat_redirect)
4+
的游戏服务器框架,采用C# .Netcore开发,开发效率高,性能强,跨平台,并内置不停服热更新机制。可以满足绝大部分游戏类型的需求,特别是和Unity3D协同开发更佳。
85

9-
| 程序集名称 | 介绍 | 用途 |
10-
|:-------------------:|:--------------------------:|:--------------------------------------------------------------:|
11-
| GameFrameX.Launcher | 程序启动入口 | 用于编写启动逻辑 |
12-
| GameFrameX.Hotfix | 热更新逻辑和处理程序对象放置区(该部分为热更新内容) | 用于编写逻辑的地方(`主要工作区`,目录和`Apps`目录结构一致) |
13-
| GameFrameX.Apps | 组件和实体对象放置区(该部分不能热更新) | 用于编写基础逻辑和数据存储(`功能的添加修改主要工作区`,目录结构按照`ServerType` 划分和`HotFix`对应) |
14-
| GameFrameX.Config | 配置文件对象和配置文件放置区 | 用于编写配置文件的类和文件的映射(目前由LuBan自动生成) |
15-
| GameFrameX.Proto | 数据通讯协议对象放置区 | 用于编写通讯协议对象 |
6+
__设计理念:大道至简,以简化繁__
167

178
# GameFrameX功能:
189

19-
### 1.跨平台
10+
## 1.跨平台
2011

2112
使用C# .Netcore开发(可以跨平台,可以跨平台,可以跨平台),.Netcore现在功能和性能都已经十分强大和稳健,不管是在windows还是linux上部署起来都很简便。
2213

23-
### 2.全面异步编程
14+
## 2.全面异步编程
2415

2516
全部采用异步编程(async/await),让逻辑代码变得整洁优雅,清晰易懂,让代码写起来行如流水。
2617

27-
### 3.TPL(Task Parallel Library) Actor模型
18+
## 3.TPL(Task Parallel Library) Actor模型
2819

29-
Actor模型构建于强大的TPL DataFlow之上,让Actor模型如虎添翼。(不了解Actor模型,可以搜一下相关资料,Akka,Orleans都是采用的Actor模型)[了解更多](https://github.com/leeveel/GeekServer/blob/main/Docs/1.Actor%E6%A8%A1%E5%9E%8B.md)
20+
Actor模型构建于强大的TPL DataFlow之上,让Actor模型如虎添翼。(不了解Actor模型,可以搜一下相关资料,Akka,Orleans都是采用的Actor模型)
3021

31-
### 4.Actor入队透明化
22+
## 4.Actor入队透明化
3223

3324
内部会自动处理线程上下文, 编译期间会通过[Source Generator](https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview)自动生成入队代码, 开发人员无需关心多线程以及入队逻辑,
34-
只需要像调用普通函数一样书写逻辑。[了解更多](https://github.com/leeveel/GeekServer/blob/main/Docs/Actor%E5%85%A5%E9%98%9F.md)
25+
只需要像调用普通函数一样书写逻辑。
26+
27+
#### Actor入队透明
28+
29+
`GameFrameX` 编译期间会自动注入入队代码(**AgentWeaver**), 开发人员无需入队逻辑, 只需要像调用普通函数一样书写逻辑。
30+
31+
```c#
32+
//编译期间会注入一个继承自xxxCompAgent的wrapper类,来实现自动入队
33+
//同时SendAsync内部自动处理了线程上下文,开发者只需要像调用普通函数一样书写逻辑
34+
public class ServerComponentAgentWrapper : ServerComponentAgent
35+
{
36+
public override Task CheckCrossDay()
37+
{
38+
return base.Actor.SendAsync((Func<Task>)base.CheckCrossDay, isAwait: false, 10000);
39+
}
40+
41+
public override Task<int> GetDaysFromOpenServer()
42+
{
43+
return base.Actor.SendAsync((Func<Task<int>>)base.GetDaysFromOpenServer, isAwait: true, 10000);
44+
}
45+
}
46+
47+
var serverComp = await EntityMgr.GetCompAgent<ServerComponentAgent>(ActorType.Server);
48+
//使用方式(就像调用普通函数一样,无需关心多线程或入队)
49+
_ = serverComp.CheckCrossDay();
50+
51+
```
52+
53+
#### 线程上下文
54+
55+
`GameFrameX` 内部会自动处理线程上下文,由RuntimeContext实现,主要用于环路调用检测,以及判断是否需要入队,其内部使用**AsyncLocal**实现
56+
57+
```c#
58+
internal class RuntimeContext
59+
{
60+
internal static long Current => callCtx.Value;
61+
internal static AsyncLocal<long> callCtx = new AsyncLocal<long>();
62+
63+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
64+
internal static void SetContext(long callChainId)
65+
{
66+
callCtx.Value = callChainId;
67+
}
68+
69+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
70+
internal static void ResetContext()
71+
{
72+
callCtx.Value = 0;
73+
}
74+
}
75+
```
76+
77+
## 5.Actor死锁检测
78+
79+
#### Actor模型 ##
80+
81+
actor模型是什么可以自行查阅一下相关资料;actor模型在一定程度可以说是解决并发的最佳方案。
82+
83+
`GameFrameX` 的actor可以简单理解为一个线程(其实用的是线程池),一个actor的逻辑只需保证在自己的队列里面执行即保证可线程安全,无需关心锁的问题。 `GameFrameX` 的基础就是actor,一切皆可为actor。 `GameFrameX` 的actor模型构建于强大的TPL DataFlow之上,底层使用内置线程池。 `GameFrameX` 同时使用异步变成(async/await),让逻辑代码更加清晰明了,符合人类的思维方式。
84+
85+
#### Actor死锁 ##
86+
87+
Actor模型本身是存在死锁的情况,且不容易被发现。 `GameFrameX` 内部可检测环路死锁(即:A->B->C->A),并采用调用链重入机制消除环路死锁(类似与线程的可重入性)。
88+
还有另外一种情况(多路死锁),比如有2个actor,A和B,一段逻辑A调用B,同时有另外一段逻辑发起了B调用A,就会出现A等B,B再等A,此时发生死锁。同理 [A->B->C,C->D->A] [A->B->C,B->C->A],这样的调用路径都可能会存在死锁。
89+
这种死锁无法解决,而且不确定,因为它和调用时间节点有关系,只能从设计上去规避。
90+
91+
```csharp
92+
class ActorA
93+
{
94+
Task A1()
95+
{
96+
await Task.Delay(10);
97+
var b = GetActorB();
98+
return b.SendAsync(b.B1);
99+
}
100+
101+
Task A2()
102+
{
103+
var b = GetActorB();
104+
return b.SendAsync(b.B1);
105+
}
106+
}
107+
108+
class ActorB
109+
{
110+
Task B1()
111+
{
112+
await Task.Delay(5);
113+
var a = GetActorA();
114+
return a.SendAsync(a.A2);
115+
}
116+
}
117+
118+
Task Call()
119+
{
120+
var a = GetActorA();
121+
return a.SendAsync(a.A1);//这里就会触发死锁
122+
}
123+
```
124+
125+
如果得到打印日志【执行超时】很有可能就是触发了死锁。由于Actor设定了超时时间,在断点调试的时候需要手动将超时时间改长,默认10秒,在BaseActor.TIME_OUT修改。**(或者用一个Debug模式的宏来控制)**
126+
127+
#### 多路死锁解决方案 ##
128+
129+
由以上的分析可以看出,多路死锁其实是由于**Await**引起的,如果所有调用都不Await则永远不会有死锁。
130+
这里为大家罗列了几种解决方案:(在 `GameFrameX` 中可以通过ActorLimit来定制自己的检测规则)
131+
132+
1. **跨Actor调用,不能Await**
133+
优点:规则简单,统一,绝对不会发生死锁
134+
缺点:失去了异步语法的优势,所有需要跨Actor获取返回值的,只能使用回调,代码结构散乱,书写代码不方便。
135+
136+
2. **为Actor分配等级,只允许低等级await调用高等级(如:Role->Server 而Server不能等待调用Role, 推荐使用此方案)**
137+
案例:公民可以去政府部门排队等待办理业务员,但是政府部门不可能去等待某个人去处理完自己的私事,再帮下一个人办理业务,顶多发短信或者打电话通知某个人做什么事情。
138+
在方案1的基础上,给了使用者更多的异步调用空间。
139+
140+
3. **提供一个注册接口,注册哪些Actor可以Await调用哪些Actor,并在注册时候进行检测看是否可能存在死锁**
141+
这是方案2的一个更加灵活的变种。
142+
143+
4. **允许交错执行** [了解更多](https://blog.csdn.net/uddiqpl/article/details/86294520)
144+
如果你的某个组件不会操作数据或者对操作顺序不敏感,交错执行是很有用的。 `GameFrameX` 中的FuncComponent比较符合这个特点,但不绝对,仍需要开发人员自行判断。 (Orleans中有此方案)
145+
146+
5. **允许存在多路死锁的风险,由开发人员保证不会触发死锁**
147+
优点:规则简单,统一,书写代码很方便,全程异步。
148+
缺点:有可能发生死锁,对编码人员能力要求较高
149+
150+
6. **超时规则**
151+
如果发生多路死锁,选择一条调用路径并终止,保证其中一条调用路径正确。(Orleans中有此方案)
152+
优缺点和方案5一样。
153+
154+
7. **使用线程安全容器以及lock等**
155+
但这与无锁化设计的理念冲突
35156

36-
### 5.Actor死锁检测
157+
## 6.支持不停服更新
37158

38-
Actor模型本身是存在死锁的情况,且不容易被发现。内部可检测环路死锁(即:A->B->C->A),并采用调用链重入机制消除环路死锁。[了解更多](https://github.com/leeveel/GeekServer/blob/main/Docs/1.Actor%E6%A8%A1%E5%9E%8B.md)
159+
采用组件+状态的分离设计实现不停服热更新:
39160

40-
### 6.支持不停服更新
161+
1. 架构设计优势
41162

42-
采用组件+状态的设计,状态只有属性,没有方法,组件只用方法,没有属性,并通过代理的方式全部放到热更dll中,运行时重新加载dll即可热更所有逻辑。[了解更多](https://github.com/leeveel/GeekServer/blob/main/Docs/%E7%83%AD%E6%9B%B4hotfix.md)
163+
- 状态与逻辑完全分离,状态类只包含数据属性
164+
- 组件类只包含业务逻辑方法,不持有状态
165+
- 通过代理模式将所有逻辑代码放入热更新dll
166+
- 运行时只需重载dll即可更新全部业务逻辑
43167

44-
### 7.网络模块
168+
2. 热更新优势
45169

46-
网络模块采用SuperSocket的默认服务器Kestrel,支持协议多(Tcp,udp,Http123,websocket,signalr等),而且性能高[了解更多](https://github.com/leeveel/GeekServer/blob/main/Docs/%E7%BD%91%E7%BB%9CNet(tcp%26http).md)
170+
- 无需停服即可更新游戏逻辑
171+
- 玩家无感知,不会影响游戏体验
172+
- 支持增量更新,只更新修改的逻辑
173+
- 更新过程安全可靠,失败可回滚
47174

48-
### 8.持久化透明
175+
3. 开发效率提升
49176

50-
采用Nosql作为数据存储,状态的持久化全透明,框架会自动序列化/反序列,让开发人员更加专注于业务逻辑,无需操心数据库。 [了解更多](https://github.com/leeveel/GeekServer/blob/main/Docs/2.Actor%26Component%26State.md)
177+
- 逻辑bug修复无需重启服务器
178+
- 新功能可以随时发布更新
179+
- 减少维护成本和停机时间
180+
- 便于快速验证和调试
51181

52-
### 9.Timer/Scheduler/Event
182+
#### 热更新
53183

54-
内置线程安全的Timer,Scheduler,Event系统,轻松应对游戏服务器常见的定时,任务计划,事件触发等业务需求。[了解更多](https://github.com/leeveel/GeekServer/blob/main/Docs/%E4%BA%8B%E4%BB%B6Event-timer.md)
184+
`GameFrameX` 支持不停服热更新逻辑。
55185

56-
### 10.定期释放不活跃内存数据
186+
#### 热更思路
187+
188+
游戏中的所有状态放在App工程中,始终存在于内存,不能热更。Actor和Component的逻辑使用代理模式(Agent)放到Hotfix工程。热更时载入新的dll( `GameFrameX` .Hotfix.dll),清除所有老的Agent,所有新逻辑重新从Actor/Component获取新的Agent汇入新dll中执行热更后的逻辑,达到热更目的。正在执行老dll逻辑的代码获取的Agent依然来自热更前的老Dll中,等待老dll中的逻辑执行完后清理掉内存中老的dll。底层使用接口驱动热更dll中的逻辑。
189+
需要注意的是,热更时新的dll需要放在新的目录下面,然后再载入内存,因为老的dll可能正在运行,是无法直接覆盖的。
190+
191+
##### 可以热更部分
192+
193+
可以热更的逻辑都应该放在 `GameFrameX` .Hotfix工程中
194+
195+
1. 所有Actor/Component的Agent,Agent中只有逻辑没有状态,状态全部放到Component的State
196+
2. HttpHandler
197+
3. TcpHandler
198+
4. 协议
199+
5. 配置表/配置表代码
200+
201+
##### 热更新流程
202+
203+
1. 游戏后台将新的 `GameFrameX` .Hotfix.dll及相关文件(对应pdb,json等)拷贝到游戏服特定目录下
204+
2. 游戏后台向游戏服发送http命令,通知进行热更,并告知dll目录,md5等信息
205+
3. 游戏服中热更HttpHandler根据后台信息,验证热更dll完整性,合法性,修改dllVersion.txt,发起热更调用
206+
207+
## 7.网络模块
208+
209+
网络模块采用SuperSocket作为底层网络框架,具有以下优势:
210+
211+
1. 高性能和可扩展性
212+
213+
- 基于.NET Core的高性能网络库
214+
- 采用异步IO模型,支持高并发连接
215+
- 内置连接池和内存池优化,减少GC压力
216+
- 可配置的线程模型,支持灵活的扩展
217+
218+
2. 协议支持全面
219+
220+
- 支持TCP、UDP、WebSocket等多种传输协议
221+
- 内置HTTP 1.1/2/3协议支持
222+
- 支持SignalR实时通信
223+
- 支持自定义二进制协议
224+
- 支持SSL/TLS加密传输
225+
226+
3. 功能特性丰富
227+
228+
- 内置会话管理和心跳检测
229+
- 支持消息过滤和管道处理
230+
- 提供命令过滤器机制
231+
- 支持消息分包和粘包处理
232+
- 支持二进制和文本消息编解码
233+
234+
4. 开发便捷性
235+
236+
- 简单易用的API设计
237+
- 完善的文档和示例
238+
- 支持配置化开发
239+
- 提供多种扩展点
240+
- 活跃的社区支持
241+
242+
[了解更多SuperSocket详情](https://github.com/GameFrameX/GameFrameX.SuperSocket)
243+
244+
## 8.持久化透明
245+
246+
采用Nosql作为数据存储,状态的持久化全透明,框架会自动序列化/反序列,让开发人员更加专注于业务逻辑,无需操心数据库。
247+
248+
#### Entity和Component和State
249+
250+
`GameFrameX` 的Entity包含1个/多个Component,Component包含0个/1个State,你可以这样理解:Entity=实体,Component=功能系统,State=功能系统的数据。每个Entity都包含一个Actor成员变量,Entity的所有行为调度都有Actor完成,以保证线程安全。
251+
252+
Entity是 `GameFrameX` 的基础,一切功能都可以作为单独的Entity(比如一个角色,一个公会,一个玩法系统。。。),Component隶属于Entity,State隶属于Component,Component承载各种逻辑,State承载各种数据。Entity拆分方式根据项目的具体需求可以任意规划。
253+
254+
#### Entity(Actor)拆分
255+
256+
1. 尽可能独立(一个系统或者玩家的操作尽量不阻塞其他玩家的操作)
257+
2. 在独立的前提下尽可能少(节约管理成本)
258+
3. 一个角色,包含若干个功能系统(背包,宠物,任务。。。)
259+
4. 一个公会,包含多个公会基础系统(基础信息,捐赠,工会副本。。。)
260+
261+
比如有2个全局玩法,世界boss和工会战,如果这2个系统归于一个Actor,那么当一个玩家挑战世界boss时,挑战逻辑到Actor线程中执行,此时另一个玩家要去报名工会战,报名逻辑也需要到Actor线程中执行,此时报名公会战的逻辑则需要等待前一个玩家挑战世界boss逻辑完成后才能执行,客户端表现可能就是更长时间的网络连接中,在cpu够用的情况下就产生了多余的等待时间,这就有些不合理了;这种功能之间没有任何交集的情况下我们建议将世界boss和工会战分别拆分为Actor,各自的逻辑便可以更加顺畅的执行,客户端等待的时间也越短。
262+
263+
## 9.Timer/Scheduler/Event
264+
265+
内置线程安全的Timer,Scheduler,Event系统,轻松应对游戏服务器常见的定时,任务计划,事件触发等业务需求。
266+
267+
#### 定时器&计划任务
268+
269+
`GameFrameX` 中没有使用传统意义中的Update,除MMO项目,其他大部分游戏类型的服务器基本没有必要使用Update,需要Update的模块添加一个Timer也可以实现
270+
根据热更新设计方案,定时器和计划任务采用接口方式进行回调,任务使用扩展方法实现,
271+
272+
定时器支持:1次性delay,周期性timer。
273+
计划任务:指定时间1次性任务,每天任务,每周任务,每周几任务,每月任务。
274+
`GameFrameX` 中没有对定时器&计划任务做持久化,所以你可能需要在开服后、玩家上线或者Component激活时考虑一下计划任务逻辑是否需要被处理了。
275+
回调函数继承TimerHandler<>,重写HandleTimer,里面处理定时器回调逻辑即可。
276+
需要注意的是定时器是接入的Quartz,由于硬件精度问题(windows时间实际精度为10毫秒左右),回调时间可能会提前1-2毫秒,如果对时间依赖特别大的可能需要特殊处理下,比如在Timer回调后延时50毫秒再执行回调逻辑。
277+
278+
## 10.定期释放不活跃内存数据
57279

58280
以功能系统级别的粒度,定期剔除内存中不活跃的玩家数据,尽最大可能减少服务器内存开销。
59281

60282
# 运行
61283

62-
1. 安装[.NetCore8.0](https://dotnet.microsoft.com/download/dotnet/6.0)
284+
1. 安装[.NetCore8.0](https://dotnet.microsoft.com/download/dotnet/8.0)
63285
2. 安装[mongodb4.x](https://www.mongodb.com/try/download/community)
64286
3. 打开git clone本项目
65-
4. 用VisualStudio 或者Rider打开Server.sln 启动 G`ameFrameX.Launcher`
287+
4. 用VisualStudio 或者Rider打开Server.sln 启动 `GameFrameX.Launcher`
66288
5. 打开Unity工程,打开Launcher 场景,运行查看日志
67289

68290
# 文档&例子&Demo
69291

70-
[视频教程](https://www.bilibili.com/video/BV1yrpeepEn7/)
71-
72-
[参考文档](https://gameframex.doc.alianblank.com/)
292+
[视频教程](https://www.bilibili.com/video/BV1yrpeepEn7)
73293

294+
[参考文档](https://gameframex.doc.alianblank.com)
74295

296+
[项目主页](https://github.com/GameFrameX)

0 commit comments

Comments
 (0)