快速了解.Net Core GeekServer

这篇具有很好参考价值的文章主要介绍了快速了解.Net Core GeekServer。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。



前言

作者的介绍:GeekServer是一个开源的分区分服的游戏服务器框架,采用C# .Netcore开发,开发效率高,性能强,跨平台,并内置不停服热更新机制。可以满足绝大部分游戏类型的需求,特别是和Unity3D协同开发更佳。
设计理念:大道至简,以简化繁
使用这个框架可以帮助你节约很多时间,框架帮助你处理了数据的发送,和接收。开发者只需要专注于业务的开发。作者的代码写的非常的精致,非常扎实,值得去阅读,学习。
我写这篇文章希望可以帮助想了解的人快速了解,节约时间。
我上传的资源包地址可以打开尝试一下。


一、快速上手GeekServer

1.快速使用

  1. 在proto项目中的proto文件夹中添加一个类。作者有例子,按照作者的样子就可以了。下面是我给出的例子—同步游戏中的物体。
    //记录玩家坐标位置
    [MessagePackObject(true)]
    public class Place
    {
    	public double X { get; set; }
    	public double Y { get; set; }
    	public long UserID { get; set; }
    }
    
    [MessagePackObject(true)]
    public class MoveMessage : Message
    {
        public double X { get; set; }
        public double Y { get; set; }
        public long UserID { get; set; }
        public List<Place> places{ get; set; }
    }
    
  2. 使用这个目录下面的工具项目目录\Tools\Geek.MsgPackTool双击 Geek.MsgPackTool.exe ,然后输入3。准备工作已经做完接下来就可以书写业务逻辑了。
  3. 接下来就可以写逻辑了,在hotfix中,按照作者的方式去创建LoginCompAgent,ReqLoginHandler 两个。下面是我给出的例子。每个类创建一个文件,是一个好习惯



 [MsgMapping(typeof(MoveMessage))]
	internal class MoveHandler : GlobalCompHandler<MoveCompAgent>
	{
		public override async Task ActionAsync()
		{
			await Comp.OnMove(Channel, Msg as MoveMessage);
		}
	}




	public class MoveCompAgent: StateCompAgent<MoveComp, MoveState>
	{
		private static readonly Logger Log = LogManager.GetCurrentClassLogger();

		public async Task OnMove(Channel channel,MoveMessage moveInfo)
		{
			try
			{
				Session session = SessionManager.GetSession(channel.GetSessionId());
				session.X += moveInfo.X;
				session.Y += moveInfo.Y;

				Console.WriteLine(session.Id);
				Console.WriteLine($"水平{session.X},垂直{session.Y}");

			}
			catch (Exception ex)
			{
				Log.Error(ex.Message);
			}

		}
	}
  1. 在session中的修改
//加上这两个属性
    public double X { get; set; }
    public double Y { get; set; }
  1. 在SessionManager添加入下方法
//同步其他玩家的位置信息
  public static void SendMove()
       {
   		MoveMessage moveMessage = new MoveMessage();
   		moveMessage.places = new List<Place>();
   		Task.Run(() =>
           {
   			foreach (var item in sessionMap)
   			{
                   Place place = new Place();
   				//将自己的位置广播出去
   				place.X = item.Value.X;
   				place.Y = item.Value.Y;
   				place.UserID = item.Value.Id;
   				moveMessage.places.Add(place);
   			}
               foreach (var item in sessionMap)
               {
   			    item.Value.WriteAsync(moveMessage);
   			}
               moveMessage.places.Clear();
   		});
   	}
  1. 在主循环中去同步。服务端部分就完成了,接下来就是客户端。
				///大约一秒钟同步六十次
               TimeSpan delay = TimeSpan.FromMilliseconds(1000/60);
               while (Settings.AppRunning)
               {
                   SessionManager.SendMove();
                   await Task.Delay(delay);
               }
  1. 找到下图中的类
    .net core游戏服务器,.netcore,unity
//添加字段
private static Dictionary<long, Tuple<GameObject, Place,bool>> shpereCollection = new Dictionary<long, Tuple<GameObject, Place,bool>>();
public static ResLogin userLogin;//用于保存登录信息
//后面测试方便,每个登录用户名称要保持不一致,
public string userName = Guid.NewGuid().ToString();
  1. 添加updateMove方法在GameMain中
