[转载]Silverlight游戏开发心得(1)——调度器

mikel阅读(1006)

[转载]Silverlight游戏开发心得(1)——调度器 – 游戏开发日志–向恺然 – 博客园.

前言:

说实话,很久没有正儿八经写东西了,都不知道咋写了。如果不幸看到这篇文章的哥们们,凑合看吧。

我这个比不了深蓝的东西,也不是教程。只能说是拾人牙慧,人云亦云吧,从各种 书籍里汲取自己能看得懂的东西,结合自己的开发经验,弄成自己的一套东西。《游戏编程精粹》、《游戏人工智能编程案例精粹》(这两本是邮电出版的)《游戏 编程中的人工智能技术》(这本是清华的),都是些不错的书。如果您已经看过而且理解,建议不要浪费时间再往下看了。个人的经验,这个过程很重要。在一些新 领域,新技术,比如Silverlight 技术,往往没有什么现成的范例可以模仿;即便有相像的东西,结合到具体的应用,也是千差万别的。

我自己的体会:学习知识的时候要低调——潭水之所以深不可测,正是因为所处的 位置极低——不要带有成见,耐心地,小心地汲取他人的经验,批判地继承,认真地分析,从中学到本质的东西为我所用;使用的时候要大胆一些,哪怕你写出来的 东西,简陋糟糕,完整的,有用的代码比 优雅的理论要好得多。不担三分险,难练一身胆;不要期望一次就写出来完美的代码,现在的简陋就是明天优秀的开始。我是鼓励大家大胆地对学习到的东西进行改 造,大刀阔斧地进行改造,不断地尝试各种方法。在这个过程中会有更深的理解过程,同时也会有自己的东西产生。

游戏架构:

提到架构,引擎,总透着一股高深莫测的味道。这个问题要分两面看,首先,架 构,引擎确实是好东西,也是很多高人前辈心血的总结,会为我们的开发带来莫大的好处,这是毋庸置疑的。但同时,这些东西是可以学习的,或许学习的曲线会有 些陡峭,但是绝不是不可理解的。

有个哥们这么说过“这类东西常常被冠上一个看似很深奥的名字,但是仔细看过以 后,你就会发现这不是我一直在用的东西么?”用这句话来给架构引擎做个注脚倒挺合适。

我们开发系统,总会有一个结构的,即便你没有意识到这点。

游戏开发是一个难度很高的事情,逻辑复杂,对系统的性能要求很高,不断有新鲜 的,好玩的点子涌现出来,而这些点子对开发者来说都是 一场场的噩梦。所以如果有一个灵活的架构,将是非常好的事情,也很幸运。

几年前初接触Silverlight 游戏开发的时候,学习到了一种游戏的开发方式。

(图1. 这是在公司里做技术交流的时候发言的PPT)

类似雷电的一个小游戏,在游戏里驱动一个主循环,在主循环里面驱动场景的循 环,里面好像是些地图不断地滚动,上面还有些云彩什么的,在场景里的维护者一个游戏对象列表,驱动这个列表里的所有对象进行循环,这些游戏对象继承自一个 基类,最后做的事情就是驱动这些对象循环。然后整个游戏就会优雅地跑起来,看上去也很酷。这些小飞机们,都会有自己的“智能”,能判断周围发生的一切,这 都源于他们每隔30ms就循环一次。

这样基于轮询的方式其实这样也不坏,也能够做很多的事情,在目前的硬件水平 下,运行的也不慢。

但随着对游戏的期望值不断增高,不满足于做些小飞机了,希望有更酷的动画,更 多的状态,更高的智能;

比如这样的东东:(我还没想好给这个游戏起个什么名字,要是有朋友有兴趣,帮 起一个?)

在原有的基础上进行改进的余地已经不大了,最后一层循环里的游戏对象,越老越复 杂,类像吹气球一样的膨胀起来。而且总觉得,有事没事都要循环一下,看看有没有什么事情发生,似乎总是有着“低效”的嫌疑。(插一句,似乎在游戏里的运行 证明,这也没什么,仍然可以让游戏运行的很好)。在这种情况下,有必要寻求更多的变化和帮助。在编程里面我信奉这样的原则:面向抽象编程,而不是面向实现 编程。has-a 比 Is-a 更好。于是有了新的架构设计

(图上标注的是更加好的游戏架构,如果写成更加灵活的价格更合适。)

我没有办法应用“电梯原则”解释这张图。(和客户做电梯从一楼到11楼,这点 时间内把项目说清楚)对于这种结构化的方式,可以构造的很合理,各部分和谐美妙的工作在一起;也可以为了结构化而结构化,画出来很酷的结构示意图,结果是 一团乱麻缠绕在一起。我建议大家这样看吧:把所有的箭头都去掉。你仅仅理解说,整个系统被划分成了:调度器,实体,实体消息处理,数据,状态机,状态机管 理…. 或者更干脆一点,简单一句话:系统被划分成了各种部分。一篇文章介绍整个系 统是不现实的,那样只会泛泛而谈,收获不大。就像这篇文章提到的《共 享的精神》,我会把这些年的SilverlightGame 开发经验,在几个月内和大家分享的。

首先让我们先介 绍一下调度器吧。

在《游戏编程精粹3》里面有这样一段话:

1.1      调度游戏中的事件

一个调度其能有效帮助以下游戏技术的实现,他们包括物理仿真,人物运动,碰撞检测,游戏中的人工智能,渲染。在所有这些技术中有一个关键问题就是时间。在 不同的时间里,当数百个不同的物体和过程都需要更新时,这些仿真技术的非常多种东西变得非常复杂。

调度器的重要能力在于他能够动态地增加和删除物体,这能使新物体非常平滑地加入到游戏里面去,和其他游戏里面的物体一起参加仿真,然后在不必的时候从调度 里面把他删除。

1.1.1 调度器的组成

调度器的基本组件包括任务管理器,事件管理器和时钟。通过这些组件调度器就能生成基于时间或基于帧的事件,然后调用相应的事件处理器。

任务管理器处理任务的注册和组织。每个任务都有一个包含了一个管理器能调用的回调 函数的接口。任务管理器维护了一个任务列表,其中包含了每一个任务的调度信息—例如开始时间,执行频率,持续时间,优先级和其他的属性。他也可能包含 一个用户数据的指针或性能统计信息。

事件管理器是调度器的核心部分。任务管理器里面的每一个任务都定义了一个或多个其 需要处理的事件。一个事件指的是个任务需要执行的时间。事件管理器的责任就是要产生必须的事件以执行相应的任务。

真实时间和虚拟时间:一个真实时间的调度在概念上是非常简单的—时间管理器不停地 进行循环,察看一个真实的时间时钟,每当目标到达的时候他就会触发一个事件。一个虚拟事件的调度器会把时间分成帧。任务在帧之间以批处理的方式进行,在虚 拟时间里运行,然后在每帧渲染出来的时候和真实的时间进行同步。

时钟组件是用来跟踪真实时间,当前的仿真时间和帧数的。时间管理器负责事件的排序 和产生。在某些情况下,多个任务可能会设置在同一个时间运行。有较高优先级的先执行。如果优先级相等或系统没有优先级就轮流执行。我们经常需要动态地更改 一个已注册的任务属性,这可能会牵涉到更改他的优先级,周期,持续时间或需求在他找到还没有结束的时候就将他删除。为了能更新任务的属性,我们必须使用一 个外部的方法来找到他,能使用一个唯一的注册ID来标志一个任务。

1.1.2 一个简单的调度器

调度器的设计主要集中在两个组件上面—–调度器引擎本身和ITask插件接口。要使调度器运行起来,必须要有一个调用他的程式。在一个非图像里面的 程式里面,这需求把他放在一个循环里面然后执行顺序里面然后执行就能。While (running) scheduler.ExecuteFrame();有两种方法把调度器集成在一个消息驱动的图像界面上。第一种方法是修改消息循环来处理消息和调用调度 器。这是个最容易想到的方法,不过有个缺点,就是当窗口大小来来改动的时候调度器会停止工作。第二种方法是创建一个视窗系统时钟,利用时钟消息来调用调度 器。由于时钟消息并不会被窗口的拖动打断,调度器就能在后台就接续运行了。

仿真:调度器能用来驱动仿真系统。为了实现动画和碰撞检测功能,大多数仿真引擎都将时间分成独立的小片。

上面的论述精彩而简洁,但可惜,如果不经过动手,是无法理解的。

本来想写一个能够图形演示的Demo,不过这会带来一些问题,要完成这样一个 Demo,就不仅仅是调度器所能完成的了,要包括很多东西,而这些东西对于熟悉整个游戏的朋友们来说好办,对于其他朋友来说,就会增加新的困扰,甚至因此 学习中断。至少我学习一些陌生的知识的时候,总是希望这个专题“单纯”一些。最后决定弄一个Silverlight的壳子,但更像控制台程序的东西。之所 以不直接做成控制台程序,是因为要保证所有用的API都是Silverlight能用的,而且能够为以后的Silverlight项目说使用。

首先大家 看看,这个Demo的结构。很简单,下面对每个类进行一下介绍。

Scheduler类:

这个类当然是核心了。

在它里面维护着一些重要的变量: 帧任务队列的头任务,时间任务 队列的头任务,还有一个渲染任务。

还有一些标志时间的变量,当然随着程序的扩展,你可以加入更多的东西。

