一步一步开发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 }

当我线程里面第一次添加定时器任务的时候加触发定时器线程的初始化。

先看看效果

地图运作方式怎么样的呢?

来一张图片看看

在正常情况下一个地图需要这些事情。然后大部分事情是需要定时器任务处理的,只有客户端交互通信是不需要定时器任务处理。

封装地图信息类

  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 }

   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("我是地图心跳检查器 执行线程:" + System.Threading.Thread.CurrentThread.Name);
 47             Logger.Debug("我是地图心跳检查器 检查玩家是否需要复活,回血,状态");
 48             //var scripts = Sz.ScriptPool.ScriptManager.Instance.GetInstances<IMapHeartTimerScript>();
 49             //foreach (var item in scripts)
 50             //{
 51             //    item.Run(serverID, lineID, mapID, mapModelID);
 52             //}
 53         }
 54
 55     }
 56 }
 57
 58
 59
 60
 61
 62 using System;
 63 using System.Collections.Generic;
 64 using System.Linq;
 65 using System.Text;
 66 using System.Threading.Tasks;
 67 using Sz.MMO.GameServer.IMonsterScripts;
 68
 69
 70 /**
 71  *
 72  * @author 失足程序员
 73  * @Blog http://www.cnblogs.com/ty408/
 74  * @mail [email protected]
 75  * @phone 13882122019
 76  *
 77  */
 78 namespace Sz.MMO.GameServer.TimerMonster
 79 {
 80     /// <summary>
 81     ///
 82     /// </summary>
 83     public class MonsterHeartTimer: ThreadPool.TimerTask
 84     {
 85
 86         int serverID, lineID, mapModelID;
 87         long mapID;
 88
 89         /// <summary>
 90         /// 指定1秒执行一次
 91         /// </summary>
 92         public MonsterHeartTimer(int serverID, int lineID, long mapID, int mapModelID)
 93             : base(1000 * 10)
 94         {
 95             this.serverID = serverID;
 96             this.lineID = lineID;
 97             this.mapID = mapID;
 98             this.mapModelID = mapModelID;
 99         }
100
101         /// <summary>
102         ///
103         /// </summary>
104         public override void Run()
105         {
106             Logger.Debug("怪物心跳检查器 执行线程:" + System.Threading.Thread.CurrentThread.Name);
107             Logger.Debug("怪物心跳检查器 检查怪物是否需要复活,需要回血,是否回跑");
108             //var scripts = Sz.ScriptPool.ScriptManager.Instance.GetInstances<IMonsterHeartTimerScript>();
109             //foreach (var item in scripts)
110             //{
111             //    item.Run(serverID, lineID, mapID, mapModelID);
112             //}
113         }
114     }
115 }
116
117
118
119 using System;
120 using System.Collections.Generic;
121 using System.Linq;
122 using System.Text;
123 using System.Threading.Tasks;
124
125
126 /**
127  *
128  * @author 失足程序员
129  * @Blog http://www.cnblogs.com/ty408/
130  * @mail [email protected]
131  * @phone 13882122019
132  *
133  */
134 namespace Sz.MMO.GameServer.TimerMonster
135 {
136     /// <summary>
137     ///
138     /// </summary>
139     public class MonsterRunTimer: ThreadPool.TimerTask
140     {
141
142         int serverID, lineID, mapModelID;
143         long mapID;
144
145         /// <summary>
146         /// 指定1秒执行一次
147         /// </summary>
148         public MonsterRunTimer(int serverID, int lineID, long mapID, int mapModelID)
149             : base(1000 * 5)
150         {
151             this.serverID = serverID;
152             this.lineID = lineID;
153             this.mapID = mapID;
154             this.mapModelID = mapModelID;
155         }
156
157         /// <summary>
158         ///
159         /// </summary>
160         public override void Run()
161         {
162             Logger.Debug("怪物移动定时器任务 执行线程:" + System.Threading.Thread.CurrentThread.Name);
163             Logger.Debug("怪物移动定时器任务 怪物随机移动和回跑");
164             //var scripts = Sz.ScriptPool.ScriptManager.Instance.GetInstances<IMonsterHeartTimerScript>();
165             //foreach (var item in scripts)
166             //{
167             //    item.Run(serverID, lineID, mapID, mapModelID);
168             //}
169         }
170     }
171 }

就在初始化地图线程的时候加入定时器任务

 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,你的地图能容纳多少怪物,多少玩家,多少掉落物?换句话说也就是你设计的复杂度间接限制了你的地图有多少场景对象。

那么还有什么需要注意的呢?

其实地图最大的消耗在于寻路。高性能的寻路算法和人性化寻路算法一直是大神研究的对象,我也只能是借鉴他们的了。

这一章我只是简单的阐述了地图运行和任务等划分和构成已经任务处理流程。

接下来我会继续讲解游戏服务器编程,一步一步的剖析。

文路不是很清晰。希望大家不要见怪。

时间: 2024-10-11 01:47:02

一步一步开发Game服务器(四)地图线程的相关文章

一步一步开发Game服务器(三)加载脚本和服务器热更新(二)完整版