//将start 方法修改
async void Start()
        {
            Txt = GameObject.Find("Text").GetComponent<Text>();
            GameDataManager.ReloadAll();
            GameClient.Singleton.Init();
            

			DemoService.Singleton.RegisterEventListener();
            await ConnectServer();
            await Login();
            if (userLogin!=null)
            {
				var p = Resources.Load("Sphere") as GameObject;
			    player = Instantiate(p);
				player.name = "SphereItem1";
				//注册监听到服务端发送的消息的响应的处理方法。
				DemoService.Singleton.AddListener(MoveMessage.MsgID, UpdateMove);
			}
            await ReqBagInfo();
            await ReqComposePet();
        }
//添加方法
public void UpdateMove(ClientEnen e)
		{
			MoveMessage moves = DemoService.Singleton.GetCurMsg<MoveMessage>(e.EventId);
			if (moves != null)
			{
				foreach (var item in moves.places)
				{
                    if (item.UserID ==userLogin.UserInfo.RoleId)
                    {
						continue;
					}

                    if (shpereCollection.ContainsKey(item.UserID))
					{
                        //更新

                         shpereCollection[item.UserID].Item2.X= item.X;
                         shpereCollection[item.UserID].Item2.Y= item.Y;
                         shpereCollection[item.UserID].Item2.UserID= item.UserID;
   
					}
					else
					{
                        //创建
                    
                        var playitem = GameObject.Instantiate(player);
						playitem.name = $"SphereItem{shpereCollection.Count}";
						Destroy(playitem.GetComponent<ShpereItem>());

						shpereCollection.Add(item.UserID, Tuple.Create(playitem, item, true));
						UnityEngine.Debug.Log($"move 创建");
					}
					UnityEngine.Debug.Log($"用户id:{item.UserID}\t水平方向:{item.X}\t垂直方向:{item.Y}");
				}
				DemoService.Singleton.AddListener(MoveMessage.MsgID,UpdateMove);
			}

		}
//Update 方法
public void Update()
		{
            foreach (var item in shpereCollection)
            {
                var position = item.Value.Item1.transform.position;
                var place = item.Value.Item2;
                double sX = Math.Round(place.X, 4);
                double sY = Math.Round(place.Y, 4);
                double lX = Math.Round(position.x, 4);
                double lY = Math.Round(position.z, 4);

                if (sX != lX || sY != lY)
                {
                    var TargetPos= new Vector3((float)(sX), (float)1, (float)(sY));
					item.Value.Item1.transform.position = UnityEngine.Vector3.Lerp(item.Value.Item1.transform.position, TargetPos, 0.5f);
				}
            }
		}
  1. 物体的脚本,我这里写的很简单
public class ShpereItem : MonoBehaviour
{
   // Start is called before the first frame update
   void Start()
   {
   	moveMessage =new MoveMessage();
       moveMessage.X = 0;
       moveMessage.Y = 0;
   	moveSpeed = 5;
   	IsMove = false;
   	relationMovemessage = new MoveMessage();
   }



   // Update is called once per frame

   private MoveMessage moveMessage;

   private MoveMessage relationMovemessage;
   public float moveSpeed;
   private bool IsMove;
   // function:控制移动,上(w)下(s)左(a)右(d)前(q)后(e)
   void Control_movement()
   {
   	relationMovemessage.X = 0;
   	relationMovemessage.Y=0;
   	float moveLength = Time.deltaTime;
   	if (Input.GetKey(KeyCode.A))
   	{
   		moveLength *= moveSpeed;
   		transform.Translate(Vector3.left*moveLength);
   		IsMove= true;
   		moveMessage.X += moveLength;
   		relationMovemessage.X += moveLength;
   	}
   	if (Input.GetKey(KeyCode.D))
   	{
   		moveLength *= moveSpeed;
   		transform.Translate(Vector3.right * moveLength);
   		IsMove = true;
   		moveMessage.X -= moveLength;
   		relationMovemessage.X -= moveLength;
   	}
   	if (Input.GetKey(KeyCode.W))
   	{
   		moveLength *= moveSpeed;
   		transform.Translate(Vector3.forward * moveLength);
   		IsMove = true;
   		moveMessage.Y += moveLength;
   		relationMovemessage.Y+= moveLength;
   	}
   	if (Input.GetKey(KeyCode.S))
   	{
   		moveLength *= moveSpeed;
   		transform.Translate(Vector3.back * moveLength);
   		IsMove = true;
   		moveMessage.Y -=moveLength;
   		relationMovemessage.Y -=moveLength;
   	}
   	if (IsMove)
   	{
   		UnityEngine.Debug.Log(moveLength);
   		DemoService.Singleton.SendMsg(relationMovemessage);
   		IsMove= false;
   	}
   }
   void Update()
   {
   	Control_movement();
   }

}

