1. 程式人生 > >一步一步開發Game伺服器(四)地圖執行緒

一步一步開發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 [email protected]
 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 [email protected]
 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("我是地圖心跳檢查器 執行執行緒:" + S