一步一步開發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

當我線程裏面第一次添加定時器任務的時候加觸發定時器線程的初始化。服務器

先看看效果ide

 

地圖運做方式怎麼樣的呢?

來一張圖片看看函數

在正常狀況下一個地圖須要這些事情。而後大部分事情是須要定時器任務處理的,只有客戶端交互通訊是不須要定時器任務處理。性能

封裝地圖信息類

  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);

 



這樣就建立了一張地圖。咱們建立的新手村有兩條線。也就是兩個線程this

這樣只是建立地圖容器和地圖線程而已。spa

如何添加各個定時器呢?

  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,你的地圖能容納多少怪物,多少玩家,多少掉落物?換句話說也就是你設計的複雜度間接限制了你的地圖有多少場景對象。

那麼還有什麼須要注意的呢?

其實地圖最大的消耗在於尋路。高性能的尋路算法和人性化尋路算法一直是大神研究的對象,我也只能是借鑑他們的了。

這一章我只是簡單的闡述了地圖運行和任務等劃分和構成已經任務處理流程。

接下來我會繼續講解遊戲服務器編程,一步一步的剖析。

文路不是很清晰。但願你們不要見怪。

相關文章
相關標籤/搜索