2.快速了解项目

  1. 项目地址:https://github.com/leeveel/GeekServer

  2. 看了作者的介绍,其实就可以简单测试启动项目了。
    .net core游戏服务器,.netcore,unity

  3. 客户端是一个unity项目,可以直接通过unity打开就好了。

  4. 接下来很多小伙伴就会关心我可不可以直接定义一个类,然后发送给客户端呢!我也是这么想的。
    也是我便打开服务端项目,以下便是我看到的项目结构

    .net core游戏服务器,.netcore,unity

  5. Geek.Server.App 是一个控制程序,是整个程序的入口。完成一些简单的初始化工作,然后比较重要的就是Session,里面可以管理所有链 接上了的用户。

  6. Geek.Server.CodeGenerator 这个可以暂时不用管它。

  7. Geek.Server.Core从名称可以看出来是geekServer的核心部分,包含非关系型数据,网络,消息,序列化等。

  8. Geek.Server.Generate,打开一看,貌似不像是手写的。其实这个proto 目录下面的类是作者写的这个工具(Geek.MsgPackTool)生成的。所以我们也可以先不用管它。
    .net core游戏服务器,.netcore,unity

  9. Geek.Server.Hotfix:这个项目是非常重要的,这里面就是放有关游戏逻辑的代码。每次修改这个项目都需要关闭服务,重新生成hotfix,然后再去重新启动服务。
    .net core游戏服务器,.netcore,unity

二、阅读源码

1.程序主体执行流程

作者写的很多方法通过名称可以很直观的看出意思,是的阅读变得更叫容易。下面这部分不包含作者有关数据库部分的解读。


AppExitHandler.Init(HandleExit);//主程序结束之前释放相关资源。

AppStartUp.Enter();//基础的初始化完成后调用该方法。可以理解为入口。

HotfixMgr.LoadHotfixModule();

HotfixMgr.Load();

module.HotfixBridge.OnLoadSuccess();

await TcpServer.Start(Settings.TcpPort, builder => builder.UseConnectionHandler<AppTcpConnectionHandler>());//启动监听服务器(kestrel)

//AppTcpConnectionHandler 继承了TcpConnectionHandler 。TcpConnectionHandler  继承ConnectionHandler。

TcpConnectionHandler.OnConnectedAsync(ConnectionContext connection);//客户端连接服务端之后调用该方法。

TcpConnectionHandler.Dispatcher();

var handler = HotfixMgr.GetTcpHandler(msg.MsgId);
//根据编号创建相应的处理对象 (作者定义的登录对象 ReqLoginHandler,相应的时间编号是一个int值,客户端和服务端统一由Geek.MsgPackTool工具生成)

await handler.Init();//处理对象调用这两个方法完成相应的初始话。

await handler.InnerAction();//进入游戏逻辑处理的的入口。开发者无需过多关心这些实现细节,这里只是了解一下,作者很多实现还是很复杂的,阅读呢更多只是梳理流程,方便使用和扩展和学习,对整体的把控更加到位。

///BaseTcpHandler 中有InnerAction()方法.以登录的处理对象ReqLoginHandler为例子:ReqLoginHandler继承了GlobalCompHandler<T>,GlobalCompHandler<T>继承 GlobalCompHandler,GlobalCompHandler继承了BaseCompHandler ,BaseCompHandler继承了BaseTcpHandler。在await handler.InnerAction();中调用了ActionAsync();

Comp.OnMove(Channel, Msg as MoveMessage);//到了这里就正式进入业务逻辑部分了



2.Tcp模块

  1. 找到这个类,我们可以看的非常清楚,他是用AspNetCore 服务器
  2. 在链接处理部分做了自定义的扩展接下来就找到TcpConnectionHandler就好了。

.net core游戏服务器,.netcore,unity

  1. 打开之后可以看到主要是重写了OnConnectedAsync 这个方法。发现作者主要使用的是NetChannel接收客户端的消息。
