/GeekServer

基于.Netcore的开发效率高,性能强,跨平台,持久化层透明,支持不停服热更新的游戏服务器。Best for your unity game server!

Primary LanguageC#MIT LicenseMIT

QQ交流群 : 228688193

GeekServer介绍:

GeekServer是一个开源的单服结构的游戏服务器框架,采用C# .Netcore开发,开发效率高,性能强,跨平台,并内置不停服热更新机制,支持http tcp。可以满足绝大部分游戏类型的需求,特别是和Unity3D协同开发更佳。
设计理念:大道至简,以简化繁

GeekServer功能:

1.跨平台

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

2.全面异步编程

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

3.TPL(Task Parallel Library) Actor模型

GeekServer的Actor模型构建于强大的TPL DataFlow之上,让Actor模型如虎添翼。(不了解Actor模型,可以搜一下相关资料,Akka,Orleans都是采用的Actor模型)

4.Actor入队透明化

GeekServer内部会自动处理线程上下文, 编译期间会自动注入入队代码, 开发人员无需关心多线程以及入队逻辑, 只需要像调用普通函数一样书写逻辑

//编译期间会注入一个继承自xxxCompAgent的wrapper类,来实现自动入队
//同时SendAsync内部自动处理了线程上下文,开发者只需要像调用普通函数一样书写逻辑
public class ServerCompAgentWrapper : ServerCompAgent
{
	public override Task CheckCrossDay()
	{
		return base.Actor.SendAsync((Func<Task>)base.CheckCrossDay, isAwait: false, 10000);
	}

	public override Task<int> GetDaysFromOpenServer()
	{
		return base.Actor.SendAsync((Func<Task<int>>)base.GetDaysFromOpenServer, isAwait: true, 10000);
	}
}

5.Actor死锁检测

Actor模型本身是存在死锁的情况,且不容易被发现,GeekServer在debug模式下提供了检测机制,让死锁问题暴露在开发过程中。GeekServer内部可检测单路死锁和多路死锁,并采用调用链重入机制消除单路死锁,多路死锁时也可由开发人员判断是否可添加[InterleaveWhenDeadlock]属性以消除死锁。

6.支持不停服更新

采用组件+状态的设计,状态只有属性,没有方法,组件只用方法,没有属性,并通过代理的方式全部放到热更dll中,运行时重新加载dll即可热更所有逻辑。

7.网络模块

网络模块采用了DotNetty,DotNetty是微软Azure团队,使用C#实现的Netty的版本,性能强劲,功能强大。

8.持久化透明

采用Mongodb作为数据存储,状态的持久化全透明,框架会自动序列化/反序列(并在编译期间动态注入IL代码,高效过滤非变化的状态)让开发人员更加专注于业务逻辑,无需操心数据库。

9.Timer/Scheduler/Event

内置线程安全的Timer,Scheduler,Event系统,轻松应对游戏服务器常见的定时,任务计划,事件触发等业务需求。

10.定期释放不活跃内存数据

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

11.高效的通信协议

通信协议,以扁平数据结构的xbuffer为基础(flatbuffer的简化版),序列化和反序列化效率极高,同时序列化之后的数据极小,数据传输效率很高。(已验证支持ILRunTime热更)

12.一键导表工具

GeekServer包含一个一键导表工具,将策划配置表,转化为二进制数据,并提供了方便快捷的API供游戏调用

运行

  1. 安装.NetCore3.1
  2. 安装mongodb4.x
  3. 打开git clone本项目https://github.com/leeveel/GeekServer.git
  4. 运行Tools/ExcelGen/ExcelConverter.exe 点击[服务器-ALL]导出配置表
  5. 用VisualStudio2019打开GeekServer.sln 启动GeekServer.App
  6. 启动GeekServer.Test (一个1000人登录的demo)

文档&例子&Demo

文档

代码片段

//注册Actor组件
RegistAutoActiveActor(ActorType.Server); //server
RegistComp<ServerComp>(ActorType.Server);  
RegistComp<RoleComp>(ActorType.Role); //role
RegistComp<RoleLoginComp>(ActorType.Role);

//调用Actor组件函数(就像调用普通函数一样,无需关心多线程或入队)
var serverComp = await ActorMgr.GetCompAgent<ServerCompAgent>(ActorType.Server);
_ = serverComp.CheckCrossDay();

//定义状态(数据)
public class RoleState : DBState
{
    public string RoleName { get; set; }
    public long RoleId { get; set; }
    ...
}
//绑定组件
public class RoleComp : StateComponent<RoleState>{}
//绑定组件Agent(Agent类逻辑可全部热更新)
public class RoleCompAgent : StateComponentAgent<RoleComp, RoleState>{}

最佳实践

GeekServer有严格的书写规范检查,如不符合规范编译直接报错
1.CompAgent不能被二次继承,Agent继承的需求理论上很少,如果有请采用组合模式
2.CompAgent中的所有方法必须以Task为返回值(理由:全面异步,防止出现async void导致异常无法捕捉,致使程序崩溃,详见微软AsyncGuidance.md)
3.CompAgent中不能书写构造函数(实际上也没有这样的需求)
4.大部分情况下你都应该使用await等待来书写逻辑,不需要等待的方法请加上[NotAwait]注解,如:通知全服玩家,就没必要等待一个通知完成后再通知下一个。如果逻辑上没有等待,又没加注解可能会存在线程安全问题

public Task NotifyAllClient()
{
   for(int i=0; i<clients.count; i++)
   {
     _ = NotifyOneClient(clients[i].roleId);
   }
}

[NotAwait]
public Task NotifyOneClient(long roleId)
{
   //...
   //...
}

5.CompAgent中不需要提供给外部访问方法,请尽量声明为非public类型,这样会少一次入队判断,效率会更高

public class RoleLoginCompAgent : StateComponentAgent<RoleLoginComp, RoleInfoState>
{
	//agent内部调用,非public
	private Task OnCreate(ReqLogin reqLogin, long roleId)
	{
	    State.CreateTime = DateTime.Now;
	    State.Level = 1;
	    State.VipLevel = 1;
	    State.RoleId = roleId;
	    State.RoleName = new System.Random().Next(1000, 10000).ToString();//随机给一个
	    return Task.CompletedTask;
	}
        //外部接口public
	public async Task OnLogin(ReqLogin reqLogin, bool isNewRole, long roleId)
	{
	    if (isNewRole)
	    {
		await OnCreate(reqLogin, roleId);
	    }
	    State.LoginTime = DateTime.Now;
	}
}

6.为明确知道为线程安全的函数加上[ThreadSafe]注解,同样可以减少入队判断提高效率。(这个是非必须的操作,不加也不会有问题,如果你不知道什么线程安全忽略此项)

public class RoleLoginCompAgent : StateComponentAgent<RoleLoginComp, RoleInfoState>
{
	[ThreadSafe] //我是线程安全的函数
	public Task<ResLogin> BuildLoginMsg()
        {
            var res = new ResLogin()
            {
                code = 0,
                userInfo = new UserInfo()
                {
                    createTime = State.CreateTime.Ticks,
                    level = State.Level,
                    roleId = State.RoleId,
                    roleName = State.RoleName,
                    vipLevel = State.VipLevel
                }
            };
            return Task.FromResult(res);
        }
}

更多异步书写规范请参考微软官方文档AsyncGuidance.md

推荐项目

xbuffer 一种简化版本的 flatbuffer 序列化库
ExcelToCode 一键从Excel中导出模板代码和二进制数据
cshotfix Unity基于ILRunTime的补丁试热更新架构