上一篇文章我介绍了如果动态加载dll文件来更新程序 一步一步开发Game服务器(三)加载脚本和服务器热更新 可是在使用过程中,也许有很多会发现,动态加载dll其实不方便,应为需要预先编译代码为dll文件.便利性不是很高. 那么有么有办法能做到动态实时更新呢???? 官方提供了这两个对象,动态编译源文件. 提供对 C# 代码生成器和代码编译器的实例的访问. CSharpCodeProvider 提供一下方法加载源文件, // 基于包含在 System.CodeDom.CodeCompileUnit

一步一步开发Game服务器(一)

什么是服务器?对于很多人来说也许只是简单成为在服务器端运行的程序的确如此,服务器通常意义就是说在服务器端运行的程序而已.那么我们怎么理解和分析游戏服务器哪? 传统意义上来说,程序运行后,正常流程, 启动 -> 加载必要数据 -> 分析必要数据 -> 接受连接 -> 登陆系统 -> 交换数据 -> 退出登陆. 这样单线程服务器的思路就算完成.有经验的同学都可以看得出这是最简单的程序运行流程,有一个最主要的问题就是这是一个单线程的流程,承载量自然就不大,吞吐量也就自然下降了

(转) 一步一步学习ASP.NET 5 (四)- ASP.NET MVC 6四大特性

转发:微软MVP 卢建晖 的文章,希望对大家有帮助.原文:http://blog.csdn.net/kinfey/article/details/44459625 编者语 : 昨晚写好的文章居然csdn不审核,这个也难怪人,但自己比较忙没办法.分享继续,今天谈ASP.NET MVC 6. 我蛮喜欢Ruby On Rails 这种约定胜于配置的框架,在.NET 有ASP.NET MVC 和Java有Play! Framework .  ASP.NET MVC 版本基本上每年一更新,从不让你失望.我

C#WPF 语音开发教程 源代码下载 csdn tts(text to sound) 一步一步 教你制作语音软件 附图和源代码

C#WPF  语音开发教程  一步一步 教你制作语音软件 附图和源代码 效果展示 一 项目准备 1.vs2012开发平台 2.微软的语音软件库 下载:http://download.csdn.net/detail/wyx100/8431269 (含实例项目源代码) 二.开发目标 制作一个语音软件,可以朗读文字: 多个语音库:男音和女音.支持英文和中文朗读: 支持选择播放设备 支持朗读语速选择 支持音量选择 三 开发过程 1.新建WpfSpeechDemo工程 文件(vs开发平台左上角)----新

跟我一步一步开发自己的Openfire插件

http://www.blogjava.net/hoojo/archive/2013/03/07/396146.html 跟我一步一步开发自己的Openfire插件 这篇是简单插件开发,下篇聊天记录插件. 开发环境: System:Windows WebBrowser:IE6+.Firefox3+ JavaEE Server:tomcat5.0.2.8.tomcat6 IDE:eclipse.MyEclipse 8 开发依赖库: Jdk1.6.jasper-compiler.jar.jasper

一步一步构建手机WebApp开发——环境搭建篇

从2007年,乔布斯带来了第一代Iphone手机,整个移动互联网发生天翻地覆的变化,也同时证明了乔布斯的一句名言:“再一次改变世界”. 在当今的移动互联网,手机App居多,很多App对移动设备的要求也越来越高,当然,土豪就可以经常更新换代.我们这群屌丝只能望梅止渴.为了解决少部分由于硬件或者软件引起的问题,我们将App迁移到浏览器上,也就是人们常说的WebApp,WebApp相对与手机App客户端有以下优点: 1.免安装,一个浏览器就可以搞定. 2.不需要繁忙的迭代更新. 3.不需要担心存储不足

一步一步构建手机WebApp开发——页面布局篇

继上一篇:一步一步构建手机WebApp开发——环境搭建篇过后,我相信很多朋友都想看看实战案例,这一次的教程是页面布局篇,先上图: 如上图所示,此篇教程便是教初学者如何快速布局这样的页面.废话少说,直接上代码 注意:此教程是接上一篇教程,也就是所有的内容是直接从body开始写,当然,也会贴出所有代码给大家. 第一步:框架的布局及幻灯片的布局(Html) ① 如上图所示,我们应该准备以下容器,方便填充内容 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

一步一步构建手机Web开发——环境搭建篇

从2007年,乔布斯带来了第一代Iphone手机,整个移动互联网发生天翻地覆的变化,也同时证明了乔布斯的一句名言:“再一次改变世界”. 在当今的移动互联网,手机App居多,很多App对移动设备的要求也越来越高,当然,土豪就可以经常更新换代.我们这群屌丝只能望梅止渴.为了解决少部分由于硬件或者软件引起的问题,我们将App迁移到浏览器上,也就是人们常说的WebApp,WebApp相对与手机App客户端有以下优点: 1.免安装,一个浏览器就可以搞定. 2.不需要繁忙的迭代更新. 3.不需要担心存储不足

一步一步学会puppet(四)--master/agent模型

这篇博文主要介绍puppet在实际生产环境下的master/agent模型的使用: =================================================================== 1 原理介绍 1.1 原理图 1.2 详细说明 2 配置实例 2.0 准备 2.1 master配置 2.2 agent配置 2.3 master签署证书 2.4 agent一次完整的同步案例 ===========================================