而它的方法包括:

  • 注册任务(把一个任务注册到帧任务队列/时间任务队列,或者是注册成渲染任务)
  • 取得帧任务队列/时间任务队列的头任务
  • 插入任务到帧任务队列/时间任务队列
  • 根据ID删除任务
  • 根据ID暂停任务(注意,这里时间上是把任务的状态标志设置为暂停,不要因词害 意,实际上后面的编程都说明了一点,这些方法是协同工作的,单独一个方法是完成不了一个功能性的任务,如果你这么做了,往往这个方法就设计的太复杂了)
  • 执行任务队列里的任务和渲染任务

这里我要提请大家注意,调度器的方法是很时钟紧密联系在一起的,正因为这样,才可 以发送延时消息之类的。里面的代码不是很复杂,但也不那么直观,一些精巧的地方还是需要反复理解的。

Clock类:

虽然这个类很重要,但它并不复杂,里面就是些标志时间的变量:帧数,当前时间,每 帧持续时间,系统总持续时间…

但我希望你不要忽视这个类,整个游戏都是和时间有关的,会不断地用到

还有一些重要但却没有什么实际内容的类,幸好我的注释写的详细,大家看起来不会困 难

Clock

public class Clock { public int FrameElapsed; public bool IsRunning; /// <summary> /// 获取系统启动后的时间,这是一个绝对值 /// </summary> public int ThisTime; public int LastTime; /// <summary> /// 系统启动后的运行的时间,调用Reset后会归零,这是一个累计, /// 每经过一个Update,就累计一个帧长度,这个帧长度是 ThisTime - LastTime /// </summary> public int SystemTime; public int SystemOffset; public int PauseTime; public int FrameCount; public int FrameStart; public int FrameEnd; /// <summary> /// 仿真时间 /// </summary> public int SimulationTime; public int SimulationTimeOffset; private static Clock instance; /// <summary> /// 这样写是为了避免一种特殊情况,在还没有建立实例的时候,有多个线程调用这个属性,那么它就会被创建多个实例,所以采取了一个Lock /// 但也有更简洁的写法 public static readonly Clock Instance = new Clock();我为了提醒自己多线程的特殊情况而选择了个麻烦的做法 /// </summary> public static Clock Instance { get { if (instance == null) { lock (typeof(Clock)) { if (instance == null) instance = new Clock(); } } return instance; } } private Clock() { //单例模式 Reset(); } public void Reset() { IsRunning = false; ThisTime = Environment.TickCount; LastTime = ThisTime; SystemTime = 0; PauseTime = 0; SystemOffset = ThisTime; FrameCount = 0; FrameEnd = 0; FrameStart = 0; SimulationTime = 0; SimulationTimeOffset = 0; } /// <summary> /// 每一个时钟循环所要做的事情 /// </summary> public void Update() { int elapsed = 0; LastTime = ThisTime; ThisTime = Environment.TickCount; elapsed = ThisTime - LastTime; if (elapsed < 0) elapsed = -elapsed; SystemTime += elapsed; } /// <summary> /// 启动时钟,更新此时帧数,设置虚拟时间 /// </summary> public void BeginFrame() { FrameCount++; Update(); if (IsRunning) { FrameElapsed = FrameEnd - FrameStart; FrameStart = FrameEnd; FrameEnd = SystemTime - SimulationTimeOffset; SimulationTime = FrameStart; } } /// <summary> /// 启动程序,如果是暂停状态,要进行一个更新 /// </summary> public void Run() { if (!IsRunning) { Update(); SimulationTimeOffset += (SystemTime - PauseTime); } IsRunning = true; } /// <summary> /// 暂停程序,并且如果是启动状态,要进行一次更新,更新暂停时间为系统运行时间 /// </summary> public void Stop() { if (IsRunning) { Update(); PauseTime = SystemTime; } IsRunning = false; } /// <summary> /// 设置虚拟时间,如果提供的值大于等于上一个虚拟时间,这更新虚拟时间为新值 /// </summary> /// <param name="_newTime">提供的值</param> public void AdvanceTo(int _newTime) { if (IsRunning && (_newTime >= SimulationTime)) SimulationTime = _newTime; } /// <summary> /// 设置虚拟时间,让虚拟时间等于FrameEnd /// </summary> public void AdvanceToEnd() { if (IsRunning) SimulationTime = FrameEnd; } }
调度器

public class Scheduler { public static readonly Scheduler Instance = new Scheduler(); /// <summary> /// 保证渲染事件的ID总是1 /// </summary> private const int RENDER_TASK_ID = 1; private TaskInfo timeTaskHead; private TaskInfo frameTaskHead; private TaskInfo renderTask; //渲染任务 private int nextID; private int frameTaskStart; private int frameTaskEnd; private int frameTaskElapsed; private TaskInfo taskInfo; private Scheduler() { renderTask = new TaskInfo(); nextID = RENDER_TASK_ID + 1; } public void ExecuteFrame() { Clock.Instance.BeginFrame(); Scheduler.Instance.taskInfo = null; int start = Clock.Instance.SystemTime; TaskInfo current = GetHeadTask(TASKTYPE.TIME); while (current != null) { Clock.Instance.AdvanceTo(current.InfoTime.next); //current.PTask.Execute(current.ID, Clock.Instance.SimulationTime, current.Data); //和下面一句起同样的作用 current.ExecuteTask(current.ID, Clock.Instance.SimulationTime, current.Data); current.InfoTime.last = current.InfoTime.next; current.InfoTime.next += current.InfoTime.interval; if ((current.InfoTime.duration == 0) || (current.InfoTime.duration >= current.InfoTime.next)) { InsertTimeTask(current); } else { current = null; } current = GetHeadTask(TASKTYPE.TIME); } Clock.Instance.AdvanceToEnd(); current = GetHeadTask(TASKTYPE.FRAME); while (current != null) { current.ExecuteTask(current.ID, Clock.Instance.SystemTime, current.Data); current.InfoTime.last = current.InfoTime.next; current.InfoTime.next += current.InfoTime.interval; if ((current.InfoTime.duration == 0) || (current.InfoTime.duration >= current.InfoTime.next)) { InsertFrameTask(current); } current = GetHeadTask(TASKTYPE.FRAME); } if(renderTask!=null) renderTask.ExecuteTask(renderTask.ID, Clock.Instance.FrameCount, renderTask.Data); } /// <summary> /// 注册任务 /// </summary> /// <param name="_taskType">任务类型</param> /// <param name="_time">任务的时间信息</param> /// <param name="_task">任务执行方法</param> /// <param name="_data1">附加信息</param> public void RegistTask(TASKTYPE _taskType, int _start, int _interval, int _duration, int _budget, ExecuteTask _execute, Object _data) { if (_taskType == TASKTYPE.RENDER) { renderTask.ExecuteTask = _execute; renderTask.Data = _data; renderTask.Status = STATUS.ACTIVE; renderTask.ID = RENDER_TASK_ID; return; } else { TaskInfo newTask = new TaskInfo(); newTask.Status = STATUS.ACTIVE; newTask.Data = _data; newTask.ExecuteTask = _execute; newTask.ID = nextID++; newTask.InfoTime.start = _start; newTask.InfoTime.interval = _interval; newTask.InfoTime.duration = _duration; newTask.InfoTime.budget = _budget; newTask.InfoTime.next = _start; if (_duration == 0) newTask.InfoTime.duration = 0; else newTask.InfoTime.duration = _start + _duration - 1; if (_taskType == TASKTYPE.FRAME) InsertFrameTask(newTask); else if (_taskType == TASKTYPE.TIME) InsertTimeTask(newTask); } } public void Run() { Clock.Instance.Run(); } public void Stop() { Clock.Instance.Stop(); } /// <summary> /// 返回时间任务队列/帧任务队列里的第一个任务,同时把这个任务从队列里清除掉 /// </summary> /// <param name="_taskType">任务类型</param> /// <returns>对应类型的头任务</returns> public TaskInfo GetHeadTask(TASKTYPE _taskType) { TaskInfo result = null; if (_taskType == TASKTYPE.FRAME) { if ((frameTaskHead!=null) && (frameTaskHead.InfoTime.next <= Clock.Instance.FrameCount)) { //像这样和顺序有关的关联操作 要小心谨慎,多做测试,否则很容易出错 result = frameTaskHead; frameTaskHead = frameTaskHead.NextTask; result.NextTask = null; } } else if (_taskType == TASKTYPE.TIME) { if( (timeTaskHead != null) && (timeTaskHead.InfoTime.next <= Clock.Instance.FrameEnd) ) { result = timeTaskHead; timeTaskHead = timeTaskHead.NextTask; result.NextTask = null; } } return result; } /// <summary> /// 把一个任务从 帧任务链表/时间任务链表 中删除,并返回这个任务 /// </summary> /// <param name="_taskID">需要找到的任务ID</param>//思考:这似乎不是一个高效的做法,字典,树,或者多线程都是解决的方式 public void RemoveTask(int _taskID) { bool isFind = false; //如果已经找到结果,就不再判断 if ((_taskID == RENDER_TASK_ID) && (renderTask!=null)) { renderTask.ExecuteTask = null; renderTask.Status = STATUS.DELETE; renderTask = null; return; } TaskInfo current = frameTaskHead; if (current != null) { if (current.ID == _taskID) { frameTaskHead = frameTaskHead.NextTask; current.Status = STATUS.DELETE; current.NextTask = null; return; } while (current.NextTask != null) { if (current.NextTask.ID == _taskID) { current.NextTask.Status = STATUS.DELETE; current.NextTask = current.NextTask.NextTask; isFind = true; break; } current = current.NextTask; } if (isFind) return; } current = timeTaskHead; if (current != null) { if (current.ID == _taskID) { timeTaskHead = timeTaskHead.NextTask; return; } while ((current.NextTask != null) && !isFind) { if (current.NextTask.ID == _taskID) { current.NextTask.Status = STATUS.DELETE; current.NextTask = current.NextTask.NextTask; break; } current = current.NextTask; } } return; } /// <summary> /// 将指定ID的Task设置为暂停,并返回这个任务 /// </summary> private void PauseTask(int _taskID) { bool isFind = false; //如果已经找到结果,就不再判断 if (_taskID == RENDER_TASK_ID) { renderTask.Status = STATUS.PAUSE; return; } TaskInfo current = frameTaskHead; if (current.ID == _taskID) { frameTaskHead.Status = STATUS.PAUSE; return; } while ((current.NextTask != null) && !isFind) { if (current.NextTask.ID == _taskID) { current.Status = STATUS.PAUSE; isFind = true; } current = current.NextTask; } if (isFind) return; current = timeTaskHead; if ((current.ID == _taskID) && !isFind) { timeTaskHead.Status = STATUS.PAUSE; return; } while ((current.NextTask != null) && !isFind) { if (current.NextTask.ID == _taskID) { current.Status = STATUS.PAUSE; isFind = true; } current = current.NextTask; } return; } private void InsertFrameTask(TaskInfo _newTask) { if (frameTaskHead == null) { _newTask.NextTask = null; frameTaskHead = _newTask; } else if (frameTaskHead.InfoTime.next > _newTask.InfoTime.next) { _newTask.NextTask = frameTaskHead; frameTaskHead = _newTask; } else { TaskInfo current = frameTaskHead; while (current != null) { if (current.NextTask == null) { _newTask.NextTask = null; current.NextTask = _newTask; break; } else if (current.NextTask.InfoTime.next >= _newTask.InfoTime.next) { _newTask.NextTask = current.NextTask; current.NextTask = _newTask; break; } current = current.NextTask; } } } private void InsertTimeTask(TaskInfo _newTask) { if (timeTaskHead == null) { _newTask.NextTask = null; timeTaskHead = _newTask; } else if (timeTaskHead.InfoTime.next > _newTask.InfoTime.next) { _newTask.NextTask = timeTaskHead; timeTaskHead = _newTask; } else { TaskInfo current = timeTaskHead; while (current != null) { if (current.NextTask == null) { _newTask.NextTask = null; current.NextTask = _newTask; break; } else if (current.NextTask.InfoTime.next >= _newTask.InfoTime.next) { _newTask.NextTask = current.NextTask; current.NextTask = _newTask; break; } current = current.NextTask; } } } }
代码

public class TaskInfo { public ITask PTask { set; get; } //有具体执行方法的ITask,这里实际上和委托是同一个意思,我觉得委托更方便一些 public ExecuteTask ExecuteTask { set; get; } //指向执行任务的具体方法 public TaskInfo NextTask { set; get; } //指向下一个任务 public int Priority { set; get; } //任务优先级 public int ID { set; get; } //标识任务的唯一性 public object Data { set; get; } //任务数据 public STATUS Status { set; get; } //任务活动属性 public TimeInfo InfoTime; //任务的时间属性 } public struct TimeInfo { public int start { set; get; } //开始 public int interval{ set; get; } //间隔 public int duration{ set; get; } //持续 public int last{ set; get; } //最后 public int next{ set; get; } //下一个 public int budget{ set; get; } //预算 } /// <summary> /// 任务的状态 /// </summary> public enum STATUS:int { ACTIVE = 0, //活动任务 PAUSE, //暂停任务 DELETE //删除任务 } /// <summary> /// 任务类型 /// </summary> public enum TASKTYPE : int { TIME = 0, //时间任务 FRAME, //帧任务 RENDER //渲染任务 } /// <summary> /// 每一个具体的任务都需要继承这个接口并实现这个执行方法 /// </summary> public interface ITask { void Execute(int _id, int _time, object _data); } //任务的具体执行方法 public delegate void ExecuteTask(int _id,int _time,object _data);

接下来的时间就简单了,需要做的就是注册一些简单的任务,我只是注册了一些 Debug.Write,然后在MainPage里面构建一个循环,把调度器的ExecuteFrame()放在这个循环里面就可以了,系统会愉快地按照 你的意思工作了。

“纸上得来终觉浅,须知此事要躬行。”最终需要亲爱的朋友,一个字符一个字符地 敲打出来,否则真的很难转化为自己的东西。我不知道会不会有朋友提出希望得到Zip源代码Demo,我想这不是我提倡的学习方式,实际上代码已经在这里 了,在后面的文章中也不会存在Zip这样的东西,记得我最初学习编程的时候,一个有经验的编程人员就告诫我,全部的代码都要敲,哪怕是一个for循环。

[转载]游戏里的实体系统 - 游戏开发日志

mikel阅读(992)

[转载]游戏里的实体系统 – 游戏开发日志–向恺然 – 博客园.

游戏世界是一个乱哄哄的世界,乱哄哄的各种各样的人物,怪物奔跑着,发射着五 花八门的子弹;各种古里古怪的地形,会吃人的建筑物;如果“上帝”愿意,随时可以刮风下雨。哦,MG!我的头脑里容不下这么多东西了,这简直是场灾难。

我希望把他们统一起来,而不是区别对待;我希望用一种逻辑来控制管理他们,而 不是一万种。

可以做到吗?

很高兴,可以。

把这些游戏元素统一成一个整体,并且提供一种通用的方法来管理,这是非常重要 的。这就是“游戏实体系统”,把许许多多各异的元素都看成实体,不论它是操控的英雄,还是张牙舞爪的怪物,或者是个里面藏着包子的坛子(你玩过三国,不是 吗?),或者是漂浮在空中的云彩;甚至是只有游戏开发者才知道的脚本,地形,游戏的根容器……在这里,统统被看成了实体。而实体之间是通过各种各 样的消息来传递信息,做出反应的。所以这一切被称作了“基于消息的实体系统”。

大家如果读过《游戏里的调度器》,应该还记得我提到过,游戏基于轮询的结构和基于消息的结构。在基于消息的系统中,实体是懒惰的,是无所事事的,除非…除非它收到了一条 消息,而且它还需要能够理解这条消息。

