時隔這麼久 才再一次的回歸正題繼續講解游戲服務器開發。
開始講解前有一個問題需要修正。之前講的線程和定時器線程的時候是分開的。
為什麼會有區別呢?一個地圖肯定有執行線程,但是每一個地圖都有不同的時間任務。
比如檢測玩家身上的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當我線程裡面第一次添加定時器任務的時候加觸發定時器線程的初始化。
先看看效果
來一張圖片看看
在正常情況下一個地圖需要這些事情。然後大部分事情是需要定時器任務處理的,只有客戶端交互通信是不需要定時器任務處理。
Structs.Map.MapInfo<Player, Npc, Monster, Drop> map = new Structs.Map.MapInfo<Player, Npc, Monster, Drop>("新手村", 101, 2);
這樣就創建了一張地圖。我們創建的新手村有兩條線。也就是兩個線程
這樣只是創建地圖容器和地圖線程而已。
就在初始化地圖線程的時候加入定時器任務
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,你的地圖能容納多少怪物,多少玩家,多少掉落物?換句話說也就是你設計的復雜度間接限制了你的地圖有多少場景對象。
其實地圖最大的消耗在於尋路。高性能的尋路算法和人性化尋路算法一直是大神研究的對象,我也只能是借鑒他們的了。
這一章我只是簡單的闡述了地圖運行和任務等劃分和構成已經任務處理流程。
接下來我會繼續講解游戲服務器編程,一步一步的剖析。
文路不是很清晰。希望大家不要見怪。