public override async Task OnConnectedAsync(ConnectionContext connection)
       {
           OnConnection(connection);
           var channel = new NetChannel(connection, new LengthPrefixedProtocol());
           var remoteInfo = channel.Context.RemoteEndPoint;
           while (!channel.IsClose())
           {
               try
               {
                  //读取客户端发送来的消息
                   var result = await channel.Reader.ReadAsync(channel.Protocol);
                   var message = result.Message;

                   if (result.IsCompleted)
                       break;
   					  //解码消息
                   _ = Dispatcher(channel, MsgDecoder.Decode(connection, message));
               }
               catch (ConnectionResetException)
               {
                   LOGGER.Info($"{remoteInfo} ConnectionReset...");
                   break;
               }
               catch (ConnectionAbortedException)
               {
                   LOGGER.Info($"{remoteInfo} ConnectionAborted...");
                   break;
               }
               catch (Exception e)
               {
                   LOGGER.Error($"{remoteInfo} Exception:{e.Message}");
               }

               try
               {
                   channel.Reader.Advance();
               }
               catch (Exception e)
               {
                   LOGGER.Error($"{remoteInfo} channel.Reader.Advance Exception:{e.Message}");
                   break;
               }
           }
           OnDisconnection(channel);
       }
  1. 打开去读的方法 ,最后可以找到这里
 public ValueTask<ProtocolReadResult<TReadMessage>> ReadAsync<TReadMessage>(IMessageReader<TReadMessage> reader, int? maximumMessageSize, CancellationToken cancellationToken = default)
       {
           if (_disposed)
           {
               throw new ObjectDisposedException(GetType().Name);
           }

           if (_hasMessage)
           {
               throw new InvalidOperationException($"{nameof(Advance)} must be called before calling {nameof(ReadAsync)}");
           }

           // If this is the very first read, then make it go async since we have no data
           if (_consumed.GetObject() == null)
           {
               return DoAsyncRead(maximumMessageSize, reader, cancellationToken);
           }

           // We have a buffer, test to see if there's any message left in the buffer
           if (TryParseMessage(maximumMessageSize, reader, _buffer, out var protocolMessage))
           {
               _hasMessage = true;
               return new ValueTask<ProtocolReadResult<TReadMessage>>(new ProtocolReadResult<TReadMessage>(protocolMessage, _isCanceled, isCompleted: false));
           }
           else
           {
               // We couldn't parse the message so advance the input so we can read
               _reader.AdvanceTo(_consumed, _examined);

               // Reset the state since we're done consuming this buffer
               _buffer = default;
               _consumed = default;
               _examined = default;
           }

           if (_isCompleted)
           {
               _consumed = default;
               _examined = default;

               // If we're complete then short-circuit
               if (!_buffer.IsEmpty)
               {
                   throw new InvalidDataException("Connection terminated while reading a message.");
               }

               return new ValueTask<ProtocolReadResult<TReadMessage>>(new ProtocolReadResult<TReadMessage>(default, _isCanceled, _isCompleted));
           }

           return DoAsyncRead(maximumMessageSize, reader, cancellationToken);
       }
//找到最后发现 了LengthPrefixedProtocol 和 ClientLengthPrefixedProtocol
//LengthPrefixedProtocol服务端的实现。把需要发送的数据做成一个数据包,接收的时候吧数据长的部分去掉然后装载
//  NMessage中

//ClientLengthPrefixedProtocol客户端部分的实现 在数据包中还加上了时间和消息包的编号

 public bool TryParseMessage(in ReadOnlySequence<byte> input, ref SequencePosition consumed, ref SequencePosition examined, out NMessage message)
       {
           var reader = new SequenceReader<byte>(input);
           //客户端传过来的length包含了长度自身(data: [length:byte[1,2,3,4]] ==> 则length=int 4 个字节+byte数组长度4=8)
           if (!reader.TryReadBigEndian(out int length) || reader.Remaining < length - 4)
           {
               message = default;
               return false;
           }

           var payload = input.Slice(reader.Position, length - 4);//length已经被TryReadBigEndian读取
           message = new NMessage(payload);

           consumed = payload.End;
           examined = consumed;
           return true;
       }

       public void WriteMessage(NMessage nmsg, IBufferWriter<byte> output)
       {
           var bytes = nmsg.Serialize();
           int len = 8 + bytes.Length;
           var span = output.GetSpan(len);
           int offset = 0;
           XBuffer.WriteInt(len, span, ref offset);
           XBuffer.WriteInt(nmsg.Msg.MsgId, span, ref offset);
           XBuffer.WriteBytesWithoutLength(bytes, span, ref offset);
           output.Advance(len);
       }

三、扩展

1.扩展webSocket

期待后续更新吧…

总结

作者是在用心写项目,从中可以学习到很多东西。有兴趣的小伙伴也可以做一些扩展!文章来源地址https://www.toymoban.com/news/detail-753687.html