  • 我们把所有能看成是实体的东西都看成是实体
  • 实体之间通过消息进行通信

接下来我们把上面两句话进行扩展并具体实现,请相信我,这一章非常的重要,而 且很有用,并且不是非常繁难。我衷心希望您能够掌握。

实体的管理:

假设已经把所有的一切都看作了实体,也就是说我们拥有了很多很多的实体,毫无 疑问,我们不能够让这么多的实体杂乱无章的放着,需要把它们组织起来。让我们想一想,应该如何组织这些实体呢?我们是游戏中的上帝,在创世之初做些什么 呢?或许你应该创立一个根实体,你可以命名为GameRoot,在这个实体上可以生长其他实体,具体有哪些,要看具体情况,但我想,应该有若干个场景实体 吧,在场景实体上跑动着很多NPC,那么NPC实体也就是存在的了,或许NPC手里还有一把来复枪或者激光炮之类的,这些可怕的武器还能够发射出子弹实 体;回顾一下,这会是什么?这肯定是棵树。一棵枝枝丫丫的树。当然,如果你愿意,你可以用任何其他的方式来组织管理你的实体们,你可以用数组,链表,或者 矩阵,图什么的,这都看你的心情和需要了。我认为使用树是合适而巧妙的。比如要删除一个怪物实体,实际上我们希望怪物的来复枪,枪上的子弹,怪物的衣服, 衣服上的饰物都一起消失,就像砍掉一个树枝一样,连带上面的小枝子,叶子都一切砍掉了,而不用做更多的事情。当然我不是说其他结构就做不到这些,或许会麻 烦些吧。看来一个实体管理类是必须的,它能够把实体以恰当的形式组织起来,而且能够删除实体。

实体间的通信:

刚才已经提到过,实体之间是通过消息来通信的,实体只对自己能理解的消息作出 反应,否则就抛弃它们。那么,我们就需要消息,而且是很多很多种消息,每类实体只对其中的特定消息作出反应。

对消息的处理:

实体收到了消息,而且这个消息是它应该理解的,然后它需要对这些消息作出反应。这就需要一个消息处理函数。

