时隔这么久 才再一次的回归正题继续讲解游戏服务器开发。
开始讲解前有一个问题需要修正。之前讲的线程和定时器线程的时候是分开的。
但是真正地图线程与之前的线程模型是有区别的。
为什么会有区别呢?一个地图肯定有执行线程,但是每一个地图都有不同的时间任务。比如检测玩家身上的buffer,检测玩家的状态值。这种情况下如何处理呢?很明显就需要定时器线程。
我的处理方式是创建一个线程的时候根据需求创建对应的 timerthread
直接上代码其他不BB
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Sz.ThreadPool { /// <summary> /// 线程模型 /// </summary> public class ThreadModel { /// <summary> /// /// </summary> public bool IsStop = false; /// <summary> /// ID /// </summary> public int ID { get; private set; } /// <summary> /// 已分配的自定义线程静态ID /// </summary> public static int StaticID { get; private set; } string Name; /// <summary> /// 初始化线程模型, /// </summary> /// <param name="name"></param> public ThreadModel(String name) : ) { } /// <summary> /// 初始化线程模型 /// </summary> /// <param name="name">线程名称</param> /// <param name="count">线程数量</param> public ThreadModel(String name, Int32 count) { lock (typeof(ThreadModel)) { StaticID++; ID = StaticID; } this.Name = name; ) { System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(Run)); thread.Name = "< " + name + "线程 >"; thread.Start(); Logger.Info("初始化 " + thread.Name); } else { ; i < count; i++) { System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(Run)); thread.Name = ) + "线程 >"; thread.Start(); Logger.Info("初始化 " + thread.Name); } } } System.Threading.Thread threadTimer = null; /// <summary> /// 任务队列 /// </summary> protected List<TaskModel> taskQueue = new List<TaskModel>(); /// <summary> /// 任务队列 /// </summary> private List<TimerTask> timerTaskQueue = new List<TimerTask>(); /// <summary> /// 加入任务 /// </summary> /// <param name="t"></param> public virtual void AddTask(TaskModel t) { lock (taskQueue) { taskQueue.Add(t); } //防止线程正在阻塞时添加进入了新任务 are.Set(); } /// <summary> /// 加入任务 /// </summary> /// <param name="t"></param> public void AddTimerTask(TimerTask t) { t.RunAttribute["lastactiontime"] = SzExtensions.CurrentTimeMillis(); if (t.IsStartAction) { AddTask(t); } lock (timerTaskQueue) { if (threadTimer == null) { threadTimer = new System.Threading.Thread(new System.Threading.ThreadStart(TimerRun)); threadTimer.Name = "< " + this.Name + " - Timer线程 >"; threadTimer.Start(); Logger.Info("初始化 " + threadTimer.Name); } timerTaskQueue.Add(t); } timerAre.Set(); } /// <summary> /// 通知一个或多个正在等待的线程已发生事件 /// </summary> protected ManualResetEvent are = new ManualResetEvent(false); /// <summary> /// 通知一个或多个正在等待的线程已发生事件 /// </summary> protected ManualResetEvent timerAre = new ManualResetEvent(true); /// <summary> /// 线程处理器 /// </summary> protected virtual void Run() { while (!this.IsStop) { )) { TaskModel task = null; lock (taskQueue) { ) { task = taskQueue[]; taskQueue.RemoveAt(); } else { break; } } /* 执行任务 */ //r.setSubmitTimeL(); long submitTime = SzExtensions.CurrentTimeMillis(); try { task.Run(); } catch (Exception e) { Logger.Error(Thread.CurrentThread.Name + " 执行任务:" + task.ToString() + " 遇到错误", e); continue; } long timeL1 = SzExtensions.CurrentTimeMillis() - submitTime; long timeL2 = SzExtensions.CurrentTimeMillis() - task.GetSubmitTime(); ) { } else if (timeL1 <= 200L) { Logger.Debug(Thread.CurrentThread.Name + " 完成了任务:" + task.ToString() + " 执行耗时:" + timeL1 + " 提交耗时:" + timeL2); } else if (timeL1 <= 1000L) { Logger.Info(Thread.CurrentThread.Name + " 长时间执行 完成任务:" + task.ToString() + " “考虑”任务脚本逻辑 耗时:" + timeL1 + " 提交耗时:" + timeL2); } else if (timeL1 <= 4000L) { Logger.Error(Thread.CurrentThread.Name + " 超长时间执行完成 任务:" + task.ToString() + " “检查”任务脚本逻辑 耗时:" + timeL1 + " 提交耗时:" + timeL2); } else { Logger.Error(Thread.CurrentThread.Name + " 超长时间执行完成 任务:" + task.ToString() + " “考虑是否应该删除”任务脚本 耗时:" + timeL1 + " 提交耗时:" + timeL2); } task = null; } are.Reset(); //队列为空等待200毫秒继续 are.WaitOne(); } Console.WriteLine(DateTime.Now.NowString() + " " + Thread.CurrentThread.Name + " Destroying"); } /// <summary> /// 定时器线程处理器 /// </summary> protected virtual void TimerRun() { ///无限循环执行函数器 while (!this.IsStop) { ) { IEnumerable<TimerTask> collections = null; lock (timerTaskQueue) { collections = new List<TimerTask>(timerTaskQueue); } foreach (TimerTask timerEvent in collections) { int execCount = timerEvent.RunAttribute.GetintValue("Execcount"); long lastTime = timerEvent.RunAttribute.GetlongValue("LastExecTime"); long nowTime = SzExtensions.CurrentTimeMillis(); if (nowTime > timerEvent.StartTime //是否满足开始时间 && (nowTime - timerEvent.GetSubmitTime() > timerEvent.IntervalTime)//提交以后是否满足了间隔时间 && (timerEvent.EndTime <= || nowTime < timerEvent.EndTime) //判断结束时间 && (nowTime - lastTime >= timerEvent.IntervalTime))//判断上次执行到目前是否满足间隔时间 { //提交执行 this.AddTask(timerEvent); //记录 execCount++; timerEvent.RunAttribute["Execcount"] = execCount; timerEvent.RunAttribute["LastExecTime"] = nowTime; } nowTime = SzExtensions.CurrentTimeMillis(); //判断删除条件 && nowTime < timerEvent.EndTime) || (timerEvent.ActionCount > && timerEvent.ActionCount <= execCount)) { timerTaskQueue.Remove(timerEvent); } } timerAre.Reset(); timerAre.WaitOne(); } else { timerAre.Reset(); //队列为空等待200毫秒继续 timerAre.WaitOne(); } } Console.WriteLine(DateTime.Now.NowString() + "Thread:<" + Thread.CurrentThread.Name + "> Destroying"); } } }
当我线程里面第一次添加定时器任务的时候加触发定时器线程的初始化。
先看看效果
地图运作方式怎么样的呢?
来一张图片看看
在正常情况下一个地图需要这些事情。然后大部分事情是需要定时器任务处理的,只有客户端交互通信是不需要定时器任务处理。
封装地图信息类
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Sz.MMO.GameServer.IMapScripts; using Sz.MMO.GameServer.TimerMap; using Sz.MMO.GameServer.TimerMonster; /** * * @author 失足程序员 * @Blog http://www.cnblogs.com/ty408/ * @mail 492794628@qq.com * @phone 13882122019 * */ namespace Sz.MMO.GameServer.Structs.Map { /// <summary> /// /// </summary> public class MapInfo<TPlayer, TNpc, TMonster, TDropGoods> : IEnterMapMonsterScript, IEnterMapNpcScript, IEnterMapPlayerScript, IEnterMapDropGoodsScript { /// <summary> /// 为跨服设计的服务器id /// </summary> public int ServerID { get; set; } /// <summary> /// 地图模板id /// </summary> public int MapModelID { get; set; } /// <summary> /// 地图id /// </summary> public long MapID { get; set; } /// <summary> /// 地图分线处理 /// </summary> Dictionary<int, MapLineInfo<TPlayer, TNpc, TMonster, TDropGoods>> mapLineInfos = new Dictionary<int, MapLineInfo<TPlayer, TNpc, TMonster, TDropGoods>>(); ) { this.MapID = SzExtensions.GetId(); this.MapModelID = mapModelId; Logger.Debug("开始初始化地图: " + name + " 地图ID:" + MapID); ; i <= lineCount; i++) { MapLineInfo<TPlayer, TNpc, TMonster, TDropGoods> lineInfo = new MapLineInfo<TPlayer, TNpc, TMonster, TDropGoods>(name + "-" + i + "线"); mapLineInfos[i] = lineInfo; } Logger.Debug("初始化地图: " + name + " 地图ID:" + MapID + " 结束"); } } #region 地图分线 class MapLineInfo<TPlayer, TNpc, TMonster, TDropGoods> : IEnterMapMonsterScript, IEnterMapNpcScript, IEnterMapPlayerScript, IEnterMapDropGoodsScript /// <summary> /// 地图分线 /// </summary> /// <typeparam name="TPlayer"></typeparam> /// <typeparam name="TNpc"></typeparam> /// <typeparam name="TMonster"></typeparam> /// <typeparam name="TDropGoods"></typeparam> class MapLineInfo<TPlayer, TNpc, TMonster, TDropGoods> : IEnterMapMonsterScript, IEnterMapNpcScript, IEnterMapPlayerScript, IEnterMapDropGoodsScript { public MapThread MapServer { get; set; } public int ServerID { get; set; } public int LineID { get; set; } public int MapModelID { get; set; } public long MapID { get; set; } public MapLineInfo(string name) { Players = new List<TPlayer>(); Monsters = new List<TMonster>(); Npcs = new List<TNpc>(); DropGoodss = new List<TDropGoods>(); MapServer = new Structs.Map.MapThread(name); } /// <summary> /// 地图玩家 /// </summary> public List<TPlayer> Players { get; set; } /// <summary> /// 地图npc /// </summary> public List<TNpc> Npcs { get; set; } /// <summary> /// 地图怪物 /// </summary> public List<TMonster> Monsters { get; set; } /// <summary> /// 地图掉落物 /// </summary> public List<TDropGoods> DropGoodss { get; set; } } #endregion }
Structs.Map.MapInfo<Player, Npc, Monster, Drop> map = , );
这样就创建了一张地图。我们创建的新手村有两条线。也就是两个线程
这样只是创建地图容器和地图线程而已。
如何添加各个定时器呢?
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Sz.MMO.GameServer.IMapScripts; /** * * @author 失足程序员 * @Blog http://www.cnblogs.com/ty408/ * @mail 492794628@qq.com * @phone 13882122019 * */ namespace Sz.MMO.GameServer.TimerMap { /// <summary> /// /// </summary> public class MapHeartTimer : ThreadPool.TimerTask { int serverID, lineID, mapModelID; long mapID; /// <summary> /// 指定1秒执行一次 /// </summary> public MapHeartTimer(int serverID, int lineID, long mapID, int mapModelID) : * ) { this.serverID = serverID; this.lineID = lineID; this.mapID = mapID; this.mapModelID = mapModelID; } /// <summary> /// /// </summary> public override void Run() { Logger.Debug("我是地图心跳检查器 执行线程:" + System.Threading.Thread.CurrentThread.Name); Logger.Debug("我是地图心跳检查器 检查玩家是否需要复活,回血,状态"); //var scripts = Sz.ScriptPool.ScriptManager.Instance.GetInstances<IMapHeartTimerScript>(); //foreach (var item in scripts) //{ // item.Run(serverID, lineID, mapID, mapModelID); //} } } } using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Sz.MMO.GameServer.IMonsterScripts; /** * * @author 失足程序员 * @Blog http://www.cnblogs.com/ty408/ * @mail 492794628@qq.com * @phone 13882122019 * */ namespace Sz.MMO.GameServer.TimerMonster { /// <summary> /// /// </summary> public class MonsterHeartTimer: ThreadPool.TimerTask { int serverID, lineID, mapModelID; long mapID; /// <summary> /// 指定1秒执行一次 /// </summary> public MonsterHeartTimer(int serverID, int lineID, long mapID, int mapModelID) : * ) { this.serverID = serverID; this.lineID = lineID; this.mapID = mapID; this.mapModelID = mapModelID; } /// <summary> /// /// </summary> public override void Run() { Logger.Debug("怪物心跳检查器 执行线程:" + System.Threading.Thread.CurrentThread.Name); Logger.Debug("怪物心跳检查器 检查怪物是否需要复活,需要回血,是否回跑"); //var scripts = Sz.ScriptPool.ScriptManager.Instance.GetInstances<IMonsterHeartTimerScript>(); //foreach (var item in scripts) //{ // item.Run(serverID, lineID, mapID, mapModelID); //} } } } using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; /** * * @author 失足程序员 * @Blog http://www.cnblogs.com/ty408/ * @mail 492794628@qq.com * @phone 13882122019 * */ namespace Sz.MMO.GameServer.TimerMonster { /// <summary> /// /// </summary> public class MonsterRunTimer: ThreadPool.TimerTask { int serverID, lineID, mapModelID; long mapID; /// <summary> /// 指定1秒执行一次 /// </summary> public MonsterRunTimer(int serverID, int lineID, long mapID, int mapModelID) : * ) { this.serverID = serverID; this.lineID = lineID; this.mapID = mapID; this.mapModelID = mapModelID; } /// <summary> /// /// </summary> public override void Run() { Logger.Debug("怪物移动定时器任务 执行线程:" + System.Threading.Thread.CurrentThread.Name); Logger.Debug("怪物移动定时器任务 怪物随机移动和回跑"); //var scripts = Sz.ScriptPool.ScriptManager.Instance.GetInstances<IMonsterHeartTimerScript>(); //foreach (var item in scripts) //{ // item.Run(serverID, lineID, mapID, mapModelID); //} } } }
就在初始化地图线程的时候加入定时器任务
) { this.MapID = SzExtensions.GetId(); this.MapModelID = mapModelId; Logger.Debug("开始初始化地图: " + name + " 地图ID:" + MapID); ; i <= lineCount; i++) { MapLineInfo<TPlayer, TNpc, TMonster, TDropGoods> lineInfo = new MapLineInfo<TPlayer, TNpc, TMonster, TDropGoods>(name + "-" + i + "线"); //添加地图心跳检测器 lineInfo.MapServer.AddTimerTask(new MapHeartTimer(ServerID, i, MapID, MapModelID)); //添加怪物移动定时器 lineInfo.MapServer.AddTimerTask(new MonsterRunTimer(ServerID, i, MapID, MapModelID)); //添加怪物心跳检测器 lineInfo.MapServer.AddTimerTask(new MonsterHeartTimer(ServerID, i, MapID, MapModelID)); mapLineInfos[i] = lineInfo; } Logger.Debug("初始化地图: " + name + " 地图ID:" + MapID + " 结束"); }
其实所有的任务定时器处理都是交给了timer线程,timer线程只负责查看该定时当前是否需要执行。而具体的任务执行移交到线程执行器。线程执行器是按照队列方式执行。保证了timer线程只是一个简单的循环处理而不至于卡死同样也保证了在同一张地图里面各个单元参数的线程安全性。
来看看效果。
为了方便我们看清楚一点,我把地图线程改为以一条线。
这样就完成了各个定时器在规定时间内处理自己的事情。
需要注意的是这里只是简单的模拟的一个地图处理各种事情,最终都是由一个线程处理的。那么肯定有人要问了。你一个线程处理这些事情能忙得过来嘛?有两点需要注意1,你的每一个任务处理处理耗时是多久,换句话说你可以理解为你一秒钟能处理多少个任务。2,你的地图能容纳多少怪物,多少玩家,多少掉落物?换句话说也就是你设计的复杂度间接限制了你的地图有多少场景对象。
那么还有什么需要注意的呢?
其实地图最大的消耗在于寻路。高性能的寻路算法和人性化寻路算法一直是大神研究的对象,我也只能是借鉴他们的了。
这一章我只是简单的阐述了地图运行和任务等划分和构成已经任务处理流程。
接下来我会继续讲解游戏服务器编程,一步一步的剖析。
文路不是很清晰。希望大家不要见怪。