一步一步开发Game服务器(四)地图线程
时隔这么久 才再一次的回归正题继续讲解游戏服务器开发。
开始讲解前有一个问题需要修正。之前讲的线程和定时器线程的时候是分开的。
但是真正地图线程与之前的线程模型是有区别的。
为什么会有区别呢?一个地图肯定有执行线程,但是每一个地图都有不同的时间任务。比如检测玩家身上的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,你的地图能容纳多少怪物,多少玩家,多少掉落物?换句话说也就是你设计的复杂度间接限制了你的地图有多少场景对象。
那么还有什么需要注意的呢?
其实地图最大的消耗在于寻路。高性能的寻路算法和人性化寻路算法一直是大神研究的对象,我也只能是借鉴他们的了。
这一章我只是简单的阐述了地图运行和任务等划分和构成已经任务处理流程。
接下来我会继续讲解游戏服务器编程,一步一步的剖析。
文路不是很清晰。希望大家不要见怪。
一步一步开发Game服务器(四)地图线程的更多相关文章
- 微信小程序开发教程(四)线程架构与开发步骤
线程架构 从前面的章节我们可以知道,.js文件是页面逻辑处理层.我们可以按需在app.js和page.js中添加程序在生命周期的每个阶段相应的事件.如在页面的onLoad时进行数据的下载,onShow ...
- 一步一步开发Game服务器(三)加载脚本和服务器热更新(二)完整版
上一篇文章我介绍了如果动态加载dll文件来更新程序 一步一步开发Game服务器(三)加载脚本和服务器热更新 可是在使用过程中,也许有很多会发现,动态加载dll其实不方便,应为需要预先编译代码为dll文 ...
- 一步一步开发Game服务器(一)
什么是服务器?对于很多人来说也许只是简单成为在服务器端运行的程序的确如此,服务器通常意义就是说在服务器端运行的程序而已.那么我们怎么理解和分析游戏服务器哪? 传统意义上来说,程序运行后,正常流程, 启 ...
- 如何一步一步用DDD设计一个电商网站(四)—— 把商品卖给用户
阅读目录 前言 怎么卖 领域服务的使用 回到现实 结语 一.前言 上篇中我们讲述了“把商品卖给用户”中的商品和用户的初步设计.现在把剩余的“卖”这个动作给做了.这里提醒一下,正常情况下,我们的每一步业 ...
- 一步一步学ZedBoard & Zynq(四):基于AXI Lite 总线的从设备IP设计
本帖最后由 xinxincaijq 于 2013-1-9 10:27 编辑 一步一步学ZedBoard & Zynq(四):基于AXI Lite 总线的从设备IP设计 转自博客:http:// ...
- 跟我一步一步开发自己的Openfire插件
http://www.blogjava.net/hoojo/archive/2013/03/07/396146.html 跟我一步一步开发自己的Openfire插件 这篇是简单插件开发,下篇聊天记录插 ...
- 一步一步实现HTTP服务器-开篇
缘起 翻开清单,一条条计划一直列在那里,一天又一天,不知道什么时候写下了它,也知不道什么时候完成它,它一直在那静静的等待着. 静下心来,反思自己,才发现自己是多么的无知,多么的没有毅力.设定了无数目标 ...
- 一步一步构建手机WebApp开发——页面布局篇
继上一篇:一步一步构建手机WebApp开发——环境搭建篇过后,我相信很多朋友都想看看实战案例,这一次的教程是页面布局篇,先上图: 如上图所示,此篇教程便是教初学者如何快速布局这样的页面.废话少说,直接 ...
- 一步一步构建手机WebApp开发——环境搭建篇
从2007年,乔布斯带来了第一代Iphone手机,整个移动互联网发生天翻地覆的变化,也同时证明了乔布斯的一句名言:“再一次改变世界”. 在当今的移动互联网,手机App居多,很多App对移动设备的要求也 ...
随机推荐
- Xamarin+Prism开发详解四:简单Mac OS 虚拟机安装方法与Visual Studio for Mac 初体验
Mac OS 虚拟机安装方法 最近把自己的电脑升级了一下SSD固态硬盘,总算是有容量安装Mac 虚拟机了!经过心碎的安装探索,尝试了国内外的各种安装方法,最后在youtube上找到了一个好方法. 简单 ...
- nginx源码分析之网络初始化
nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...
- Nexus(一)环境搭建
昨天,成功搭建了自己的 Maven 环境(详见:Maven(一)环境搭建),今天就来研究和探讨下 Nexus 的搭建! 使用背景: 安装环境:Windows 10 -64位 JDK版本:1.7 Mav ...
- 深入理解javascript函数定义与函数作用域
最近在学习javascript的函数,函数是javascript的一等对象,想要学好javascript,就必须深刻理解函数.本人把思路整理成文章,一是为了加深自己函数的理解,二是给读者提供学习的途径 ...
- Oracle第一步
Oracle 启动数据库 Startup [NOMOUNT|MOUNT|OPEN|FORCE] [restrict] [pfile=filename] 启动实例,加载数据库,启动数据库 oRACLE关 ...
- 【SAP业务模式】之ICS(六):发票输出类型
这篇开始主要讲述发票输出类型: 首先我们新建一个发票类型,用于公司间的发票MIV,而标准的发票类型还是F2保持不变: 一.新建发票类型: 目录:SPRO-销售与分销-出具发票-开票凭证-定义出具发票类 ...
- Win10连接远程桌面时提示“您的凭据不工作”
我遇到这个问题的时候查找网上都给出一堆高大上的解决办法, 然而我的错误实际上是用户名的问题, 很多人以为远程用户名就一定是锁屏状态下的登录名, 其实不是,跟自己设置有关,所以首先应该检查远程用户名是否 ...
- linux字符串url编码与解码
编码的两种方式 echo '手机' | tr -d '\n' | xxd -plain | sed 's/\(..\)/%\1/g' echo '手机' |tr -d '\n' |od -An -tx ...
- SpringMVC(关于HandlerMapping执行流程原理分析)
请求过来先碰见中央调度器(前端调度器) //Determine handler for the current request; 对当前请求决定交给哪个handler, 当前请求地址过来 处理器执行链 ...
- 超炫的HTML5粒子效果进度条 VS 如何规范而优雅地code
最近瞎逛的时候发现了一个超炫的粒子进度效果,有多炫呢?请擦亮眼镜! // _this.ch){ _this.particles.splice(i, 1); } }; this.Particle.p ...