到了这里,关于快速了解.Net Core GeekServer的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • linux 服务器利用宝塔面板部署.net 6(.net core)服务端程序图文步骤

    随着.net core 跨平台技术的兴起,微软.net拥抱云原生,支持跨平台,可以使基于.net core技术的服务端程序轻松移植到基于Linux的云服务器上,本文以图文的方式介绍如何利用阿里云轻量应用服务器安装宝塔面板部署基于.net core的后端服务器接口程序并正常运行。 步骤 准备一台

    2024年02月14日
    浏览(56)
  • 将 .net core 项目部署到 Linux服务器

    目录 1、前言 2、安装 .NET SDK 或 .NET 运行时 3、创建数据库 4、发布项目到Linux环境 5、配置Nigix 6、Nginx配置Https 7、安装Supervisor 1、前言 部署服务器:阿里云的CentOS 7.6 64位 发布项目:.Net Core 5.0 数据库:MySql 5.8  其他:Nginx,Supervisor 2、安装 .NET SDK 或 .NET 运行时 参考官网网址

    2024年02月04日
    浏览(55)
  • 全面的ASP.NET Core Blazor简介和快速入门

      因为咱们的MongoDB入门到实战教程Web端准备使用Blazor来作为前端展示UI,本篇文章主要是介绍Blazor是一个怎样的Web UI框架,其优势和特点在哪?并带你快速入门上手ASP.NET Core Blazor(当然这个前提是你要有一定的C#编程基础的情况,假如你完全没有接触过C#的话建议你先从基本语

    2024年02月19日
    浏览(40)
  • 从浅入深了解.NET Core MVC 2.x全面教程【第二章】

    章节 第一章:https://www.cnblogs.com/kimiliucn/p/17611047.html 第二章:https://www.cnblogs.com/kimiliucn/p/17616708.html 1.诊断中间件 命名空间:Microsoft.AspNetCore.Diagnostics 报告信息并处理异常 2.诊断中间件 UseDeveloperExceptionPage:开发时使用的异常页面,异常信息 UseStatusCodePages:返回状态码:400-

    2024年02月13日
    浏览(52)
  • 【ASP.NET Core】使用SignalR推送服务器日志

    一个多月前接手了一个产线机器人项目,上位机以读写寄存器的方式控制机器人,服务器就是用 ASP.NET Core 写的 Web API。由于前一位开发者写的代码质量问题,导致上位机需要16秒才能启动。经过我近一个月的改造,除了保留业务逻辑代码,其他的基本重写。如今上位机的启动

    2024年02月03日
    浏览(58)
  • 如何部署ASP.NET Core到Linux服务器

    我们开发的最终目的,是将开发后的东西发布网络上,以便自己及其他人使用。 本篇博客介绍如果在 linux 上部署 ASP.NET Core应用,使用nginx+systemd 来管理我们的应用。 Ubuntu 20.04 Nginx .NET SDK 或 Runtime 安装Nginx 安装.NET 配置文件 nginx默认配置会加载 /etc/nginx/sites-enabled 中的网站,

    2024年02月11日
    浏览(43)
  • 【一文详解】知识分享:(ASP.Net Core基础学习及快速入门)

    .Net .NET是微软的一个开发平台,这个平台的一大特点就是跨语言性,不管是什么语言,c、c++、c#、F#、J#、vb等语言都可以用这个平台合作开发; .NET,它是微软创建的一个用于构建多种不同类型的应用程序的开发人员平台。 .NET 是一个广泛的术语,用于描述整个 Microsoft 的软件

    2024年02月01日
    浏览(65)
  • 【服务器】ASP.Net Core(C#)创建Web站点

    简单几步实现本地ASP.Net.Core web 站点结合cpolar内网穿透工具实现远程访问 1. 创建站点 *环境搭建,这边测试,使用.NET 6.0 SDK,可以点击跳转到官网下载,下载后安装即可. 安装完成后,进入到某个文件夹,打开powershell执行下面命令,创建新的 Web 应用,名称叫:aspnetcoreapp 2. 运行站点 信任开

    2024年02月11日
    浏览(47)
  • asp.net core 项目从本地部署到远程云服务器

    今天向大家详细介绍一下,如何将一个.net core项目部署到远程云服务器,我的服务器镜像为 window server 2019. 1.安装IIS 首先在服务器上安装IIS(我的是最新的iis10),我已经安装完毕,这个安装操作比较简单,自行谷歌。 安装完成后会看到下面的界面: 2.安装web deploy程序 该程序的功能: 它

    2023年04月22日
    浏览(48)
  • .NET Core使用SkiaSharp快速生成二维码( 真正跨平台方案)

    在.NET 6之前我们一直是使用QRCoder来生成二维码(QRCoder是一个非常强大的生成二维码的组件,用到了 System.Drawing.Common  包),然后从.NET 6开始,当为非 Windows 操作系统编译引用代码时,平台分析器会发出编译时警告。异常如下: 由于 System.Drawing.Common 被设计为 Windows 技术的精

    2024年01月20日
    浏览(59)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包