• 一步一步开发Game服务器(四)地图线程


    时隔这么久 才再一次的回归正题继续讲解游戏服务器开发。

    开始讲解前有一个问题需要修正。之前讲的线程和定时器线程的时候是分开的。

    但是真正地图线程与之前的线程模型是有区别的。

    为什么会有区别呢?一个地图肯定有执行线程,但是每一个地图都有不同的时间任务。
    比如检测玩家身上的buffer,检测玩家的状态值。这种情况下如何处理呢?很明显就需要定时器线程。

     我的处理方式是创建一个线程的时候根据需求创建对应的 timerthread

    直接上代码其他不BB

      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.Text;
      5 using System.Threading;
      6 using System.Threading.Tasks;
      7 
      8 namespace Sz.ThreadPool
      9 {
     10     /// <summary>
     11     /// 线程模型
     12     /// </summary>    
     13     public class ThreadModel
     14     {
     15         /// <summary>
     16         /// 
     17         /// </summary>
     18         public bool IsStop = false;
     19         /// <summary>
     20         /// ID
     21         /// </summary>
     22         public int ID { get; private set; }
     23         /// <summary>
     24         /// 已分配的自定义线程静态ID
     25         /// </summary>
     26         public static int StaticID { get; private set; }
     27 
     28         string Name;
     29 
     30         /// <summary>
     31         /// 初始化线程模型,
     32         /// </summary>
     33         /// <param name="name"></param>
     34         public ThreadModel(String name)
     35             : this(name, 1)
     36         {
     37 
     38         }
     39 
     40         /// <summary>
     41         /// 初始化线程模型
     42         /// </summary>
     43         /// <param name="name">线程名称</param>
     44         /// <param name="count">线程数量</param>
     45         public ThreadModel(String name, Int32 count)
     46         {
     47             lock (typeof(ThreadModel))
     48             {
     49                 StaticID++;
     50                 ID = StaticID;
     51             }
     52             this.Name = name;
     53             if (count == 1)
     54             {
     55                 System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(Run));
     56                 thread.Name = "< " + name + "线程 >";                
     57                 thread.Start();
     58                 Logger.Info("初始化 " + thread.Name);
     59             }
     60             else
     61             {
     62                 for (int i = 0; i < count; i++)
     63                 {
     64                     System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(Run));
     65                     thread.Name = "< " + name + "_" + (i + 1) + "线程 >";                    
     66                     thread.Start();
     67                     Logger.Info("初始化 " + thread.Name);
     68                 }
     69             }
     70         }
     71 
     72         System.Threading.Thread threadTimer = null;
     73 
     74         /// <summary>
     75         /// 任务队列
     76         /// </summary>
     77         protected List<TaskModel> taskQueue = new List<TaskModel>();
     78         /// <summary>
     79         /// 任务队列
     80         /// </summary>
     81         private List<TimerTask> timerTaskQueue = new List<TimerTask>();
     82 
     83         /// <summary>
     84         /// 加入任务
     85         /// </summary>
     86         /// <param name="t"></param>
     87         public virtual void AddTask(TaskModel t)
     88         {
     89             lock (taskQueue)
     90             {
     91                 taskQueue.Add(t);
     92             }
     93             //防止线程正在阻塞时添加进入了新任务
     94             are.Set();
     95         }
     96 
     97         /// <summary>
     98         /// 加入任务
     99         /// </summary>
    100         /// <param name="t"></param>
    101         public void AddTimerTask(TimerTask t)
    102         {
    103             t.RunAttribute["lastactiontime"] = SzExtensions.CurrentTimeMillis();
    104             if (t.IsStartAction)
    105             {
    106                 AddTask(t);
    107             }
    108             lock (timerTaskQueue)
    109             {
    110                 if (threadTimer == null)
    111                 {
    112                     threadTimer = new System.Threading.Thread(new System.Threading.ThreadStart(TimerRun));
    113                     threadTimer.Name = "< " + this.Name + " - Timer线程 >";                    
    114                     threadTimer.Start();
    115                     Logger.Info("初始化 " + threadTimer.Name);
    116                 }
    117                 timerTaskQueue.Add(t);
    118             }
    119             timerAre.Set();
    120         }
    121 
    122         /// <summary>
    123         /// 通知一个或多个正在等待的线程已发生事件
    124         /// </summary>
    125         protected ManualResetEvent are = new ManualResetEvent(false);
    126 
    127         /// <summary>
    128         /// 通知一个或多个正在等待的线程已发生事件
    129         /// </summary>
    130         protected ManualResetEvent timerAre = new ManualResetEvent(true);
    131 
    132         /// <summary>
    133         /// 线程处理器
    134         /// </summary>
    135         protected virtual void Run()
    136         {
    137             while (!this.IsStop)
    138             {
    139                 while ((taskQueue.Count > 0))
    140                 {
    141                     TaskModel task = null;
    142                     lock (taskQueue)
    143                     {
    144                         if (taskQueue.Count > 0)
    145                         {
    146                             task = taskQueue[0];
    147                             taskQueue.RemoveAt(0);
    148                         }
    149                         else { break; }
    150                     }
    151 
    152                     /* 执行任务 */
    153                     //r.setSubmitTimeL();
    154                     long submitTime = SzExtensions.CurrentTimeMillis();
    155                     try
    156                     {
    157                         task.Run();
    158                     }
    159                     catch (Exception e)
    160                     {
    161                         Logger.Error(Thread.CurrentThread.Name + " 执行任务:" + task.ToString() + " 遇到错误", e);
    162                         continue;
    163                     }
    164                     long timeL1 = SzExtensions.CurrentTimeMillis() - submitTime;
    165                     long timeL2 = SzExtensions.CurrentTimeMillis() - task.GetSubmitTime();
    166                     if (timeL1 < 100) { }
    167                     else if (timeL1 <= 200L) { Logger.Debug(Thread.CurrentThread.Name + " 完成了任务:" + task.ToString() + " 执行耗时:" + timeL1 + " 提交耗时:" + timeL2); }
    168                     else if (timeL1 <= 1000L) { Logger.Info(Thread.CurrentThread.Name + " 长时间执行 完成任务:" + task.ToString() + " “考虑”任务脚本逻辑 耗时:" + timeL1 + " 提交耗时:" + timeL2); }
    169                     else if (timeL1 <= 4000L) { Logger.Error(Thread.CurrentThread.Name + " 超长时间执行完成 任务:" + task.ToString() + " “检查”任务脚本逻辑 耗时:" + timeL1 + " 提交耗时:" + timeL2); }
    170                     else
    171                     {
    172                         Logger.Error(Thread.CurrentThread.Name + " 超长时间执行完成 任务:" + task.ToString() + " “考虑是否应该删除”任务脚本 耗时:" + timeL1 + " 提交耗时:" + timeL2);
    173                     }
    174                     task = null;
    175                 }
    176                 are.Reset();
    177                 //队列为空等待200毫秒继续
    178                 are.WaitOne(200);
    179             }
    180             Console.WriteLine(DateTime.Now.NowString() + " " + Thread.CurrentThread.Name + " Destroying");
    181         }
    182 
    183         /// <summary>
    184         /// 定时器线程处理器
    185         /// </summary>
    186         protected virtual void TimerRun()
    187         {
    188             ///无限循环执行函数器
    189             while (!this.IsStop)
    190             {
    191                 if (timerTaskQueue.Count > 0)
    192                 {
    193                     IEnumerable<TimerTask> collections = null;
    194                     lock (timerTaskQueue)
    195                     {
    196                         collections = new List<TimerTask>(timerTaskQueue);
    197                     }
    198                     foreach (TimerTask timerEvent in collections)
    199                     {
    200                         int execCount = timerEvent.RunAttribute.GetintValue("Execcount");
    201                         long lastTime = timerEvent.RunAttribute.GetlongValue("LastExecTime");
    202                         long nowTime = SzExtensions.CurrentTimeMillis();
    203                         if (nowTime > timerEvent.StartTime //是否满足开始时间
    204                                 && (nowTime - timerEvent.GetSubmitTime() > timerEvent.IntervalTime)//提交以后是否满足了间隔时间
    205                                 && (timerEvent.EndTime <= 0 || nowTime < timerEvent.EndTime) //判断结束时间
    206                                 && (nowTime - lastTime >= timerEvent.IntervalTime))//判断上次执行到目前是否满足间隔时间
    207                         {
    208                             //提交执行
    209                             this.AddTask(timerEvent);
    210                             //记录
    211                             execCount++;
    212                             timerEvent.RunAttribute["Execcount"] = execCount;
    213                             timerEvent.RunAttribute["LastExecTime"] = nowTime;
    214                         }
    215                         nowTime = SzExtensions.CurrentTimeMillis();
    216                         //判断删除条件
    217                         if ((timerEvent.EndTime > 0 && nowTime < timerEvent.EndTime)
    218                                 || (timerEvent.ActionCount > 0 && timerEvent.ActionCount <= execCount))
    219                         {
    220                             timerTaskQueue.Remove(timerEvent);
    221                         }
    222                     }
    223                     timerAre.Reset();
    224                     timerAre.WaitOne(5);
    225                 }
    226                 else
    227                 {
    228                     timerAre.Reset();
    229                     //队列为空等待200毫秒继续
    230                     timerAre.WaitOne(200);
    231                 }
    232             }
    233             Console.WriteLine(DateTime.Now.NowString() + "Thread:<" + Thread.CurrentThread.Name + "> Destroying");
    234         }
    235     }
    236 }
    View Code

    当我线程里面第一次添加定时器任务的时候加触发定时器线程的初始化。

    先看看效果

    地图运作方式怎么样的呢?

    来一张图片看看

    在正常情况下一个地图需要这些事情。然后大部分事情是需要定时器任务处理的,只有客户端交互通信是不需要定时器任务处理。

    封装地图信息类

      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.Text;
      5 using System.Threading.Tasks;
      6 using Sz.MMO.GameServer.IMapScripts;
      7 using Sz.MMO.GameServer.TimerMap;
      8 using Sz.MMO.GameServer.TimerMonster;
      9 
     10 
     11 /**
     12  * 
     13  * @author 失足程序员
     14  * @Blog http://www.cnblogs.com/ty408/
     15  * @mail 492794628@qq.com
     16  * @phone 13882122019
     17  * 
     18  */
     19 namespace Sz.MMO.GameServer.Structs.Map
     20 {
     21     /// <summary>
     22     /// 
     23     /// </summary>
     24     public class MapInfo<TPlayer, TNpc, TMonster, TDropGoods> : IEnterMapMonsterScript, IEnterMapNpcScript, IEnterMapPlayerScript, IEnterMapDropGoodsScript
     25     {
     26         /// <summary>
     27         /// 为跨服设计的服务器id
     28         /// </summary>
     29         public int ServerID { get; set; }
     30         /// <summary>
     31         /// 地图模板id
     32         /// </summary>
     33         public int MapModelID { get; set; }
     34         /// <summary>
     35         /// 地图id
     36         /// </summary>
     37         public long MapID { get; set; }
     38 
     39         /// <summary>
     40         /// 地图分线处理
     41         /// </summary>
     42         Dictionary<int, MapLineInfo<TPlayer, TNpc, TMonster, TDropGoods>> mapLineInfos = new Dictionary<int, MapLineInfo<TPlayer, TNpc, TMonster, TDropGoods>>();
     43 
     44         public MapInfo(string name, int mapModelId, int lineCount = 1)
     45         {
     46 
     47             this.MapID = SzExtensions.GetId();
     48             this.MapModelID = mapModelId;
     49             Logger.Debug("开始初始化地图: " + name + " 地图ID:" + MapID);
     50 
     51             for (int i = 1; i <= lineCount; i++)
     52             {
     53                 MapLineInfo<TPlayer, TNpc, TMonster, TDropGoods> lineInfo = new MapLineInfo<TPlayer, TNpc, TMonster, TDropGoods>(name + "-" + i + "线");
     54 
     55                 mapLineInfos[i] = lineInfo;
     56             }
     57             Logger.Debug("初始化地图: " + name + " 地图ID:" + MapID + " 结束");
     58         }
     59 
     60     }
     61 
     62     #region 地图分线 class MapLineInfo<TPlayer, TNpc, TMonster, TDropGoods> : IEnterMapMonsterScript, IEnterMapNpcScript, IEnterMapPlayerScript, IEnterMapDropGoodsScript
     63     /// <summary>
     64     /// 地图分线
     65     /// </summary>
     66     /// <typeparam name="TPlayer"></typeparam>
     67     /// <typeparam name="TNpc"></typeparam>
     68     /// <typeparam name="TMonster"></typeparam>
     69     /// <typeparam name="TDropGoods"></typeparam>
     70     class MapLineInfo<TPlayer, TNpc, TMonster, TDropGoods> : IEnterMapMonsterScript, IEnterMapNpcScript, IEnterMapPlayerScript, IEnterMapDropGoodsScript
     71     {
     72         public MapThread MapServer { get; set; }
     73 
     74         public int ServerID { get; set; }
     75 
     76         public int LineID { get; set; }
     77 
     78         public int MapModelID { get; set; }
     79 
     80         public long MapID { get; set; }
     81 
     82         public MapLineInfo(string name)
     83         {
     84             Players = new List<TPlayer>();
     85             Monsters = new List<TMonster>();
     86             Npcs = new List<TNpc>();
     87             DropGoodss = new List<TDropGoods>();
     88             MapServer = new Structs.Map.MapThread(name);
     89         }
     90 
     91         /// <summary>
     92         /// 地图玩家
     93         /// </summary>
     94         public List<TPlayer> Players { get; set; }
     95 
     96         /// <summary>
     97         /// 地图npc
     98         /// </summary>
     99         public List<TNpc> Npcs { get; set; }
    100 
    101         /// <summary>
    102         /// 地图怪物
    103         /// </summary>
    104         public List<TMonster> Monsters { get; set; }
    105 
    106         /// <summary>
    107         /// 地图掉落物
    108         /// </summary>
    109         public List<TDropGoods> DropGoodss { get; set; }
    110     }
    111     #endregion
    112 }
    View Code
       Structs.Map.MapInfo<Player, Npc, Monster, Drop> map = new Structs.Map.MapInfo<Player, Npc, Monster, Drop>("新手村", 101, 2);



    这样就创建了一张地图。我们创建的新手村有两条线。也就是两个线程

    这样只是创建地图容器和地图线程而已。

    如何添加各个定时器呢?

      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.Text;
      5 using System.Threading.Tasks;
      6 using Sz.MMO.GameServer.IMapScripts;
      7 
      8 
      9 /**
     10  * 
     11  * @author 失足程序员
     12  * @Blog http://www.cnblogs.com/ty408/
     13  * @mail 492794628@qq.com
     14  * @phone 13882122019
     15  * 
     16  */
     17 namespace Sz.MMO.GameServer.TimerMap
     18 {
     19     /// <summary>
     20     /// 
     21     /// </summary>
     22     public class MapHeartTimer : ThreadPool.TimerTask
     23     {
     24 
     25         int serverID, lineID, mapModelID;
     26         long mapID;
     27 
     28         /// <summary>
     29         /// 指定1秒执行一次
     30         /// </summary>
     31         public MapHeartTimer(int serverID, int lineID, long mapID, int mapModelID)
     32             : base(1000 * 10)
     33         {
     34             this.serverID = serverID;
     35             this.lineID = lineID;
     36             this.mapID = mapID;
     37             this.mapModelID = mapModelID;
     38         }
     39 
     40         /// <summary>
     41         /// 
     42         /// </summary>
     43         public override void Run()
     44         {
     45             
     46             Logger.Debug("我是地图心跳检查器 执行线程:" + System.Threading.Thread.CurrentThread.Name);
     47             Logger.Debug("我是地图心跳检查器 检查玩家是否需要复活,回血,状态");
     48             //var scripts = Sz.ScriptPool.ScriptManager.Instance.GetInstances<IMapHeartTimerScript>();
     49             //foreach (var item in scripts)
     50             //{
     51             //    item.Run(serverID, lineID, mapID, mapModelID);
     52             //}
     53         }
     54 
     55     }
     56 }
     57 
     58 
     59 
     60 
     61 
     62 using System;
     63 using System.Collections.Generic;
     64 using System.Linq;
     65 using System.Text;
     66 using System.Threading.Tasks;
     67 using Sz.MMO.GameServer.IMonsterScripts;
     68 
     69 
     70 /**
     71  * 
     72  * @author 失足程序员
     73  * @Blog http://www.cnblogs.com/ty408/
     74  * @mail 492794628@qq.com
     75  * @phone 13882122019
     76  * 
     77  */
     78 namespace Sz.MMO.GameServer.TimerMonster
     79 {
     80     /// <summary>
     81     /// 
     82     /// </summary>
     83     public class MonsterHeartTimer: ThreadPool.TimerTask
     84     {
     85 
     86         int serverID, lineID, mapModelID;
     87         long mapID;
     88 
     89         /// <summary>
     90         /// 指定1秒执行一次
     91         /// </summary>
     92         public MonsterHeartTimer(int serverID, int lineID, long mapID, int mapModelID)
     93             : base(1000 * 10)
     94         {
     95             this.serverID = serverID;
     96             this.lineID = lineID;
     97             this.mapID = mapID;
     98             this.mapModelID = mapModelID;
     99         }
    100 
    101         /// <summary>
    102         /// 
    103         /// </summary>
    104         public override void Run()
    105         {            
    106             Logger.Debug("怪物心跳检查器 执行线程:" + System.Threading.Thread.CurrentThread.Name);
    107             Logger.Debug("怪物心跳检查器 检查怪物是否需要复活,需要回血,是否回跑");
    108             //var scripts = Sz.ScriptPool.ScriptManager.Instance.GetInstances<IMonsterHeartTimerScript>();
    109             //foreach (var item in scripts)
    110             //{
    111             //    item.Run(serverID, lineID, mapID, mapModelID);
    112             //}
    113         }
    114     }
    115 }
    116 
    117 
    118 
    119 using System;
    120 using System.Collections.Generic;
    121 using System.Linq;
    122 using System.Text;
    123 using System.Threading.Tasks;
    124 
    125 
    126 /**
    127  * 
    128  * @author 失足程序员
    129  * @Blog http://www.cnblogs.com/ty408/
    130  * @mail 492794628@qq.com
    131  * @phone 13882122019
    132  * 
    133  */
    134 namespace Sz.MMO.GameServer.TimerMonster
    135 {
    136     /// <summary>
    137     /// 
    138     /// </summary>
    139     public class MonsterRunTimer: ThreadPool.TimerTask
    140     {
    141 
    142         int serverID, lineID, mapModelID;
    143         long mapID;
    144 
    145         /// <summary>
    146         /// 指定1秒执行一次
    147         /// </summary>
    148         public MonsterRunTimer(int serverID, int lineID, long mapID, int mapModelID)
    149             : base(1000 * 5)
    150         {
    151             this.serverID = serverID;
    152             this.lineID = lineID;
    153             this.mapID = mapID;
    154             this.mapModelID = mapModelID;
    155         }
    156 
    157         /// <summary>
    158         /// 
    159         /// </summary>
    160         public override void Run()
    161         {            
    162             Logger.Debug("怪物移动定时器任务 执行线程:" + System.Threading.Thread.CurrentThread.Name);
    163             Logger.Debug("怪物移动定时器任务 怪物随机移动和回跑");
    164             //var scripts = Sz.ScriptPool.ScriptManager.Instance.GetInstances<IMonsterHeartTimerScript>();
    165             //foreach (var item in scripts)
    166             //{
    167             //    item.Run(serverID, lineID, mapID, mapModelID);
    168             //}
    169         }
    170     }
    171 }
    View Code

    就在初始化地图线程的时候加入定时器任务

     1         public MapInfo(string name, int mapModelId, int lineCount = 1)
     2         {
     3 
     4             this.MapID = SzExtensions.GetId();
     5             this.MapModelID = mapModelId;
     6             Logger.Debug("开始初始化地图: " + name + " 地图ID:" + MapID);
     7 
     8             for (int i = 1; i <= lineCount; i++)
     9             {
    10                 MapLineInfo<TPlayer, TNpc, TMonster, TDropGoods> lineInfo = new MapLineInfo<TPlayer, TNpc, TMonster, TDropGoods>(name + "-" + i + "线");
    11                 //添加地图心跳检测器
    12                 lineInfo.MapServer.AddTimerTask(new MapHeartTimer(ServerID, i, MapID, MapModelID));
    13                 //添加怪物移动定时器
    14                 lineInfo.MapServer.AddTimerTask(new MonsterRunTimer(ServerID, i, MapID, MapModelID));
    15                 //添加怪物心跳检测器
    16                 lineInfo.MapServer.AddTimerTask(new MonsterHeartTimer(ServerID, i, MapID, MapModelID));
    17 
    18                 mapLineInfos[i] = lineInfo;
    19             }
    20             Logger.Debug("初始化地图: " + name + " 地图ID:" + MapID + " 结束");
    21         }
    其实所有的任务定时器处理都是交给了timer线程,timer线程只负责查看该定时当前是否需要执行。
    而具体的任务执行移交到线程执行器。线程执行器是按照队列方式执行。保证了timer线程只是一个简单的循环处理而不至于卡死
    同样也保证了在同一张地图里面各个单元参数的线程安全性。

    来看看效果。

    为了方便我们看清楚一点,我把地图线程改为以一条线。

    这样就完成了各个定时器在规定时间内处理自己的事情。

    需要注意的是这里只是简单的模拟的一个地图处理各种事情,最终都是由一个线程处理的。那么肯定有人要问了。
    你一个线程处理这些事情能忙得过来嘛?
    有两点需要注意
    1,你的每一个任务处理处理耗时是多久,换句话说你可以理解为你一秒钟能处理多少个任务。
    2,你的地图能容纳多少怪物,多少玩家,多少掉落物?换句话说也就是你设计的复杂度间接限制了你的地图有多少场景对象。

    那么还有什么需要注意的呢?

    其实地图最大的消耗在于寻路。高性能的寻路算法和人性化寻路算法一直是大神研究的对象,我也只能是借鉴他们的了。

    这一章我只是简单的阐述了地图运行和任务等划分和构成已经任务处理流程。

    接下来我会继续讲解游戏服务器编程,一步一步的剖析。

    文路不是很清晰。希望大家不要见怪。

  • 相关阅读:
    iframe的两种通信方式,iframe的history的优先级
    React-router 将弹框Modal嵌入路由(create a modal route with react-router)
    vue 项目构建 + webpack
    vue 生命周期,v-bind 和 v-on的区别(或 : 和 @的区别),以及父传子、子传父的值传递方式
    linux上配置Sonar代码扫描
    玩转jenkins
    程序小猿的rpa----艺赛旗阶段
    学习完level3加入了uipath家庭,欢迎交流学习。小清风的rpa
    程序员小时光的rpa成长之路(艺赛旗)
    数学期望
  • 原文地址:https://www.cnblogs.com/shizuchengxuyuan/p/4791485.html
Copyright © 2020-2023  润新知