  • 经过这样的处理,系统被高度抽象化了,被抽象化的类能够忽略很多个性化的细 节而被统一处理,这给我们开发带来莫大的好处。
  • 大家请注意,经过这样处理之后,并不是把游戏开发的复杂降低了,实际上,该 怎么办还是怎么办,复杂的AI,碰撞,I/O,一个也没少;只是在我们不需要注意这些的时候,它们被“隐藏”在了这样的结构背后
  • 扩展性更好,这一点是显而易见的,如果需要什么新的功能,只需要增加新的消 息处理就可以了
  • 在这一的结构下,程序员需要维护的东西大大降低了,牵一发而动全身的局面得 到了改善

如果您并没有十分的理解上面所说的,我只好为我的写作能力抱歉了,不过也没有 太大的关系,或许您可以在代码中豁然开朗。

现在我们该谈谈实体类,结合上面所说的种种,来看一下实体类 Entity

public class Entity
{
public PROC Proc;                 // 消息处理函数的委托,具体的消息处理逻辑都在这里,你可以在任何地方把一个函数和这里挂接起来。

public Object data;               //实体数据

public Entity parent;           //实体在实体树中的域
public Entity prevSibling;
public Entity nextSibling;
public Entity child;

public int EntID;                //标志实体的唯一标志

}

很简单的一个类。随着具体游戏,你可以给它增加一些东西,我就曾经尝试过,把 可见性,类型,序号,还有一个渲染自己的Image都当作了这个实体的属性。尽管一个抽象的实体(比如脚本实体)肯定是没有可见性和渲染用的Image 的,但没有太大关系,只要在创造实体的时候不建立这些也就是了。但还是要记住,这是一个所有实体的基类,最好还是让它简单点。或许你很奇怪,不是说实体是 通过消息来通信的吗?怎么实体类里面没有体现这个。当然你可以给实体增加这样一个选项,也可以在实体管理类里实现消息发送的功能。

接下来看看消息的代码:

代码

public enum EM : int { EM_CLSINIT = 0, // initialize class EM_CLSFREE, // free up class EM_CLSGUID, // gets the globally unique ID of this entity class EM_CLSNAME, // gets the textual class name // CREATION AND DESTRUCTION EM_CREATE, // create entity (object) EM_START, // turn the entity 'on' EM_SHUTDOWN, // destroy entity gracefully EM_DESTROY, // destroy entity immediately // BASIC OPERATIONS EM_DRAW, // render the entity EM_DRAWEDIT, // render the entity in "editing mode" EM_DRAWDEBUG, // render the entity in "debug mode" EM_PREUPDATE, // run AI, etc. EM_POSTUPDATE, // check collision, etc. EM_SLEVENT, // process a game input event EM_DRAWSHADOW, // draw any shadow effects, sent after DRAW EM_DRAWOVERLAY, // draw "hud" overlays, send after DRAWSHADOW // DATA ACCESS EM_SETNAME, // sets the name of this entity EM_GETNAME, // gets the name of this entity EM_SETPOS, // set position (var1 = VECTOR3*) EM_GETPOS, // get postion (var1 = VECTOR3*) EM_SETVEL, // set velocity vector (var1 = VECTOR3*) EM_GETVEL, // get velocity vector (var1 = VECTOR3*) EM_SETDIR, // set direction vector (var1 = VECTOR3*) EM_GETDIR, // get direction vector (var1 = VECTOR3*) EM_SETOWNER, // tells an entity who "owns" him var1=ENTITY* EM_GETOWNER, // ask who owns an entity var1=ENTITY* EM_GIVEPOINTS, // gives and entity points EM_IMDEAD, // pronouncement that an entity has died EM_USER0, EM_ADDSPRITE, //增加一个游戏精灵 /// <summary> /// 键盘消息 /// </summary> EM_KEYDOWN, EM_KEYUP, /// <summary> /// 鼠标消息 /// </summary> EM_MOUSEDOWN, EM_MOUSEUP, EM_MOUSEMOVE, /// <summary> /// 这个消息用来更新 /// </summary> EM_UPDATE, EM_UPDATEFINAL, /// <summary> /// 默认的一个消息 /// </summary> DEFAULT }

现在我们有了实体,也有了消息,你还记得实体树吗?把你说创建的实体,插到具体的实体树里就是了。

还需要做的一件事情,把消息处理函数和实体挂接起来。真正做事的是它。

我写了一个孩子兄弟树的类,供朋友们参考。

代码

public class CSNode<T> where T:Entity { /// <summary> /// 节点唯一的ID; /// </summary> private int id; public int ID { internal set { id = value; } get { return id; } } private T data; public T Data { set { data = value; } get { return data; } } private CSNode<T> firstChild; public CSNode<T> FirstChild { set { firstChild = value; } get { return firstChild; } } private CSNode<T> nextSibling; public CSNode<T> NextSibling { set { nextSibling = value; } get { return nextSibling; } } private CSNode<T> parent; public CSNode<T> Parent { set { parent = value; } get { return parent; } } public CSNode() { data = default(T); firstChild = null; nextSibling = null; } public CSNode(T _data) { data = _data; firstChild = null; nextSibling = null; } public CSNode(CSNode<T> _firstChild, CSNode<T> _nextSibling) { data = default(T); firstChild = _firstChild; nextSibling = _nextSibling; } public CSNode(T _data,CSNode<T> _firstChild, CSNode<T> _nextSibling) { data = _data; firstChild = _firstChild; nextSibling = _nextSibling; } /// <summary> /// 用节点的ID是否相等作为判断两个节点是不是同一节点的标志 /// </summary> /// <param name="_target">相比较的节点</param> /// <returns>true:同一节点 false:不同节点</returns> public bool EqualsNode(CSNode<T> _target) { return ID == _target.ID; } } public class CSTree<T>:ITree<CSNode<T>> where T:Entity { private int validId = 100; //这里面存在一个隐患,如果实体不断消失,不断增加,但validId总是递增的,程序运行很长时间后,或许会超过int的取值范围 private CSNode<T> root; public CSNode<T> Root { private set { root = value; } get { return root; } } public CSTree() { root = null; } public CSTree(T _rootData) { root = new CSNode<T>(_rootData); } public CSNode<T> GetRoot() { return root; } /// <summary> /// 因为很多方法用了递归,所以只好把返回的结果放在外面了,注意这个集合的值是随时会变的,使用它之前要先调用返回集合的方法。 /// 并且要注意清空 /// </summary> public List<CSNode<T>> result; //------------------------------------------------------------------------------------插入操作-----------------------------------------------------------------------------// /// <summary> /// 把一个节点插在父节点的第一个子节点位置上 /// </summary> /// <param name="_parent">父节点</param> /// <param name="_firstChild">将要插入的节点</param> /// <returns>插入是否成功</returns> public void InsertFirstChild(CSNode<T> _parent, CSNode<T> _firstChild) { if (_parent != null) { validId += 1; _firstChild.ID = validId; _firstChild.Data.ID = validId; _firstChild.Parent = _parent; _firstChild.NextSibling = _parent.FirstChild; _parent.FirstChild = _firstChild; } } /// <summary> /// 把一个指定值的新节点插在父节点的第一个子节点位置上 /// </summary> /// <param name="_parent">父节点</param> /// <param name="_firstChild">将要插入的节点</param> /// <returns>操作是否成功</returns> public bool InsertFirstChild(CSNode<T> _parent, T _data) { if (_parent != null) { CSNode<T> temp = new CSNode<T>(_data); validId += 1; temp.ID = validId; _data.ID = temp.ID; temp.Parent = _parent; temp.NextSibling = _parent.FirstChild; _parent.FirstChild = temp; return true; } return false; } /// <summary> /// 把一个新节点插在父节点的最后一个子节点的后面 /// </summary> /// <param name="_parent">父节点</param> /// <param name="_lastChild">将要插入的节点</param> /// <returns>操作是否成功</returns> public bool InsertLastChild(CSNode<T> _parent,CSNode<T> _lastChild) { if(_parent!=null) { validId += 1; _lastChild.ID = validId; _lastChild.Data.ID = validId; _lastChild.Parent = _parent; if(_parent.FirstChild==null) { _parent.FirstChild = _lastChild; } else { GetLastChild(_parent).NextSibling = _lastChild; } return true; } return false; } /// <summary> /// 把指定节点插入到源节点的指定位置 /// </summary> /// <param name="_sourceNode">父节点</param> /// <param name="_targetNode">被插入的节点</param> /// <param name="_nodePos">需要插入的位置</param> public void Insert(CSNode<T> _sourceNode, CSNode<T> _targetNode, int _nodePos) { if (_nodePos == 0) { InsertFirstChild(_sourceNode, _targetNode); } else if (_nodePos >= GetNodeDegree(_sourceNode)) { InsertLastChild(_sourceNode, _targetNode); } else { CSNode<T> current = _sourceNode.FirstChild; int nodePos = 1; while (nodePos != _nodePos) { current = current.NextSibling; nodePos++; } _targetNode.NextSibling = current.NextSibling; current.NextSibling = _targetNode; } } //-----------------------------------------------------------------------------------删除操作-------------------------------------------------------------------------// /// <summary> /// 删除目标节点 /// </summary> /// <param name="_target">目标节点</param> /// <returns>返回被删除的节点,如果没有此节点,则返回Null</returns> public CSNode<T> Delete(CSNode<T> _target) { if (_target.EqualsNode(Root)) Root = null; ClearChild(_target); if (_target.Parent == null) { MessageBox.Show("节点不存在!"); return null; } if (_target.Parent.FirstChild.EqualsNode(_target)) { _target.Parent.FirstChild = _target.NextSibling; return _target; } else { CSNode<T> current = _target.Parent.FirstChild; while (current.NextSibling!=null) { if (current.NextSibling.EqualsNode(_target)) { current.NextSibling = _target.NextSibling; return _target; } current = current.NextSibling; } return null; } } /// <summary> /// 删除目标节点指定位置的子节点 /// </summary> /// <param name="_target">目标节点</param> /// <param name="_pos">索引</param> /// <returns>被删除的节点</returns> public CSNode<T> DeleteChild(CSNode<T> _target, int _pos) { if (_pos < 0) throw new Exception(); CSNode<T> result = null; int searchPos = 0; CSNode<T> next = null; if (_pos == 0) { result = _target.FirstChild; ClearChild(result); _target.FirstChild = _target.FirstChild.NextSibling; } else { next = _target.FirstChild; searchPos++; while (searchPos != _pos) { next = next.NextSibling; searchPos++; } result = next.NextSibling; ClearChild(result); next.NextSibling = result.NextSibling; } return result; } /// <summary> /// 把父节点的所有子节点指向父节点的域和父节点的首子节点全部设为null /// </summary> /// <param name="_parent">父节点</param> public void ClearChild(CSNode<T> _parent) { CSNode<T> next; if (_parent.FirstChild != null) { next = _parent.FirstChild.NextSibling; while (next != null) { next.Parent = null; next = next.NextSibling; } _parent.FirstChild = null; } } //-----------------------------------------------------------------------------------返回节点集合操作-------------------------------------------------------------------------// /// <summary> /// 取得父节点的最后一个子节点 /// </summary> /// <param name="_parent">父节点</param> /// <returns>父节点的最后一个子节点</returns> public CSNode<T> GetLastChild(CSNode<T> _parent) { if(_parent.FirstChild==null) { return null; } CSNode<T> temp = _parent.FirstChild; while(temp.NextSibling!=null) { temp = temp.NextSibling; } return temp; } /// <summary> /// 返回指定节点的所有子节点,注意!仅仅是子节点,不包括节点的的节点 /// </summary> /// <param name="_parent">父节点</param> /// <returns>父节点的所有子节点的集合</returns> public List<CSNode<T>> GetParentChildren(CSNode<T> _parent) { if (_parent.FirstChild != null) { List<CSNode<T>> result = new List<CSNode<T>>(); result.Add(_parent.FirstChild); CSNode<T> current = _parent.FirstChild.NextSibling; while (current != null) { result.Add(current); current = current.NextSibling; } return result; } else { return null; } } /// <summary> /// 返回指定节点包括自己在内的所有兄弟节点,注意!仅仅是子节点,不包括节点的的节点 /// </summary> /// <param name="_sibling">指定节点</param> /// <returns>结果节点的集合</returns> public List<CSNode<T>> GetSiblingChildren(CSNode<T> _sibling) { CSNode<T> parent = _sibling.Parent; return GetParentChildren(parent); } /// <summary> /// 返回所有的节点,注意!这个方法仅适用于取根节点 /// </summary> /// <param name="_parent">这个参数只能是根节点</param> public void GetAllChildren(CSNode<T> _parent) { if (_parent != null) { GetAllChildren(_parent.FirstChild); result.Add(_parent); GetAllChildren(_parent.NextSibling); } } /// <summary> /// 返回目标节点的所有子节点 /// </summary> /// <param name="_parent">目标节点(父节点)</param> public void GetTargetAllChildren(CSNode<T> _parent) { if (_parent.FirstChild != null) { result.Add(_parent.FirstChild); GetTargetAllChildren(_parent.FirstChild); CSNode<T> current = _parent.FirstChild.NextSibling; while (current != null) { result.Add(current); GetTargetAllChildren(current); current = current.NextSibling; } } } //---------------------------------------------------------------------------------查找操作----------------------------------------------------------------// public CSNode<T> GetNodeFromID(int _id) { result = new List<CSNode<T>>(); CSNode<T> resultNode = null; GetAllChildren(root); foreach (CSNode<T> item in result) { if (item.ID == _id) { resultNode = item; break; } } result.Clear(); result = null; return resultNode; } //---------------------------------------------------------------------------------其他操作----------------------------------------------------------------// /// <summary> /// 判断是否是空树 /// </summary> /// <returns>true:空树 false:非空树</returns> public bool IsEmpty() { return root == null; } /// <summary> /// 得到目标节点的度 /// </summary> /// <param name="_target">目标节点</param> /// <returns>目标节点的度</returns> public int GetNodeDegree(CSNode<T> _target) { int result = 0; CSNode<T> current = _target.FirstChild ; while (current!= null) { result++; current = current.NextSibling; } return result; } /// <summary> /// 判断是否是叶子节点 /// </summary> /// <param name="_target">需要判断的节点</param> /// <returns>true:叶子节点 false:非叶子节点</returns> public bool IsLeaf(CSNode<T> _target) { return GetNodeDegree(_target) == 0; } }

[转载]浅谈AnkhSvn版本控制

mikel阅读(1126)

[转载]浅谈AnkhSvn – 漫步云端 – 博客园.

不管是公司开发还是高校中的合作开发,做好版本和源代码控制始终是所有工作的前提。怎样保证团队中的每个成员能够及时的获取最新的工作进度?怎样避 免团队成员在开发的时候产生修改冲突(最典型的就是两个人同时在修改同一个文件,最后提交的时候提示冲突,可能出现我们丢失掉自己所做的修改)?怎样才能 使我们的开发工作提高效率(比如我们已经保存并提交了某个更改之后却发现修改有误,这种情况下我们需要撤销所作的更改)?怎样才能了解团队其他成员的工作 进度(这是必要的,我们并不仅限于做好自己的本职工作)?怎样才能及时的发现并纠正代码的错误(参加过团队开发的你是否遇到过在本地版本上开发时遇到了错 误,解决后需要到服务器版本上再次修改)?AnkhSvn和SoureSafe的出现在源码控制上给我们提供了解决思路。

一。 AnkhSvn的使用:

1。AnkhSvn是一种对版本和源代码进行控制的工具。使用它我们可以做到随时随地的进行项目开发,摆脱了开 发过程中空间的限制(当然,随时随地的条件是可以上网),同时解决了团队合作中的一个令人头疼的问题–修改冲突。以前我们用SourceSafe进行项 目控制时,经常发生团队成员之间对同一段代码做了修改,而在提交时就会出错,时常出项代码的覆盖以及以及更改的丢失。同时AnkhSvn解决了我们无法查 看对该解决方案所做更改(即view history)的问题,保证了代码提交的安全性,同时使我们每个人从担心代码冲突、代码覆盖的疑虑中解放出来,能够全身心投入项目开发中。

2。 AnkhSvn的常用操作:

(1)建立本地的服务器版本。Subversion->Add to Subversion

此 功能的作用是从源代码上获取一个服务器版本到本地上。

截图00

Subversion->Open from Subversion,然后再Url地址框中输入地址路径(注意:此处的协议头是https,它传输的安全性更高。),点击open按钮即可(第一次使用 时需要制定获取服务器版本到本地的物理路径)。

截图01

(2)获取最新版本

打开解决 方案后,点击右键有一个“Update to Latest Version”。

(3)提交(commit)、撤销(revert)

(4)撤销至某一个版本:右键你要撤销的文件(注意:在这里点击不同的文件的效果是不一样的,例如右键解决方案或者是类库或者是文件夹, 只能在viewhistory或者viewsolutionhistory选项中撤销至你选定的版本;而如果右键某一个文件,例如。cs文件或者。 aspx文件,除了上述选项,我们还可以选定任意的版本来show changes)。

(5)view history、view solution history、view project history三种浏览历史记录的方式。通过浏览历史记录我们可以了解版本信息、了解团队成员的工作情况。(注意:当点击相应的浏览历史记录的按钮时,在 弹出框中有changed paths提示框,双击框中的记录可以查看具体的修改信息。)

截图02

(6)作副本

点击“视图”中 的working copy explorer,然后再working copy folders中选定sln文件,点击上方的copyto图标选定一个存放副本的物理路径(注意必须是一个新的文件夹,而不能是已经存在的文件或文件 夹)。

(7)Merge–版本合并。

最常用的merge方式就是以下两种:

((1))Merge a range of revisions

我的理解是可以合并一系列的不同版本,将不同版本上与本版本的不同之处进行合并。

((2))Reintegrate a branch

就是将分支版本上的修改返回到创建该分支版本的源版本,即主干版本或者其他。要使用这个方法要求这个分支版本不能再 有任何本地修改,必须是一个单独的版本,不能有任何分支子版本。而且这个版本号必须高于或者等于要融合的最后的版本号。

3。常见问题解 析:

(1)代码过时。通常会表现为当我们修改了某个文件的某段代码后准备提交时,当点击“commit”时提示part of your code is out of date。这说明我们在对这个文件进行修改时,同时有另外的成员在对这个页面进行修改,这时我们需要首先获取一下最新版本,如果别的成员做过的修改恰好是 我们改动的部分,就会有形如“>>>>mine”和“<<<<XXXX”的错误,同时文件上可能会有一 个“!”。一般“>>>>mine”下面的表示我们的代码,而“<<<<XXXX”下面的表示其他成员 更改的代码。这时我们需要分析决定怎样处理这个错误,处理后要将“>>>>mine”和 “<<<<XXXX”提示符删掉。

(2)修改冲突。有时候我们修改完毕以后将我们的工作提交后才获取最新版本 (一般不提倡,最好的做法是提交前先获取最新版本,如果有问题处理问题),获取后开始“重新生成解决方案”出现了我们的web项目 BlackJack.Web.csproj无法加载。这是由于我们做了自己的修改提交前并没有及时获取最新版本,导致代码中有重复修改的地方。这时我们需 要右击BlackJack.Web.csproj有个选项是“编辑BlackJack.Web.csproj”,然后在打开的文件中搜索“mine”(目 的是找到冲突地方),找出后进行分析取舍,完成后再次“重新生成解决方案”后加载web项目,这时就会出现,但因为代码中的冲突依然存在因此会有“!”提 示我们,找到相关的冲突的文件处理后会发现文件前的图标变成了红色小方框,代表冲突解决我们就可以编译后提交了。

[转载]发布一个自动生成实体类,接口类,数据底层和业务类的小工具

mikel阅读(998)

[转载]发布一个自动生成实体类,接口类,数据底层和业务类的小工具 – Jiangrod Sky – 博客园.

之前用过李天平大师开发的源码生成器,很好用,功能很全,现在发布的这个小工具当然没法跟李大师的工具相比,它能够为大家代劳一些重复性的而且有规 律性的劳动,比如生成实体类,接口类,数据底层类和业务类。截图如下:

下载地址:

http://files.cnblogs.com/jiangrod/CGT.Steup.rar

[转载]工厂方法模式

mikel阅读(1143)

[转载]工厂方法模式-2 – 云飞龙行 – 博客园.

2  解决方案

2.1  工厂方法模式来解决

用来解决上述问题的一个合理的解决方案就是工厂方法模式。那么什么是工厂方法模式呢?
(1)工厂方法模式 定义
定义一个用于创建对象的接口,让子类决定实例化哪一个类,Factory Method使一个类的实例化延迟到其子类。

(2)应用工厂方法模式来解决的思路
仔细分析上面的问题,事实上在实现导出数据的业务功能对象里面,根本就不知道究竟要使用哪一种导出文件的格式,因此这个对象本就不应该和具体的导出文件的 对象耦合在一起,它只需要面向导出的文件对象的接口就好了。
但是这样一来,又有新的问题产生了:接口是不能直接使用的,需要使用具体的接口实现对象的实例。
这不是自相矛盾吗?要求面向接口,不让和具体的实现耦合,但是又需要创建接口的具体实现对象的实例。怎么解决这个矛盾呢?
工厂方法模式的解决思路很有意思,那就是不解决,采取无为而治的方式:不是需要接口对象吗,那就定义一个方法来创建;可是事实上它自己是不知道如何创建这 个接口对象的,没有关系,那就定义成抽象方法就好了,自己实现不了,那就让子类来实现,这样这个对象本身就可以只是面向接口编程,而无需关心到底如何创建 接口对象了。

2.2  模式结构和说明

工厂方法模式的结构如图3所示:

图3  工厂方法模式结构示意图
Product
定义工厂方法所创建的对象的接口,也就是实际需要使用的对象的接口。
ConcreteProduct:
具体的Product接口的实现对象。
Creator:
创建器,声明工厂方法,工厂方法通常会返回一个Product类型的实例对象,而且多是抽象方法。也可以在Creator里面提供工厂方法的默认实现,让 工厂方法返回一个缺省的Product类型的实例对象。
ConcreteCreator:
具体的创建器对象,覆盖实现Creator定义的工厂方法,返回具体的Product实例。

2.3  工厂方法模式示例代码

(1)先看看Product的定义,示例代码如下:

/**

* 工厂方法所创建的对象的接口

*/

public interface Product {

//可以定义Product的属性和方法

}

(2)再看看具体的Product的实现对象,示例代码如下:

/**

* 具体的Product对象

*/

public class ConcreteProduct implements Product {

//实现Product要求的方法

}

(3)接下来看看创建器的定义,示例代码如下:

/**

* 创建器,声明工厂方法

*/

public abstract class Creator {

/**

* 创建Product的工厂方法

* @return Product对象

*/

protected abstract Product factoryMethod();

/**

* 示意方法,实现某些功能的方法

*/

public void someOperation() {

//通常在这些方法实现中,需要调用工厂方法来获取Product对象

Product product = factoryMethod();

}

}

(4)再看看具体的创建器实现对象,示例代码如下:

/**

* 具体的创建器实现对象

*/

public class ConcreteCreator extends Creator {

protected Product factoryMethod() {

//重定义工厂方法,返回一个具体的Product对象

return new ConcreteProduct();

}

}

2.4  使用工厂方法模式来实现示例

要使用工厂方法模式来实现示例,先来按照工厂方法模式的结构,对应出哪些是被创建的Product,哪些是Creator。分析要求实现的功能,导出的文 件对象接口ExportFileApi就相当于是Product,而用来实现导出数据的业务功能对象就相当于Creator。把Product和 Creator分开过后,就可以分别来实现它们了。
使用工厂模式来实现示例的程序结构如图4所示:

图4  使用工厂模式来实现示例的程序结构示意图
下面一起来看看代码实现。
(1)导出的文件对象接口ExportFileApi的实 现没有变化,这里就不去赘述了
(2)接下来看看接口ExportFileApi的实现,为了示例简单,只实现导出文本文件格式和数据库备份文件两 种。先看看导出文本文件格式的实现,示例代码如下:

/**

* 导出成文本文件格式的对象

*/

public class ExportTxtFile implements ExportFileApi{

public boolean export(String data) {

//简单示意一下,这里需要操作文件

System.out.println(“导出数据”+data+”到文本文件”);

return true;

}

}

再看看导出成数据库备份文件形式的对象的实现,示例代码如下:

/**

* 导出成数据库备份文件形式的对象

*/

public class ExportDB implements ExportFileApi{

public boolean export(String data) {

//简单示意一下,这里需要操作数据库和文件

System.out.println(“导出数据”+data+”到数据库备份文 件”);

return true;

}

}

(3)Creator这边的实现,首先看看ExportOperate的实现,示例代码如下:

/**

* 实现导出数据的业务功能对象

*/

public abstract class ExportOperate {

/**

* 导出文件

* @param data 需要保存的数据

* @return 是否成功导出文件

*/

public boolean export(String data){

//使用工厂方法

ExportFileApi api = factoryMethod();

return api.export(data);

}

/**

* 工厂方法,创建导出的文件对象的接口对象

* @return 导出的文件对象的接口对象

*/

protected abstract ExportFileApi factoryMethod();

}

(4)加入了两个Creator实现,先看看创建导出成文本文件格式的对象,示例代码如下:

/**

* 具体的创建器实现对象,实现创建导出成文本文件格式的对象

*/

public class ExportTxtFileOperate extends ExportOperate{

protected ExportFileApi factoryMethod() {

//创建导出成文本文件格式的对象

return new ExportTxtFile();

}

}

再看看创建导出成数据库备份文件形式的对象,示例代码如下:

/**

* 具体的创建器实现对象,实现创建导出成数据库备份文件形式的对象

*/

public class ExportDBOperate extends ExportOperate{

protected ExportFileApi factoryMethod() {

//创建导出成数据库备份文件形式的对象

return new ExportDB();

}

}

(5)客户端直接创建需要使用的Creator对象,然后调用相应的功能方法,示例代码如下:

public class Client {

public static void main(String[] args) {

//创建需要使用的Creator对象

ExportOperate operate = new ExportDBOperate();

//调用输出数据的功能方法

operate.export(“测试数据”);

}

}

运行结果如下:

导出数据测试数据到数据库备份文件

你还可以修改客户端new的对象,切换成其它的实现对象,试试看会发生什么。看来应用工厂方法模式是很简单的,对吧。

未完待续……

[转载]Spring.NET企业架构实践之 Nhibernate + WCF + ASP.NET MVC + NVelocity 对PetShop4.0重构(二)——领域模型

mikel阅读(938)

[转载]Spring.NET企业架构实践之 Nhibernate + WCF + ASP.NET MVC + NVelocity 对PetShop4.0重构(二)——领域模型 – 刘冬的博客 – 博客园.

什么是领域模型?领域模型是对领域内的概念类或现实世界中对象的可视化表示。又称概念模型、领域对象模型、分析对象模型。它专注于分析问题领域 本身,发掘重要的业务领域概念,并建立业务领域概念之间的关系。

当我们不再对一个新系统进行数据库提炼时,取而代之的时面向对象的模型提炼。我们必须大刀阔斧地对业务领域进行细分,将一个复杂的业务领域划分 为多个小的子领域,同时还必须分清重点和次要部分,抓住核心领域概念,实现重点突破。

著名建模专家Eric Evans提出了Domain Driven Design(领域驱动设)。最初层次只分为三层:表现层、业务层和持久层,DDD其实告诉我们如何让实现业务层。

领域模型种类

传统模型分为两种:实体(Entity)和值对象(Value Object),服务(Service)成为第三种模型元素。

领域驱动设计有两种常用的模式:贫血模式充血模式

贫血模式:只有状态,没有行为。

贫血模型

public class OrderInfo
{
public virtual int OrderId { get; set; }
public virtual DateTime Date { get; set; }
public virtual string UserId { get; set; }
public virtual decimal OrderTotal { get; set; }
public virtual IList<LineItemInfo> LineItems { get; set; }
public virtual int? AuthorizationNumber { get; set; }
}
}

Service

public class OrderManager
{
public IOrderDao CurrentDao { get; set; }

public object Save(OrderInfo entity)
{
ProcessCreditCard(entity);
return CurrentDao.Save(entity);
}

private void ProcessCreditCard(OrderInfo entity)
{
Random rnd
= new Random();
entity.AuthorizationNumber
= (int)(rnd.NextDouble() * int.MaxValue);
}
}

从上面贫血模式的Domain Object可看出,其类代码中只有属性,这种Domain Object只是单纯的数据载体。虽然它的名字是Domain Object,却没有包含任何业务对象的相关方法。这样,方法都写在服务层(Service)中,会使得层次更加明显。但随着业务方法增多,会使服务层中 的代码过于臃肿,从而难以维护。

充血模式:既有状态,又有行为。

充血模型

public class OrderInfo
{
public virtual int OrderId { get; set; }
public virtual DateTime Date { get; set; }
public virtual string UserId { get; set; }
public virtual decimal OrderTotal { get; set; }
public virtual IList<LineItemInfo> LineItems { get; set; }
public virtual int? AuthorizationNumber { get; set; }

public void ProcessCreditCard()
{
Random rnd
= new Random();
this.AuthorizationNumber = (int)(rnd.NextDouble() * int.MaxValue);
}
}

从充血模型的代码中可以看出,其类既有属性又有方法,服务层(Service)的代 码比较少,相当于门面。方法和属性的混合编码方式,在被多个业务类调用的情况下,代码的内聚性比较好。假设AManager和BManager都使用到了 OrderInfo,ProcessCreditCard方法只在OrderInfo中写了一次就足够了。这样,职责性就更加明显。

这两种模型都是稳定的,至于用哪种模型,还是需要根据具体需求来断定。在大多 数.NET项目中使用贫血模型要比使用充血模型的情况多。

领域模型的对应关系一般有三 种:一对一,一对多(多对一),多对多。

一对多

public class OrderInfo
{
public virtual int? OrderId { get; set; }
public virtual DateTime Date { get; set; }
public virtual string UserId { get; set; }
public virtual decimal OrderTotal { get; set; }
public virtual IList<LineItemInfo> LineItems { get; set; }
public virtual int? AuthorizationNumber { get; set; }
}

public class LineItemInfo
{
public virtual string Id { get; set; }
public virtual string Name { get; set; }
public virtual int Quantity { get; set; }
public virtual decimal Price { get; set; }
public virtual string ProductName { get; set; }
public virtual string Image { get; set; }
public virtual string CategoryId { get; set; }
public virtual string ProductId { get; set; }
public virtual OrderInfo Order { get; set; }
}

OrderInfo和 LineItemInfo是一对多的关系。在NHibernate的 双向外键中,实体之间是循环引用的,所以不能直接序列化。作为每个实体来说他们都是POJO(脱离框架一样能使用),在更换ORM框 架的情况下一样能够使用他们。

“一对多(多对一)”一般用于集合外键的ORM描 述。“多对多”的关系一般需要一个外键关系表。“一对一”的关系一般用于继承映射或者用于把一个多字段的表拆分成多个辅助表。

NHibernate的 映射配置有两种方式,一种是在单独的.hbm.xml文件中配置映射关系,另一种是使用NHibernate.Mapping.Attributes的方 式标注映射关系。后者省略了很多繁琐的配置文件,但需要应用了NHibernate.Mapping.Attributes程序集。前者有大量的配置文 件,但是不需要引用其他程序集,并且在脱离ORM框架下也能够使用。所以我个人倾向于前者。

OrderInfo.hbm.xml

<?xml version=”1.0″ encoding=”utf-8″ ?>

<hibernate-mapping xmlns=”urn:nhibernate-mapping-2.2″ assembly=”PetShopOrder.Domain” namespace=”PetShopOrder.Domain”>
<class name=”PetShopOrder.Domain.OrderInfo, PetShopOrder.Domain” table=”Orders” lazy=”true” >

<id name=”OrderId” column=”OrderId” type=”Int32″ >
<generator class=”native” />
</id>

<property name=”Date” type=”DateTime”>
<column name=”OrderDate” not-null=”true”></column>
</property>

<property name=”UserId” type=”String”>
<column name=”UserId” length=”20″ not-null=”true”></column>
</property>

<property name=”AuthorizationNumber” type=”Int32″>
<column name=”AuthorizationNumber” not-null=”false”></column>
</property>

<property name=”OrderTotal” type=”Decimal”>
<column name=”TotalPrice” precision=”10″ scale=”2″ not-null=”true”></column>
</property>

<bag name=”LineItems” inverse=”true” lazy=”true” generic=”true” cascade=”all” table=”LineItem”>
<key column=”OrderID”/>
<one-to-many class=”PetShopOrder.Domain.LineItemInfo, PetShopOrder.Domain” />
</bag>

</class>
</hibernate-mapping>

在持久层中每个实体模型都对应了数据库中的一 个表,每个属性都对应了表中的一个字段,每个实体对象对应了表中的一条记录。

在服务层中,需要得到的模型对象往往和持久层 的实体模型不一致,如某个类中有属性:数量,单价和金额,但是数据库中只有数量和单价。这时候需要建立一种模型——业务模型。然而Linq和 匿名类的出现缓解了这一点。在门面层调用服务层返回DTO对象的过程中,通过Linq查询实体模型来方式返回DTO。这样业务模型就能够被省略(后面的文 章会谈到这一点)。

最后在设计领域模型中我们需要分清“主次”。当设计进销存系统,业务类数据就数主要的,如采购,销售,库存信息。基础资料数据就是次要的,如供 应商和客户信息。当设计客户关系管理系统时,客户资料数据则是主要的,围绕的客户产生的活动,社交等数据则是辅助数据。这就产生一个规律:主要数据的变化 频率比较高,辅助数据变化频率比较低。在PetShop4.0中, 主要数据当然是订单。这样我们就需要想方设法去优化系统,以便于更好的处理订单数据。

出处http://www.cnblogs.com/GoodHelper/archive/2010/06/18/SpringNet_PetShop4_2.html

欢迎转载,但需保留版权。

[转载]asp.net 下FckEditor上传文件中文乱码的解决办法

mikel阅读(994)

[转载]asp.net 下FckEditor上传文件中文乱码的解决办法 – 浪子.蔺文龙的博客 – 博客园.

使用FckEditor上传文件一直都很正常,但后来修改了Fck的核心,增加了上传成功后,将上传文件的相关信息写入数据库

发现中文文件名的文件上传不正常(偶尔不成功)后来终于发现是中文乱码引起的(中文乱码是之前就发现了,但没有发现影响取文件后缀)

提到中文乱码,首先肯定是由于编码问题引起的

所以就从编码转换入手,尝试了将UTF-8转换为GB2312,但发现无论如何没有办法转成功

看到很多文章说修改配置文件

<globalization requestEncoding=”UTF-8″ responseEncoding=”UTF-8″/>这样是挺Easy的,但是其他页面就是乱套,因为是其他页面都是基于GB2312的

这里突然灵光一闪,由于我的FCK是单独是一个目录的,我给这个目录指定一个专用的web.config,指定编码为utf-8不就完整了嘛,一试 果然有效,顿时一身轻构了!

发出来与大家分享之。

这里贴出一个比较干净的web.config的全部内容:

web.config

<?xml version=”1.0″ encoding=”utf-8″ ?>
<configuration>

<system.web>

<compilation defaultLanguage=”C# Debug=”true” />

<authorization>
<allow users=”*” />
</authorization>

<trace enabled=”false” requestLimit=”10″ pageOutput=”false” traceMode=”SortByTime” localOnly=”true” />

<customErrors mode=”Off” />

<globalization requestEncoding=”utf-8″ responseEncoding=”utf-8″ fileEncoding=”utf-8″ />

<pages validateRequest=”false” enableEventValidation=”false” enableViewStateMac=”false” viewStateEncryptionMode =”Never” />

<xhtmlConformance mode=”Legacy”/>

<httpRuntime maxRequestLength=”2097151″ executionTimeout=”3600″/>
</system.web>

</configuration>

[转载]Spring.NET企业架构实践之 Nhibernate + WCF + ASP.NET MVC + NVelocity 对PetShop4.0重构(一)

mikel阅读(1083)

[转载]Spring.NET企业架构实践之 Nhibernate + WCF + ASP.NET MVC + NVelocity 对PetShop4.0重构(一)——架构设计 – 刘冬的博客 – 博客园.

PetShop4.0是微软针对.NET企业系统推出的一个范例。业界有许多.NET与J2EE之争,许多数据 是从微软的PetShop和Sun的PetStore而来。这种争论不可避免带有浓厚的商业色彩,对于我们开发人员而言,没有必要过多关注。然而 PetShop随着版本的不断更新,至现在基于.Net 2.0的PetShop4.0为止,整个设计逐渐变得成熟而优雅,而且有很多可以借鉴之处。PetShop是一个小型的项目,系统架构与代码都比较简单, 却也凸现了许多颇有价值的设计与开发理念。

然而PetShop4.0存在很多争议,我想园子里一定有很多PetShop4.0的 “粉丝”,重构PetShop4.0会引发很多争议,我希望园子里的朋友以“交流技术”的心态来看待此问题。对于“重构”,我 只是以我个人的观点阐述了PetShop4.0一些不合理的地方,并加以“修改”,同时引入了一些我“片面”的架构设计思想。

我个人认为PetShop4.0存在以下的弊端:

1、入门级别的架构,不完全适于中、高级开发人员学习。

PetShop4.0作为.NET三层的一种入门型架构。目前据我了解,大多数公司的架构模式都采用或者效仿PetShop4.0。对此我个人认为:作为.NET开发人员来说,这样并没有完全理解分层的真正意义,照搬PetShop4.0,而没有真正灵活应用PetShop4.0。 我想,针对真正的大型项目,在扩展性,重用性,负载均衡上,PetShop4.0是 很吃力的。对于服务器集群的分布式的应用来说是个空白。

2、错误的引导程序员对架构的深入了解。

很多.NET开发人员习惯认为:学会PetShop4.0以 后就学会了大多数公司的架构。对此我个人认为这是.NE开发人员的悲哀。目前可以这么说,PetShop4.0影 响了一代.NET程序员的架构思路,并把这代程序员的设计思路给限定“死”了。习惯性认为架构就是“DAL,BLL,UI”。我想,这样就会阻碍出架构设 计。我认为PetShop4.0仅仅是一个“特列”,而不是一种通用型架构。

3、移植性和重用性偏弱。

对于SQLServerDAL和OracleDAL来说,在实际中增加第三种数据库就需要再写一个DAL,这 样会增加我们的开发成本,我个人建议使用ORM框架来实现比较恰当。因为这样便于数据库的移植。在持久层中,基本上每个表都需要对应的CRUD,建议使用 Repository将代码内聚起来。PetShop4.0SQL语句是写在类里的,这一点我比较反对,我倾向于把SQL语句写在配置文件或者模板文件里(如:ibatis.net),这样看上去会更灵活。

4、仅适用于展示.NET2.0的特性,在NET3.5以上环境却失去了优势。

PetShop4.0发布已经有好几年了,在新技术层出不穷的时代。PetShop4.0对 于AJAX,Web SericveWCFASP.NET MVC的支持略有欠缺。对于ORMIoCAOP等 编程思想的概括几乎为空白。

考虑到以上的问题,我个人准备对PetShop4.0进行重构,以便新技术发展的需要。

———————————————————————————————————————————

图1是重构后的架构图:

(图1)

从图1中能够看出,该架构的持久层选择了一种ORM框 架。这样能够便于数据库的移植,除了能兼容SQL Server和Oralce以 外,还支持更多的数据库。而并非针对每种数据库来写一个DAL。

对于数据库的事务来说,该架构采用配制的方式来实现数据库的“自动事务策略”。这样能减少我们的代码量和降低开发成本。

作为一个子系统的“门面”部分,相当外部系统来说,封装了数据库持久层(DAO)和服务器层(Service)。外部系统不关心其子系统内部的 具体实现,而是通过“契约(Contract)”来交互。

———————————————————————————————————————-

让我们看一下原PetShop4.0的 数据库:这里有3个数据库。当服务器负载过重时,通常不会把这3个库部署到同一台服务器上,而是分别部署到不同服务器上。接下来,对于同一个持久层访问3 个数据库来说也是不合理的。这样,我能把每个数据库看做一个子系统,通过分布式技术将这3个子系统连接起来,从而分摊压力和降低子系统与子系统之间的耦合 程度。我个人认为,在设计体统架构体系中,我们应该控制子系统之间的耦合程序。在某个子系统中引用了其他“平级”的子系统,这样的做法并非是很明智的。

图2是重构后服务于服务之间的示意图:

(图2)

上述三个子系统相对是“平级”的,它们之间不应该发生耦合。客户端能够同时访问这三个子系统;或者增加一个二级服务,让二级服务访问这三个子系统, 客户端只依赖二级服务,而不是直接依赖三个子系统,就相当于这个“二级”服务是这三个子系统的门面。根据不同的需要,客户端可以是一个,也可以是多个,管 理人员使用一个客户端,客户使用另一个客户端。

出处http://www.cnblogs.com/GoodHelper/archive/2009/11/20/SpringNet_PetShop4_1.html

欢迎转载,但需保留版权。

[转载]CodeSmith和PowerDesigner的安装和数据库创建

mikel阅读(1107)

[转载]CodeSmith和PowerDesigner的安装和数据库创建(原创) – 批发の上帝ぐ – 博客园.

最近要忙期考,但还是决定每天抽点空来写CodeSmith的系列文章了,在此实在不敢用教程这个词语,毕竟自己对 CodeSmith了解的也不是很多,有很多牛人都在博客园发布了不少关于CodeSmith的文章了,但我还是想发表一下自己的心得,希望能给初学者更 多的参考,教程中有错误或者表述不清的地方欢迎大家指出,我还是会秉承之前的风格,用尽量多的图来说明步骤,因为我觉得还是图文并茂的文章比较容易理解.

好了,废话少说,开始我们的CodeSmith旅程吧,我先讲讲这个系列教程要完成的目标吧,众所周 知,CodeSmith其中一个强大的功能就是依照模板生成批量代码,这也是吸引着众多编程人士使用它的原因,它大大的节约了编程时间,省去了大量复制粘 贴的废操作.

首先,既然要讲解如何使用CodeSmith和PowerDesigner快速生成批量代码,当然要先安装这2个软件 啦,下面就简单说说如何安装破解这2款软件吧,当然破解只是学习之用,请大家不要用于商业用途哈,要支持正版,大家都是做软件的,知道开发一套软件实属不 易啊.

CodeSmith安装方法: 1). 点击下载安装文件 2). 运行,安装步骤就不说了,选择你要安装的目录,一路下一步就OK了. 3). 运行,选择CodeSmith的安装目录,一路下一步,就破解 完成啦,接着你就可以在开始--CodeSmith Professional 5.1--CodeSmith Studio运行程序啦. PowerDesigner安装方法:  http://dev.firnow.com/course/3_program/java/javajs/20090908/174375.html

安装完这2个软件了,接着就开始使用吧,还是和以前一样,先提出需求,再做实例,因为我们之所以使用它们,肯定是因为它 们能巧妙的解决某种问题,不然我们干嘛花那么多时间和硬盘空间安装它们.

好,我们现在的需求是:希望当接到一个新的工程项目时,程序员只需要根据项目需求,设计出数据库的结构(表,表的字段, 表间关系),之后只要按一个按键,就能立刻得到基于该数据库的所有分层架构代码文件.也就是说,我们希望能在不敲一行 代码的情况下获取所有我们想要的代码!而这就是这2个软件能做到的事情,也是我们安装它们的目的.

好了,角色已经介绍完了,接着看看它们是如何演绎这场精彩的演出的吧:

首先我们需要创建一个测试数据库,为了简单,我们在这个数据库中只创建一个Student和 一个Major.其表结构和关系如下所示.

看看怎样用PowerDesigner快速的创建出这个数据库吧.

1.现在开始使用PowerDesigner创建数据库,首先运行程序,进入主界面:

2.File—New Model—Physical Data Model—Physical Diagram—Model name 设置为test,DBMS属性设置为Microsoft SQL Server 2005:

3.首先用表格工具创建一个表格模板:

4.双击表格模板,设置属性,我们首先设置Major表:

5.设置好表名,点击Columns标签,设置字段属性,设置如图所示:

6.因为MajorID字段我们要设置为自动增长,所以要设置它的高级属性,选择MajorID字段,点 击属性按钮,在General面板中勾选上Identity复选框:

7.确定后我们再创建一个Student表,字段设置如图所示:

8.接着是为Student创建一个MajorID外键,使用PowerDesigner可以很轻松的完成这个工作,选 择关系设置工具,在Student表上按住左键不放,拖拽至Major表,便可为Student表添加一个MajorID的外键:

9.哈哈,现在测试表已经设置好了,接着设置一下我们要生成的数据库吧,这些表都将被创建到该数据库中,我们在设计面板空白处右键—Properties, 在弹出的属性设置对话框设置如下:

10好了,在此我们对新数据库的设置已经完成,但是在SQL中还是空空如也啊,我们要怎么把这边设计好的结构移植到 SQLServer 2005中呢?执行操作:Database—Generate Database,设置好存储过程导出目录和文件名, 点击确定即可:

11.到你的导出目录,就可以看见导出的数据库创建存储过程了,打开SQL,执行一下,你就会看到数据库被神奇的创建好了:

12.好了,数据库的准备工作做好了,下一篇我们就将使用该数据库结合CodeSmith自动批量生成代码啦~

上述实践中生成的源代码:

SQL.sql

/*==============================================================*/ /* Database name: PD_test */ /* DBMS name: Microsoft SQL Server 2005 */ /* Created on: 2010/6/13 星期日 17:27:17 */ /*==============================================================*/ drop database PD_test go /*==============================================================*/ /* Database: PD_test */ /*==============================================================*/ create database PD_test go use PD_test go /*==============================================================*/ /* Table: Major */ /*==============================================================*/ create table Major ( MajorID int identity, Name nvarchar(20) not null, Remark nvarchar(Max) null, constraint PK_MAJOR primary key (MajorID) ) go /*==============================================================*/ /* Table: Student */ /*==============================================================*/ create table Student ( StudentID nvarchar(20) not null, MajorID int null, Name nvarchar(20) not null, Sex bit not null, Age int null, Remark nvarchar(Max) null, constraint PK_STUDENT primary key (StudentID) ) go alter table Student add constraint FK_STUDENT_REFERENCE_MAJOR foreign key (MajorID) references Major (MajorID) go

出处:http://www.cnblogs.com/huangcong/archive/2010/06/13/1757957.html

本文版权归作 者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

[转载]满园尽是503,记曾经的一次IIS 7性能考验

mikel阅读(1111)

[转载]满园尽是503,记曾经的一次IIS 7性能考验 – dudu – 博客园.

在一个漆黑的夜晚,伸手不见五指,一个黑衣人偷偷潜入博客园的图片库,放了一些图片,然后迅速消失在黑幕中…

在人山人海的淘宝网,也出现了一个黑衣人,在淘宝网的产品库中,将一些产品展示页面中的素材图片指向了博客园图片库中的图片…

史上最强的一次风暴席卷整个博客园,巨量的访问请求涌向博客园,访问速度越来越慢,然后就是“满园尽是503”。

IIS 7的同时连接达到了设置的上限20000,上限改为3万,同时连接立即达到3万,改为5万,同时连接立即会达到3万…

开始以为是拒绝服务攻击(DDOS),后来发现是“黑衣人”上传的图片,淘宝网上很多页面引用了这些图片…

找到了原因,原以为可以轻松地解决问题,将这些图片移走就会风平浪静,雨过天晴。哪知,图片不在,503依旧…

这样都不行,几乎让人崩溃了。图片请求是由IIS7的StaticFileModule(位于%windir%\System32\inetsrv \static.dll)处理的,虽然图片不存在,但对于这么大的请求,IIS 7仅仅是接受请求,检查文件是否存在,然后返回404这样的处理都顶不住。只要淘宝上的图片引用存在,503将一直继续,难道只有联系淘宝处理这些图片引 用。即使淘宝处理了图片引用,但以后只要谁在访问量大的网站放一个不存在的指向博客园的链接,IIS还是处理不了。根本的解决办法还是要能应对这样的巨量 请求。

这时,博客园团队中有一个人急中生智,说试试通过ISAPI_Rewrite对 图片请求进行重定向。当时想连微软自己的IIS7都处理不了,难道由第三方写的ISAPI Filter能处理。不管怎么样,试试这个方法吧。不试不知道,试了吓一跳,在ISAPI_Rewrite的http.ini中写了重定向规则(重定向至 淘宝网站)之后,奇迹出现了,问题解决了。

好了,这篇随笔目的就是把博客园实际遇到过的一个问题与大家分享,希望能给大家带来一点收获与思考。