Where there is a dream ,there is hope

  C++博客 :: 首页 :: 联系 :: 聚合  :: 管理
  64 Posts :: 0 Stories :: 8 Comments :: 0 Trackbacks

常用链接

留言簿(1)

我参与的团队

搜索

  •  

最新评论

阅读排行榜

评论排行榜

2011年11月6日 #

首先声明:忙,保证持续更新不保证结束时间,估计一周左右写完,忙的话两周。涉及到对创新工场、李开复人品、移动互联网等的看法代表我魏小康的个人看法,版权所有,转载请务必注明。 
  
大纲: 
一、缘由、概述 
二、创新工场的模式 
三、职业发展道路的影响因素 
四、职业选择的几个小问题 
五、李开复的移动互联网和我眼中的移动互联网 
六、再见和祝福 
  
一、缘由、概述 
1、缘由     
     前两周,有个师弟咨询我个offer选择——360产品经理offer和创新工场某团队产品经理。他说面试的时候,创新工场某面试官说在360做产品经理不如在创新工场,360那边产品团队XX不专业,创新工场这边产品团队XX牛B,劝说这人推掉360的offer接创新工场的offer。 
     我当时听了那叫一个汗阿——能说出这样话的人要么特别无知要么特别无耻。别的不说,就冲360可以监控数亿网民上网习惯这点也该去360阿。何况,360产品经理做出的产品有多少人用,你创新工场有多少人用。 
    最近估计是创新工场开始发offer了,不断有人咨询我这家公司,加上有些人咨询我另外的offer比较问题。我本来是懒得写长文的,只是最近看到李开复的一些过于XX的言论,再加上前几天听说了创新工场忽悠师弟师妹们签约而说一些极为荒唐的理由,实在看不下了。 
     so,写一篇吧。     
  
2、概述 
     我并不是反对大家去创新工场, 如果你生下来就是做老板的(不是自认为,而是你在学校的时候已经有不少成功的创业经验),而且你很聪明,很勤奋,人格没啥短板,你可以考虑去,也只有这种人在创新工场才有前途,创新工场也只适合这种人。 
     如果你以后的职业目标是个行业专家或者是企业中高级管理层,那么你还是老老实实找个发展较为迅速的企业卖命干着。现在互联网发展这么快,你去搜狗、360的核心部门,乃至美团、去哪儿、开心等未来成功的概率都比去创新工场大。 
     如果你的职业目标是赚的差不多就行了,比其他人强一些,别太累,收入中上,那你找个百度、腾讯等成熟部门安安心心做个螺丝钉就可以了。 
  
     对于普通人,什么时候时候适合去创新工场?找实习的时候,正式工作还是算了。 
     我写这片文章,只是想告诉大家,创新工场模式的运转思路,学生加入会有哪些风险,你该如何选择一个offer,移动互联网的泡沫。如果最后有精力就写写offer的比较吧。 
  
二、创新工场的模式,学生加入的问题、学生创业 
(在看之前,请先去了解一下“天使投资人”、“A轮”、“B轮”的大概意思,了解一下天使投资的成功率,了解一下红杉树等大型VC A、B轮的成功率) 
  
1、创新工场的模式 
a.VC的模式 
     红杉树、经纬投资等知名VC,他们一般是在企业有一定的规模,有较为清晰的盈利模式的时候才进入,用较高的钱换取较低的股份和较高的成功率。 
    而天使投资,是在产品刚有个雏形,甚至只有个idea的时候进入,用较少的钱换取较高的股份,承担非常高的失败率,单个项目一旦成功会有很高的收益率。 
  
b.天使投资者创新工场的优势 
天使投资的收益=项目数×成功率×投资回报率—项目数×平均项目成本 
大家可以看到,这个模式的关键点在于: 
(1)、项目多: 
    天使投资人,投的项目成功概率都很低,李开复也绝对不会例外。项目差不多靠谱就上,创新工场拼的就是量; 
(2)、平均成本低: 
    IT团队初期的成本主要是人员、硬件投入、场地以及行政、法律开支等。 
    人员:都创业了,你要什么高工资?给你期权忽悠一下就行。 
    硬件、带宽、场地、行政:这块创新工场集约化了,人越多,项目越多,这块成本越低。 
  
    看到上述模式了?说白了,最关键的就是要量大,至于成功率差不多就行。米聊、UC做那么大,都快被qq放倒了,目前创新工场投的这些项目有什么好顾虑的? 
  
    而李开复在做量这方面拥有着独特的杠杆,他的主要受众就是——学生和入世不深的资源匮乏者。这批人是最需要天使投资人的,这批人也是最容易被忽悠加入创业团队的。因此,他能够帮助:投资人解决项目数量问题,帮助创业者的老板解决掉招聘问题(招聘问题一般是初创团队面临的最大问题)。 
    因此,目前就天使投资的规模来说,创新工场是最大的。这也是为什么李开复的微博一说话,VC们都出来捧场,因为他手里项目多,VC都等着A、B轮的时候跟进的。你还会发现,互联网大佬们相互之间的互动不算少,但是鲜有转发、评论李开复的微博,因为大家就算都知道他放屁,但跟李开复也没仇,李开复PR做的又那么好,惹他干吗?但是如果转了伤自己的RP也不好。 
   
  
     so,创新工场这事,本来也没啥特别的,就是一个大型天使投资机构投资了一大批初创团队。这些创业团队再闪亮,也是一堆初创团队,跟其它创业团队一样,有很多问题,也会有一些机会,也存在很大的风险。但是由于某人的影响力,加上最近移动互联网的泡沫,收益被无限地放大了,风险看起来被缩小了。 
     李开复在招聘的时候到处去忽悠,其实也没啥,哪个公司招人的时候不到处忽悠呢?否则开什么宣讲会?只不过开复同志到处对没有辨别能力的学生去忽悠加入创新工场机会大大、回报高高、风险低低,这实在有点恶心人。 
    对于投资人、创业的老板来说,创新工场不错,投资回报率高,但是对于员工来说,风险高收益低,远逊于加入其它公司。一将功成万骨枯,2、3年过后,李开复成功的时候,谁会记得这些无辜的应届生呢? 
  
下面我说一下加入这样团队的风险。 
——————————————————————————————————————— 
未完,明后天争取 继续二更。 
  
20111103日二更———————————————————————————————————————————————— 
  
三、职业发展道路的影响因素 
  
1、职业规划基本理论 
    先讲两个基本的理论: 
    我自创了一套评价人的体系,大概的意思就是人可以分为四个维度去观察,人格、能力、知识、其它方面(如人际关系的等)。 
    我也自己在摸索一些职业规划的理论,还没完全定型,初步地讲,做职业规划是从:行业、职业、公司、地点等维度去分析。 
    所谓的职业规划,就是选择合适的行业、职业、公司等,帮助自己以最大的概率快速地获得职业的高点。至于如何才能取得职业规划效益的最大化,话题太大,就不讲了,只讲讲“新人入职场”和“为什么不是创新工场”。 
   
2、职业发展的两条道路 
    李开复光说加入创新工场比加入百度、腾讯成功概率高,但是怎么个高法、怎么个锻炼法,没说。我也一直蛮好奇的,如何能让一个人在创新工场成功概率比在百度、腾讯高?我觉得李开复如果能做到,作为百度、腾讯的股东,我会申请发起股东会投票,呼吁李彦宏、化腾下课让贤。 
    在没看到结果之前,我们还是先理论分析一下,单从职业发展角度,如何才能更为成功。做猎头这么多年了,我也大概琢磨出来了——人的最终发展结果无非两种,业务专家 or 管理层. 
     这个跟你是否要创业还是打工无关,你是腾讯的创始人、百度的部门经理、创新工场的项目负责人,你承担的也还是这两类角色。因此,你找工作,要找的就是能帮助你在未来几年内成为这类角色的公司、职位,只要一个公司能帮助你成为业内的专家、管理层,咱就可以坐下来谈offer。管你这家公司是百度、腾讯还是国务院。 
  
     管理层需要的东西我们就不谈了,谈这个对于应届生太扯淡了。如果有人能教会一个应届生如何快速成为高级管理层,我觉得只在一种场合里面有——做传销的,热衷给你讲如何在1、2年内快速成功,快速当上家。 
     我认为想做管理层,没啥捷径,先成为你们小组的小组长再说。见过提拔业绩好的当组长的,没见过提拔业绩差的当组长的公司,so,如何当管理层这事,你可以工作1、2年升了组长后去职场版再讨论。 
    so,新人的你,第一步是得研究如何成为小组内的专家。 
  
3、如何能快速当上专家 
    结合如何成为一个行业知名专家或者公司内的专家这个目标,我们来分析一下职业规划跟求职者自身情况的匹配。行业就不分析了,这话题太大了,最多过两天写写移动互联网的情况。 
  
(1)职业和人格,职业转换的高昂成本 
    想成为专家,首先应该看自己的人格适合什么职业,做自己不适合做的事情,肯定成不了专家。这个职业不应该是入职后去各种尝试,工作后转行的成本太高。要知道,你再跳槽的时候,招聘方只对专家和管理层的人感兴趣,对于想换职业方向的人从来不会感兴趣,谁有兴趣招个目前做运营的过来写代码?or招个写C++的过来做产品? 
    转职业的成本非常高昂,因此尝试各种职业、各个方向应该是在你做实习的时候,这也是我为什么说创新工场非常适合去实习的原因,太合适了,团队小,不谈做的专业否,但是每个人都得干好几块事情,各个职位都有机会接触到。 
  
(2)当专家需要啥?   
    当专家需要啥?主要是两块:职业相关的能力,如沟通、逻辑、学习等;业务相关知识,如XX开发语言、某工具掌握等,那么如何能快速提升能力、构建知识体系? 
    国内没啥成熟的理论,国际的我也没看到,我自己个人认为以下一些因素是必要的:良好的职业习惯、科学的学习工作方法思路、有大牛带你、少走弯路。理论粗糙,还请大家补充。 
     
(3)职业习惯    
    职业习惯:大公司或者稍微正规的公司,都会有各项流程文档、规章制度,或者有人带你教你。这非常有助于养成良好的职业习惯:常规事情处理流程、团队协作流程、邮件、文档写作规范、程序开发规范、开会制度,甚至如outlook、office的使用。这些职业习惯的养成,可以很好地提高你的做事效率,提高你跟公司的协同效率,提高大家做事情的成功率。 
     我就举个小例子,我们公司是小公司,但是我们会对员工windows、office的几十个快捷键做考试,考试不过的,要求全键盘不用鼠标干活一天。这些快捷键的掌握可以让一个经常处理文档的员工每天节约20分钟,一年就是120个小时,相当于15个工作日。 
    对于普通人而言,第一件事情就是该学会,如何让自己做事更合行业、公司的规矩、流程,能在这些范围内尽可能地提高业绩。如果你有一天完全掌握且能改进、颠覆公司规则,你就能当你们公司的专家或者管理层,如果你有一天能改进、颠覆行业的规则、流程,那你就能创业、开山立派了。 
    别看大公司的流程琐碎,流程的存在实际是为了提高效率,这事工作三年以上的人都会认同。否则腾讯、百度就不用这两年花大力气从华为、IBM等挖人过来改进管理了,不强调流程、制度的公司大多都是一些小作坊式的公司,没前途更没钱途。 
(4)科学的学习工作方法思路 
    这个就不多说了,每个行业、职业都不一样,但是相同的是都肯定有方法。做分布式开发应该先学什么,先做什么,有什么常规的模型、工具,做产品规划有哪些常规产品规划方法、工具。做猎头分哪几个阶段,每个阶段应该掌握什么能力、知识,如何做才能掌握这些能力、知识等等。   
(5)大牛带、经验的积累: 
     记得职场版有个新浪的小伙子写过一个帖子,讲他涨薪的经历,写的蛮实在的。其中有一条我记得大概的意思就是,每天晚上加班看前人的文档、操作记录,看公司的前辈们如何处理问题、解决问题的。这些行业知识、经验有些公司是形成文档的,有些公司是靠你老大教给你的,在这方面,如果是大牛或者公司有成熟的知识、经验分享制度,那会让你少走很多弯路。 
    这个非常重要,别人可能几个月、一两年处理故障、遭遇的问题、瓶颈浓缩在你职业道路不同时期、恰到好处的几次培训中,会让你少摸索很多年。平时乐于给你分享知识、经验,把他多年的知识分享给你,而不是没人分享——前提是知识是正确的。 
(6)少走弯路 
     这个弯路主要是指由于其它人,公司带来的影响,例如产品人员水平一般,导致需求经常变化,开发人员疲于奔命。公司渠道资源有问题,大家辛苦做出的产品上线访问量低,无从得到 消费者的真实反馈,无法改进产品。或者像米聊这样,在明知道会有微信这样的对手会出现的情况下,还投入这么大的财力、物力去做,跟当年金山office号称要打垮MS有啥区别。 
     一个产品从做出来到有用户,到有大量用户,到成为行业第一,到发展遭遇瓶颈没法再提高,你从中收获到的经验是截然不同的,总是在低水平重复是没有意义的。像我们做猎头,从R到AC、C、SC、MC每步都是不一样的,一年协助别人做几十万的业绩到自己带团队做几百万是不一样的。 
  
  
——————————————————————————————————————————————— 
抱歉,看到微博催更的童鞋,加班完回来抓紧写了会。 
今天就写到这了,人手紧张,天天加班,明天还得早起,先洗洗睡了,明后天有空再写下一节——为什么不是创新工场、李开复的人品、移动互联网,争取一气或两气呵成。  
  
  
  
20111104三更—————————————————————————————————————————————————————— 
  
四、找工作的常见几个误区——主要是指在互联网行业 
     由于最近收到了各种渠道的善意的、不善意、赤裸的、隐晦的关爱、提醒、暗示、威胁,那家公司和某人的情况就不写了,下面两章仅就一些求职的小问题和移动互联网做个探讨。 
     至于那家公司情况到底如何,北邮也有很多童鞋在那实习过,找人问问就知道了。问问各个团队的重组是否频繁,管理如何,流程是否有,如果有流程是否顺畅,培训是否满足日常工作的需要,老大是否很有经验能让小弟少走弯路,开发运营是否经常做无用功,对比对手自己的产品推广渠道是否给力,问问社招的人都是啥情况(这个可以对比一下同为创业公司的美团的开发团队,两者比较,你就可以得出来有工作经验的人如何看待创新工场和美团)……把这些按照我上文写的那些标准,跟其它offer做比较分析就可以了。 
  
     在这里,还讲个简单的方法,今年HR的校招招聘压力很大,你如果拿了多家公司的offer,不知道如何权衡,可以直接去问HR。让HR和业务部门给你建议,分析各家offer的优劣,HR和业务部门会很尽心尽责地给你讲的,多听几家的HR分析,你就知道该如何选择了。在这其中你也能看出各个HR和业务老板的人品到底如何。 
     说到这,我是真不知道某些新注册,发文为0,or故意跳出来讲“哪家公司招聘的时候不忽悠,李开复忽悠怎么了?”的人是个啥意思?意思是别的HR都忽悠了,某人忽悠大点,影响恶劣点就不算事了? 
  
    在此,讨论几个小问题进行讨论。 
1、去大公司就是一定当螺丝钉 
    李开复宣讲的时候说“我去大公司工作过,他们说,恭喜你,加入了全世界最牛的某某项目组,今后你的工作就是写一个按钮。”,这句话,按照心理学讲,叫心理暗示——看着是说自己的经历,但却给你暗示“你去大公司就是做螺丝钉,就是写按钮”。 
     我想说,某人是揣着明白装糊涂,有点睁眼说瞎话的意思。你去IBM、MS、通信业可能是这个样子,行业很稳定,必然了。但是在互联网行业,明明是完全不同的。 
     李开复又不是没在google呆过,他在google的时候不是很提倡给员工时间让员工自己来尝试一些东西吗?在google那么大的公司里面,不是很多研发都自己试过规划产品、运营产品吗?他这么快就忘了? 
     中国的互联网发展才这么几年,大部分企业有没什么成熟的流程制度来让员工变成螺丝钉?也就baidu、腾讯这两年不愁生死了,在尝试做这方面的流程制度。 
    大部分互联网公司都在担心明年、现在的生死问题,没有精力搞什么螺丝钉制度。研发为什么不能参与产品设计?研发为什么不能帮助运营抓取数据来分析?产品为什么不能参与运营来规划活动?竞争对手都把产品上线了,用户数都好几十万了,那还有那么多规矩?你们还不抓紧以效率优先!干嘛呢!你们几个研发和产品抓紧碰一下,赶紧做出来先上线,看着运营数据直接改!再做不出来把你们都裁了。 
    所有的互联网公司,包括百度、腾讯在内(除了一些非常成型的部门,例如新浪的新闻网站部门等,以及一些非常重量级,必须分的很细的工作,如网页搜索),不管你是研发还是其它,只要你愿意,你可以很轻松地接触到别人的工作,甚至可以参与部分(当然,大量地参与就算了,该干点啥干点,别把副业当主业)。 
    各个互联网企业的实际情况,请自己去问问论坛的各个师兄师姐,自己师兄师姐的说法,比李开复靠谱的多。我所知道的是:我们微软、宝洁、华为等公司的候X选X人(你妹的,那三个字居然是敏感词,害我尝试了无数遍,用了二分查找算法才找到敏感词)去了baidu、腾讯,第一反应就是——靠,这些互联网公司管理怎么这么混乱、职责变来变去,产品动不动就调整?我怎么还要承担别的部门的工作职责?百度、腾讯尚且如此,就更不用说其它公司了。
  
  
2、轮岗、兼作 
   术业有专攻,你相信一个人又做产品又干研发,比专做产品能做的更好?你作为一个新人,能把一样东西做好就行了。很多东西你了解一下不就行了吗?为什么要做兼作?专做一件事情,对你的能力、知识等提升更快。 
  
    如果有人对你说经常换部门对于当管理层有帮助的,除非那人是你爸,他准备让你轮岗之后当CEO,否则赶紧踹他一脚给他一巴掌让他闭嘴。在目前的中国,已经有成熟的轮岗、提升计划,且有成功先例的大公司一只手就数过来了。这几年,很多公司的管理培训生计划最后都不了了之,招进去的人打落牙齿和血吞,更多的是变成了销售培训生的代名词。更不用说在没有轮岗、培养规划的公司里面了这些人会落个什么结果。 
    
    一个公司如果说,我们打算让你多干几块工作,专注提高一下,我只能说——他在忽悠你。我再举个例子,在我们猎头业,有些猎头公司哪怕一些大的猎头公司,员工是进来之后分个大行业,然后不分细分行业,不分职业角色都做。你可以想想,一个人又做互联网研发类的职位,又做通信类销售的职位,还做软件类测试的职位,他最后挖人的时候能比得过专做移动互联网研发,专做通信业销售的猎头吗? 
  
3、可以快速致富 
    收益=成本×投资回报率×(1-失败率) 
    你在年轻的时候,成本是10~20万(你的薪水、期权),回报率是300%(给底层员工这个回报率了不起了),失败率是99.9%(眼光不行,不具备判断力;能力不够,没法控制局面降低风险)。 
    你在3、40岁的时候,成本是4、50万,回报率是3000%甚至百倍(给部门经理的,给创业骨干的肯定不一样),失败率是90%(创业风险总是有的)。 
    你自己算算帐吧。 
4、暴富机会一去不复返 
    引用李开复老师的一句话来回答——“中国平均九年出现一次大发财成功机会”,所以你不用急着去赶李开复老师宣传的这次移动互联网热潮。9年后还有呢。 
  
    引用我写《通信行业求职宝典》写的一段话:“10年前,没有人会想到SP公司会这么挣钱,新浪、搜狐等公司能靠着SP业务撑过了互联网最为寒冷的一段时间。8年前,没有没有人会想到RIM会凭借黑莓达到一年60亿美元的年收入,净利润可以达到13亿美元;6年前没有人会想到彩铃也会挣钱,一首“疯狂青蛙”居然能带来4000多万英镑的收入,4年前没有人会意识到彩信报这种几个人就可以做的产品会有数百万数千万的订阅者,2年前没人想到RIM很快就没落过时了,1年前没人想到移动互联网的泡沫会如此之大……” 
  
5、期权 
    我只想说,我认识的中高层候X选X人,谈offer大家一般最关注的是薪水,鲜有最关注股票、期权的,越高层越如此,给股票、期权再多也不如给钱实在。公司给你股票期权是为了让你暴富的还是为了让你承担公司发展风险的? 
    公司如果眼瞅着明年就一定能上市,或者公司股票明年一定比现在涨100%,公司为什么要给你这个捞钱的机会?股东方自己钱太多了? 
    收益越高,风险越高。如果有公司的期权能让你比同龄人多30%的收益,那意味着这事还有点希望,要是能多100%,那意味着这事不靠谱, 
    要是能多1000%,那基本肯定这事没啥希望了。 
  
——————————————————————————————————————————————————— 
中场休息,吃夜宵,晚上再更一次最后一段,写写移动互联网。之前说的最后一部分“offer选择”就不写,没时间,明天还要干活。 
  
四更———————————————————————————————— 
  
  
五、李开复对于移动互联网的看法和我对于移动互联网的看法 
     简单分析一下,细的东西大家可以自己分析。我给个分析的方法——多看看各个互联网大佬的动作和演讲,多去问问师兄师姐各个公司实际的人力、财力投入。跟着业内的大佬,而不是记者、爱炒作的人走,你一般成功概率会更高一些。 
  
1、移动互联网的市场前景: 
     “如果传统互联网的三大巨头,百度腾讯阿里加在一起,值一千五百亿美金的话,那么,移动互联网三个巨头,将是一千五百亿美金的十倍,一万五千亿美金。”  
     对于这段话,互联网大佬们大多选择了无视,VC们大多选择了支持这段话。有个互联网老大隐讳地调侃了一下,当然,人家说的很客气,不像我说的那么直接,他说的是“美国股市市值最高的三家公司,埃克森美孚/苹果/微软加起来市值9千多亿美元,中国无线互联网三巨头10年后市值加起来要达到1.5万亿......中国人民,站起来了......”。 
  
     他列举的是美国前三,而全世界市值前三的公司加起来都没有一万五千亿……我估计李开复1.5万亿的计算基础是全球货币高速贬值,而不是移动互联网高速发展。 
  
2、机会大 
“这个机会,我可以保证,绝对比1999年的更大,也许大五倍,也许大十倍,或者五十倍,具体大多少我不好说,但是我肯定,绝对比当年丁磊马云的机会要大很多。” 
  
     网易手里有着一百多亿的现金,既然这个机会比丁磊当年大多了,那丁磊为什么不投个几亿招几百人过来开发? 
     现在全国所有的移动互联网项目加起来还不够网易一家收购的。网易直接都买了,然后就可以直接再造一个百倍于网易的公司,岂不是最快捷? 
     做为曾经的中国首富,第一代互联网CEO,目前中国最赚钱的三家互联网公司之一的老板,丁磊的互联网从业、烧钱、赚钱经验可比李开复丰富多了。
  
3、发展 
“你要来创新工场,从基层做起,从工程师做起。你在百度微软中国银行,可能十年二十年,你能成为一个中层或高层管理者,而在创业公司,快的两三年,慢的七八年,完全有可能创业成功。” 
  
唉,两三年的中高层,让我们羞愧地掩面而泣阿。  
  
4、产品竞争激烈度 
     大家去看看豌豆荚的产品,再去看看qq推出的应用助手。豌豆荚是去年4月份推出来的,qq应用助手是今年8月推出来的,两者一个推出来了18个月,一个推出来了3个月,你去看看现在两者功能的差距。qq运用助手不知道现在装机量超过豌豆荚没有,就算没超过,我估计也就是几个月的事情。 
  
     大家再随便去qq旗下挑一些手机客户端产品,看看适配的终端种类,再去业内的其它移动互联网新秀们看看?不在一个量级。 
     Android给了很多手机公司机会,但是也给软件开发者带来了巨大的麻烦。就例如终端适配这个事情,不做,用户体验差,做,公司根本养不起这样一个团队。有些时候我甚至觉得在移动互联网上做东西还不如在腾讯等的开放平台上做东西,起码不用去考虑各个终端的种类、版本、屏幕等。 
  
5、渠道 
“推销不需要耗费巨大的渠道费用,直接经过搜索社交引擎推广” 
ucweb做了那么大了,今年在预装渠道里面被腾讯卡的死死的——你手机公司要么装我的qq和qq浏览器,要么你装他的Ucweb但是不能装QQ,钱我给的更多。 
没钱,哪来的流量,真靠社交推广就能推广起来,360哪能那么霸气当软件渠道商? 
  
     这两天还看到个很有趣的事情,跟大家分享一下。前几天苏宁易购图书上线搞活动,京东也搞活动,当当也搞活动。苏宁的系统最烂,0点就挂了,3、4点才好,活动也最一般。 
     但在某几家新闻门户看到的报道都是“苏宁易购血战XXX,投入巨资不惜亏本”,半点没提过系统挂掉的事情,而京东当当的新闻是“京东当当图书战被指花钱买骂名:大部分缺货”,边上还挂着一个新闻“京东重磅促销遇网络瘫痪 10月被投诉389起”。 
  
     呵呵,有时间大家可以多观察一下新浪、腾讯、搜狐的新闻,基本都是对掐,你看到的新闻都是资本家们想让你看到的,不管是新闻网站还是微博。 
  
6、移动互联网薪水和其它行业的薪水 
     移动互联网发展很快很大,薪水被抬的很高。 
     例如我们很多客户开的条件都是:有1、2年的相关经验,能独立干活的,不用名校毕业,不用算法好,做Android的就能到20来万,做iPhone的能到25万,更高的也有。远超过同期其它行业的人,我们前几年就看到这个趋势,前两年起就储备人才,这一年收益颇多。 
     但是,我们自己的判断是,现在的薪水泡沫在2、3年左右会慢慢破掉,这个行业的门槛很低,技术难度并不大,这几年进去的应届生2、3年成长起来后就不会这么夸张。 
  
     而且,移动互联网客户端开发未来成长空间较小,毕竟比起后台、搜索等,技术难度在那摆着。在我们知道的所有互联网公司里面,起薪都很高,但是超过30万薪水的职位很少,35万的罕见,40万的工程师职位没见过几个。但是我们做的分布式后台、搜索、数据挖掘等职位,3、40万的很普遍,1、200万的职位也有,大家可以自己思考一下为什么。 
     因此我觉得,如果你热爱开发,技术大牛,可以选择后台、数据挖掘、搜索等难度较大的,如果不是特别热爱开发,不是技术大牛,那你在Android开发和常规的一些开发职位中可以选择Android开发之类的,毕竟都是工作,未来泡沫灭了也不会差到哪去,起码近几年收入不错。 
  
7、移动互联网的创业环境和前景。 
     移动互联网的机会不少,也是未来必然的趋势,但是跟小团队没啥关系,包括现在在iPhone应用市场上,小团队存活的概率很低,开发、UI、推广等都是要钱的,人员成本也很贵。 
     有不少移动互联网小公司找我们招人,我们的第一句话就是你给我预付费我也不给你们做,太难了。大公司都抢不过来人,何谈小公司,so,这也是李老师为什么这么着急的原因。毕竟创新工场这2、3年能活下来,不管最后成几个项目,他就算成功了,要是在这轮泡沫结束前活不下来,他可能就成为了第二个方东兴。 
  
     现在的创业环境比以前要糟糕,当年遍地都是机会,全是空白的,现在创业你能做的就是依附一颗大树创业,在qq、百度、阿里系等公司框下的圈子里面创业,当年的淘宝都畏惧百度,现在的京东都畏惧etao,何谈其它。 
  
  
8、创业门槛 
 “今天的创业和以前不一样了,你只要做一个最低的不被用户骂的产品,先推向市场。不要太花力气琢磨用户喜欢黑色还是红色,一半黑一半红,哪个黏性大就用哪个,不要考虑让用户注册好还是不注册好,一半注册一半不注册,看用户反应。如果你做汽车,一半黑一半红,行吗?成本太大,做软件互联网最大的好处,就是无本生意,用户也不用付钱,互联网就是你的实验室。” 
  “采用云计算,不需要买服务器和带宽;采用开源软件,大大节省工程师成本;推销不需要耗费巨大的渠道费用,直接经过搜索社交引擎推广,大大节约了营销成本……150万就够了。虽然不算很低,但这是历史新低。 
  
     论据我就不评价了,如果论点是正确的,的确门槛低,那么我想说——对创业者而言,在初期想捞一把就走,门槛低是个好事,如果在中后期,那简直是个灾难,门槛越低竞争越激烈。只能比烧钱速度和兜里的银子了。 
  
9、移动互联网前景 
     这话题太大了,我懒得写,只想说,前景很开阔,但是走势很难定,鹿死谁手未知,且未来移动互联网开发的难度、门槛、平台到底是什么样?不知道。当有一天折叠屏幕、投影、电池技术都解决的时候,移动互联网和互联网的界限真会那么清晰吗?(貌似现在已经很模糊了)。 
     而且,轻客户端,web化已经成为了一个必然的趋势,无线传输技术、材料学的发展给这个行业带来了很多不确定性。 
   
  
六、再见和祝福 
     以后就慢慢不写东西了,老了,从真情流露job版到北邮人论坛job版,该写的东西都重复一万遍了,没有啥写东西的欲望,也没啥新东西好写的,每年看到你们讨论的都是一样的话题,都看了6、7年了。这可能是我最后一次在job版写长文了,年底可能在职场版再写一篇长文,我的论坛生活就差不多over了,论坛已经不是我们这个时代的人的了。 
  
  
在最后,想对各位师弟,师妹说的是—— 
各位师弟,师姐师妹是你们的,offer和银子也是你们的! 
各位师妹,师兄师弟是你们的,师姐师妹有时候也是你们的,offer和户口也是你们的! 
看到不少公司今年开了40多万的offer,你们可以一定要努力,一定给北邮争光,户口和50万的offer都要给母校拿一堆回来! 
  
祝大家找工作顺利,11月才刚开始,后续的路还长。论坛以后靠你们啦!找完工作了别忘了给你们的师弟师妹们多分享点经验
 
posted @ 2011-11-06 11:31 IT菜鸟 阅读(187) | 评论 (0)编辑 收藏

2011年10月19日 #

16.将vector和string传给遗留的API
*vector ,const char* c ,用 &vector[0]即可
*string ,string.c_str()

17.使用“交换技巧”来修正过剩容器
*vector<Contestant> vec(contestants).swap(contestants)

18.避免使用vector<bool>
两个问题
*它不是一个STL容器,它并不容纳bool
如果C是一个T类型的对象的容器,且C支持operator[],那么以下代码必须能够通过编译
T* p= &c[0] //无论operator[]返回什么,都可以用这个地址初始化一个T*
但是vector<bool>中做了优化,里面存放的是bit值
deque内部内存不是连续的,但里面存的是bool值
还有一个bitse可解决这个问题

19.了解相等和等价的区别
*相等的概念是基于operator== ,等价的概念基于operator<
20.为指针的关联容器指定比较类型
关联容器对 相同 的定义是等价

21.永远让比较函数对相等的值返回false

22.避免原地修改set和multiset的键

 

posted @ 2011-10-19 18:29 IT菜鸟 阅读(238) | 评论 (0)编辑 收藏

2011年10月12日 #

条款10 和 11不太懂,以后再看看

条款10,注意分配器的协定和约束
如果要自定义分配器
*把你的分配器做成一个模板,带有模板参数T,代表你要分配的内存的对象类型
*提供pointer和reference的typedef,但总是让pointer是T* reference是T&
*通常,分配器不能有非静态的数据成员
*记得应该传给分配器的allocate成员函数需要分配的对象个数而不是字节数,也应该记得这些函数返回T*指针,即时还没有T对象被构造
*一定要提供标准容器依赖的内嵌rebind模板

条款12,对STL容器线程安全性的期待现实一些

template<typename Container>
class Lock
{
public:
 Lock(
const Container container): c( containner ){
  getMutexFor( c );
 }

 
~Lock(){
  releaseMutexFor( c );
 }

private:
const Container& c;

}
;

vector
<int> v;
{
 Lock
< vect< int > > lock( v );
 vector
<int>::iterator first5( find(v.begin() v.end(), 5));
 
if( first5 != v.end()){
  
*first5 = 0;
 }

}


 

posted @ 2011-10-12 17:51 IT菜鸟 阅读(343) | 评论 (0)编辑 收藏

2011年10月11日 #

条款九,在删除选项中仔细选择
*在一个标准STL容器中去掉值为1963的对象,若是一个连续内存容器,最好的方法是erase-remove
c.erase ( remove( c.begin(), c.end(), 1963 ), c.end() );//当C时vector,string ,deque时,这是一处特定值得元素的最佳方法
*当C是标准关联容器的时候(map, set)使用任何叫做remove的东西都是完全错误的
而应该直接采用c.erase(1963)对数的高效时间
*但如果操作是从C中除去每个有特定值的物体
bool bandValue(int x)
对于序列容器(vector, string,deque,list)我们要做的只是把每个remove替换为remove_if然后就OK了
c.erase( remove_if(c.begin(), c.end(), badValue), c.end())//vector,string,deque
c.remove_if( badValue ) //list
对于标准关联容器,
AssocContainner<int> c;
for(AssoContainer<int>::iterator i = c.begin();
i!= c.end(); )
{
 if(badValue(*i)) c.erase(i++);
 else ++i;
}
posted @ 2011-10-11 16:24 IT菜鸟 阅读(182) | 评论 (0)编辑 收藏

条款五,尽量使用区间成员函数代替他们的单元素兄弟
*对于所有标准序列容器(vector, string, deque, 和list)都有效,无论何时你必须完全替代一个容器的内容,你就应该想到赋值
*insert,每次都必须移动为新元素腾出空间
*序列容器erase时返回迭代器,而关联容器返回空

条款六,警惕C++最令人恼怒的解析
int g( double pf() ) pf其实是一个指针
int g( double() );同上,函数名省略
int g( double x) == int g( double (x) )

条款七, 当时用new得指针的容器时,记得在销毁容器前delete那些指针
*这样的代码造成内存泄露

void doSomething()
{
 vector
<Widget*> vwp;
 
for(int i =0 ; i < SOME_MAGIC_NUMBER; ++i)
 
{
  vwp.push_back(
new Widget);
 }

 
//使用完毕的时候,vwp内的Widget对象没有释放
}



最简单的实现方法是

void doSomething()
{
 
for(vector<Widget*>::iterator i = vwp.begin(); != vwp.end(); ++i)
 
{
  delete 
*i;
 }

}



这样的问题是for循环代码多余for_each,但没有使用for_each简单明了
另一个问题是这段代码不是异常安全的

简洁不考虑异常的方法

struct DeleteObject{
 template
<typename T>
 
void operator()(const T* ptr) const
 
{
  delete prt;
 }

}
;
void doSomething()
{
 deque
<SpecialString*> dssp;
 for_each( dssp.begin(), dssp.end(), DeleteObject());
}


void doSomething()
{
 typedef boost::shar_ptr
<Widget> SPW;
 vector
<SPW> vwp;
 
for(int i = 0; i <SOME_MAGIC_NUMBER; ++i)
 
{
  vwp.push_back( SPW(
new Widget));
 }

}



 

posted @ 2011-10-11 15:03 IT菜鸟 阅读(315) | 评论 (0)编辑 收藏

2011年10月10日 #

条款三 使容器里对象的拷贝量轻而正确
*容器中的对象都是拷贝来拷贝出去
*容器的拷贝要注意基类的切割问题
*使拷贝更高效、正确,且对分割问题免疫的简单的方式是建立指针的容器而不是对象的容器,最好是智能指针

条款四 容器用empty来代替检查size()是否为0
*理由很简单,对于所有的标准容器,empty是一个常数时间的操作,但对于一些List实现,size()花费线性时间
*list的size花费线性时间是因为为了让splice变为常数时间,这是一个让哪个函数实现最高效率的问题

所以对于所有容器来说,用empty()而不是size()==0

posted @ 2011-10-10 18:31 IT菜鸟 阅读(225) | 评论 (0)编辑 收藏

条款二 小心对容器无关的幻想

UML 的几点 总结,不一定对。。
关联一般保存的都是指针
聚合一般保存的都是对象
依赖一般都是参数
关联都是我主动方指向被动方

一般来讲不要这么写:

class Widget{};
vector
<Widget> vw;
Widget bestWidget;
vector
<Widget>::iterator i = find(vw.begin(), vw.end(),bestWidget);


 

要这么写

 

class Widget{};
typedef vector
<Widget> WidgetContainer;
typedef WidgetContainer::iterator WCIterator;
WidgetContainer cw;
Widget bestWidget;
WCIterator i 
= find(cw.begin(), cw.end(),bestWidget);

 

posted @ 2011-10-10 17:43 IT菜鸟 阅读(224) | 评论 (0)编辑 收藏

条款一 仔细选择你的容器
*vector<char>可以作为string的替代品
*vector list deque ,vector是一种可以默认使用的序列类型,当很频繁的对序列中进行插入和删除的时候应该用list,大部分发生在尾部的话用deque这种数据结构
*连续内存容器和基于节点的容器的区别
连续容器vector/string/deque在一个或者多个内存块中保存它们的元素,如果新元素被插入或者已存元素被删除,其他在同一个内存块中的元素必须向上或者向下移动来为新元素提供空间或者填充原来被删除的元素所占的空间,这种移动影响了效率。
基于节点的list,插入或者删除的时候不需要移动
*序列容器在任意位置插入一个新元素,关联容器不可以
*容器中的数据的内存布局需要兼容C吗,如果是,那只能用vector
*查找速度,散列容器》排序的vector>标准的关联容器
*需要可靠的插入和删除的能力,如果是需要使用基于节点的容器
*需要迭代器、指针、引用的实效次数减少到最小,如果是,使用介于节点的容器,一般来说,在连续容器上的插入和删除会使所有指向容器的迭代器指针和引用实效
posted @ 2011-10-10 13:33 IT菜鸟 阅读(332) | 评论 (0)编辑 收藏

2011年9月23日 #

务器结构探讨 -- 最简单的结构 

  所谓服务器结构,也就是如何将服务器各部分合理地安排,以实现最初的功能需求。所以,结构本无所谓正确与错误;当然,优秀的结构更有助于系统的搭建,对系统的可扩展性及可维护性也有更大的帮助。 

  好的结构不是一蹴而就的,而且每个设计者心中的那把尺都不相同,所以这个优秀结构的定义也就没有定论。在这里,我们不打算对现有游戏结构做评价,而是试着从头开始搭建一个我们需要的MMOG结构。 

  对于一个最简单的游戏服务器来说,它只需要能够接受来自客户端的连接请求,然后处理客户端在游戏世界中的移动及交互,也即游戏逻辑处理即可。如果我们把这两项功能集成到一个服务进程中,则最终的结构很简单: 

  client ----- server 

  嗯,太简单了点,这样也敢叫服务器结构?好吧,现在我们来往里面稍稍加点东西,让它看起来更像是服务器结构一些。 

  一般来说,我们在接入游戏服务器的时候都会要提供一个帐号和密码,验证通过后才能进入。关于为什么要提供用户名和密码才能进入的问题我们这里不打算做过多讨论,云风曾对此也提出过类似的疑问,并给出了只用一个标识串就能进入的设想,有兴趣的可以去看看他们的讨论。但不管是采用何种方式进入,照目前看来我们的服务器起码得提供一个帐号验证的功能。 

  我们把观察点先集中在一个大区内。在大多数情况下,一个大区内都会有多组游戏服,也就是多个游戏世界可供选择。简单点来实现,我们完全可以抛弃这个大区的概念,认为一个大区也就是放在同一个机房的多台服务器组,各服务器组间没有什么关系。这样,我们可为每组服务器单独配备一台登录服。最后的结构图应该像这样: 

  loginServer   gameServer 
     |           / 
     |         / 
     client 

  该结构下的玩家操作流程为,先选择大区,再选择大区下的某台服务器,即某个游戏世界,点击进入后开始帐号验证过程,验证成功则进入了该游戏世界。但是,如果玩家想要切换游戏世界,他只能先退出当前游戏世界,然后进入新的游戏世界重新进行帐号验证。 

  早期的游戏大都采用的是这种结构,有些游戏在实现时采用了一些技术手段使得在切换游戏服时不需要再次验证帐号,但整体结构还是未做改变。 

  该结构存在一个服务器资源配置的问题。因为登录服处理的逻辑相对来说比较简单,就是将玩家提交的帐号和密码送到数据库进行验证,和生成会话密钥发送给游戏服和客户端,操作完成后连接就会立即断开,而且玩家在以后的游戏过程中不会再与登录服打任何交道。这样处理短连接的过程使得系统在大多数情况下都是比较空闲的,但是在某些时候,由于请求比较密集,比如开新服的时候,登录服的负载又会比较大,甚至会处理不过来。 

  另外在实际的游戏运营中,有些游戏世界很火爆,而有些游戏世界却非常冷清,甚至没有多少人玩的情况也是很常见的。所以,我们能否更合理地配置登录服资源,使得整个大区内的登录服可以共享就成了下一步改进的目标。 

服务器结构探讨 -- 登录服的负载均衡 

  回想一下我们在玩wow时的操作流程:运行wow.exe进入游戏后,首先就会要求我们输入用户名和密码进行验证,验证成功后才会出来游戏世界列表,之后是排队进入游戏世界,开始游戏... 

  可以看到跟前面的描述有个很明显的不同,那就是要先验证帐号再选择游戏世界。这种结构也就使得登录服不是固定配备给个游戏世界,而是全区共有的。 

  我们可以试着从实际需求的角度来考虑一下这个问题。正如我们之前所描述过的那样,登录服在大多数情况下都是比较空闲的,也许我们的一个拥有20个游戏世界的大区仅仅使用10台或更少的登录服即可满足需求。而当在开新区的时候,或许要配备40台登录服才能应付那如潮水般涌入的玩家登录请求。所以,登录服在设计上应该能满足这种动态增删的需求,我们可以在任何时候为大区增加或减少登录服的部署。 

  当然,在这里也不会存在要求添加太多登录服的情况。还是拿开新区的情况来说,即使新增加登录服满足了玩家登录的请求,游戏世界服的承载能力依然有限,玩家一样只能在排队系统中等待,或者是进入到游戏世界中导致大家都卡。 

  另外,当我们在增加或移除登录服的时候不应该需要对游戏世界服有所改动,也不会要求重启世界服,当然也不应该要求客户端有什么更新或者修改,一切都是在背后自动完成。 

  最后,有关数据持久化的问题也在这里考虑一下。一般来说,使用现有的商业数据库系统比自己手工技术先进要明智得多。我们需要持久化的数据有玩家的帐号及密码,玩家创建的角色相关信息,另外还有一些游戏世界全局共有数据也需要持久化。 

  好了,需求已经提出来了,现在来考虑如何将其实现。 

  对于负载均衡来说,已有了成熟的解决方案。一般最常用,也最简单部署的应该是基于DNS的负载均衡系统了,其通过在DNS中为一个域名配置多个IP地址来实现。最新的DNS服务已实现了根据服务器系统状态来实现的动态负载均衡,也就是实现了真正意义上的负载均衡,这样也就有效地解决了当某台登录服当机后,DNS服务器不能立即做出反应的问题。当然,如果找不到这样的解决方案,自己从头打造一个也并不难。而且,通过DNS来实现的负载均衡已经包含了所做的修改对登录服及客户端的透明。 

  而对于数据库的应用,在这种结构下,登录服及游戏世界服都会需要连接数据库。从数据库服务器的部署上来说,可以将帐号和角色数据都放在一个中心数据库中,也可分为两个不同的库分别来处理,基到从物理上分到两台不同的服务器上去也行。 

  但是对于不同的游戏世界来说,其角色及游戏内数据都是互相独立的,所以一般情况下也就为每个游戏世界单独配备一台数据库服务器,以减轻数据库的压力。所以,整体的服务器结构应该是一个大区有一台帐号数据库服务器,所有的登录服都连接到这里。而每个游戏世界都有自己的游戏数据库服务器,只允许本游戏世界内的服务器连接。 

  最后,我们的服务器结构就像这样: 

               大区服务器        
          /     |       \ 
            /       |        \ 
            登录服1   登录服2   世界服1   世界服2 
         \         |         |       |   
          \       |         |         | 
          帐号数据库         DBS     DBS 

  这里既然讨论到了大区及帐号数据库,所以顺带也说一下关于激活大区的概念。wow中一共有八个大区,我们想要进入某个大区游戏之前,必须到官网上激活这个区,这是为什么呢? 

  一般来说,在各个大区帐号数据库之上还有一个总的帐号数据库,我们可以称它为中心数据库。比如我们在官网上注册了一个帐号,这时帐号数据是只保存在中心数据库上的。而当我们要到一区去创建角色开始游戏的时候,在一区的帐号数据库中并没有我们的帐号数据,所以,我们必须先到官网上做一次激活操作。这个激活的过程也就是从中心库上把我们的帐号数据拷贝到所要到的大区帐号数据库中。 

服务器结构探讨 -- 简单的世界服实现 

  讨论了这么久我们一直都还没有进入游戏世界服务器内部,现在就让我们来窥探一下里面的结构吧。 

  对于现在大多数MMORPG来说,游戏服务器要处理的基本逻辑有移动、聊天、技能、物品、任务和生物等,另外还有地图管理与消息广播来对其他高级功能做支撑。如纵队、好友、公会、战场和副本等,这些都是通过基本逻辑功能组合或扩展而成。 

  在所有这些基础逻辑中,与我们要讨论的服务器结构关系最紧密的当属地图管理方式。决定了地图的管理方式也就决定了我们的服务器结构,我们仍然先从最简单的实现方式开始说起。 

  回想一下我们曾战斗过无数个夜晚的暗黑破坏神,整个暗黑的世界被分为了若干个独立的小地图,当我们在地图间穿越时,一般都要经过一个叫做传送门的装置。世界中有些地图间虽然在地理上是直接相连的,但我们发现其游戏内部的逻辑却是完全隔离的。可以这样认为,一块地图就是一个独立的数据处理单元。 

  既然如此,我们就把每块地图都当作是一台独立的服务器,他提供了在这块地图上游戏时的所有逻辑功能,至于内部结构如何划分我们暂不理会,先把他当作一个黑盒子吧。 

  当两个人合作做一件事时,我们可以以对等的关系相互协商着来做,而且一般也都不会有什么问题。当人数增加到三个时,我们对等的合作关系可能会有些复杂,因为我们每个人都同时要与另两个人合作协商。正如俗语所说的那样,三个和尚可能会碰到没水喝的情况。当人数继续增加,情况就变得不那么简单了,我们得需要一个管理者来对我们的工作进行分工、协调。游戏的地图服务器之间也是这么回事。 

  一般来说,我们的游戏世界不可能会只有一块或者两块小地图,那顺理成章的,也就需要一个地图管理者。先称它为游戏世界的中心服务器吧,毕竟是管理者嘛,大家都以它为中心。 

  中心服务器主要维护一张地图ID到地图服务器地址的映射表。当我们要进入某张地图时,会从中心服上取得该地图的IP和port告诉客户端,客户端主动去连接,这样进入他想要去的游戏地图。在整个游戏过程中,客户端始终只会与一台地图服务器保持连接,当要切换地图的时候,在获取到新地图的地址后,会先与当前地图断开连接,再进入新的地图,这样保证玩家数据在服务器上只有一份。 

  我们来看看结构图是怎样的: 

              中心服务器 
           /       \         \ 
         /         \         \ 
       登录服     地图1     地图2   地图n 
         \         |         /       / 
           \       |         /       / 
                客户端 

  很简单,不是吗。但是简单并不表示功能上会有什么损失,简单也更不能表示游戏不能赚钱。早期不少游戏也确实采用的就是这种简单结构。 

服务器结构探讨 -- 继续世界服 

  都已经看出来了,这种每切换一次地图就要重新连接服务器的方式实在是不够优雅,而且在实际游戏运营中也发现,地图切换导致的卡号,复制装备等问题非常多,这里完全就是一个事故多发地段,如何避免这种频繁的连接操作呢? 

  最直接的方法就是把那个图倒转过来就行了。客户端只需要连接到中心服上,所有到地图服务器的数据都由中心服来转发。很完美的解决方案,不是吗? 

  这种结构在实际的部署中也遇到了一些挑战。对于一般的MMORPG服务器来说,单台服务器的承载量平均在2000左右,如果你的服务器很不幸地只能带1000人,没关系,不少游戏都是如此;如果你的服务器上跑了3000多玩家依然比较流畅,那你可以自豪地告诉你的策划,多设计些大量消耗服务器资源的玩法吧,比如大型国战、公会战争等。 

  2000人,似乎我们的策划朋友们不大愿意接受这个数字。我们将地图服务器分开来原来也是想将负载分开,以多带些客户端,现在要所有的连接都从中心服上转发,那连接数又遇到单台服务器的可最大承载量的瓶颈了。 

  这里有必要再解释下这个数字。我知道,有人一定会说,才带2000人,那是你水平不行,我随便写个TCP服务器都可带个五六千连接。问题恰恰在于你是随便写的,而MMORPG的服务器是复杂设计的。如果一个演示socket API用的echo服务器就能满足MMOG服务器的需求,那写服务器该是件多么惬意的事啊。 

  但我们所遇到的事实是,服务器收到一个移动包后,要向周围所有人广播,而不是echo服务器那样简单的回应;服务器在收到一个连接断开通知时要向很多人通知玩家退出事件,并将该玩家的资料写入数据库,而不是echo服务器那样什么都不需要做;服务器在收到一个物品使用请求包后要做一系列的逻辑判断以检查玩家有没有作弊;服务器上还启动着很多定时器用来更新游戏世界的各种状态...... 

  其实这么一比较,我们也看出资源消耗的所在了:服务器上大量的复杂的逻辑处理。再回过头来看看我们想要实现的结构,我们既想要有一个唯一的入口,使得客户端不用频繁改变连接,又希望这个唯一入口的负载不会太大,以致于接受不了多少连接。 

  仔细看一看这个需求,我们想要的仅仅只是一台管理连接的服务器,并不打算让他承担太多的游戏逻辑。既然如此,那五六千个连接也还有满足我们的要求。至少在现在来说,一个游戏世界内,也就是一组服务器内同时有五六千个在线的玩家还是件让人很兴奋的事。事实上,在大多数游戏的大部分时间里,这个数字也是很让人眼红的。 

  什么?你说梦幻、魔兽还有史先生的那个什么征途远不止这么点人了!噢,我说的是大多数,是大多数,不包括那些明星。你知道大陆现在有多少游戏在运营吗?或许你又该说,我们不该在一开始就把自己的目标定的太低!好吧,我们还是先不谈这个。 

  继续我们的结构讨论。一般来说,我们把这台负责连接管理的服务器称为网关服务器,因为内部的数据都要通过这个网关才能出去,不过从这台服务器提供的功能来看,称其为反向代理服务器可能更合适。我们也不在这个名字上纠缠了,就按大家通用的叫法,还是称他为网关服务器吧。 

  网关之后的结构我们依然可以采用之前描述的方案,只是,似乎并没有必要为每一个地图都开一个独立的监听端口了。我们可以试着对地图进行一些划分,由一个Master Server来管理一些更小的Zone Server,玩家通过网关连接到Master Server上,而实际与地图有关的逻辑是分派给更小的Zone Server去处理。 

  最后的结构看起来大概是这样的: 

         Zone Server         Zone Server 
                 \             / 
                 \           / 
                 Master Server           Master Server 
                     /       \                   / 
                   /         \                 / 
         Gateway Server         \               / 
             |         \         \             / 
             |         \         \           / 
             |               Center Server 
             | 
             | 
           Client 

服务器结构探讨 -- 最终的结构 

  如果我们就此打住,可能马上就会有人要嗤之以鼻了,就这点古董级的技术也敢出来现。好吧,我们还是把之前留下的问题拿出来解决掉吧。 

  一般来说,当某一部分能力达不到我们的要求时,最简单的解决方法就是在此多投入一点资源。既然想要更多的连接数,那就再加一台网关服务器吧。新增加了网关服后需要在大区服上做相应的支持,或者再简单点,有一台主要的网关服,当其负载较高时,主动将新到达的连接重定向到其他网关服上。 

  而对于游戏服来说,有一台还是多台网关服是没有什么区别的。每个代表客户端玩家的对象内部都保留一个代表其连接的对象,消息广播时要求每个玩家对象使用自己的连接对象发送数据即可,至于连接是在什么地方,那是完全透明的。当然,这只是一种简单的实现,也是普通使用的一种方案,如果后期想对消息广播做一些优化的话,那可能才需要多考虑一下。 

  既然说到了优化,我们也稍稍考虑一下现在结构下可能采用的优化方案。 

  首先是当前的Zone Server要做的事情太多了,以至于他都处理不了多少连接。这其中最消耗系统资源的当属生物的AI处理了,尤其是那些复杂的寻路算法,所以我们可以考虑把这部分AI逻辑独立出来,由一台单独的AI服务器来承担。 

  然后,我们可以试着把一些与地图数据无关的公共逻辑放到Master Server上去实现,这样Zone Server上只保留了与地图数据紧密相关的逻辑,如生物管理,玩家移动和状态更新等。 

  还有聊天处理逻辑,这部分与游戏逻辑没有任何关联,我们也完全可以将其独立出来,放到一台单独的聊天服务器上去实现。 

  最后是数据库了,为了减轻数据库的压力,提高数据请求的响应速度,我们可以在数据库之前建立一个数据库缓存服务器,将一些常用数据缓存在此,服务器与数据库的通信都要通过这台服务器进行代理。缓存的数据会定时的写入到后台数据库中。 

  好了,做完这些优化我们的服务器结构大体也就定的差不多了,暂且也不再继续深入,更细化的内容等到各个部分实现的时候再探讨。 

  好比我们去看一场晚会,舞台上演员们按着预定的节目单有序地上演着,但这就是整场晚会的全部吗?显然不止,在幕后还有太多太多的人在忙碌着,甚至在晚会前和晚会后都有。我们的游戏服务器也如此。 

  在之前描述的部分就如同舞台上的演员,是我们能直接看到的,幕后的工作人员我们也来认识一下。 

  现实中有警察来维护秩序,游戏中也如此,这就是我们常说的GM。GM可以采用跟普通玩家一样的拉入方式来进入游戏,当然权限会比普通玩家高一些,也可以提供一台GM服务器专门用来处理GM命令,这样可以有更高的安全性,GM服一般接在中心服务器上。 

  在以时间收费的游戏中,我们还需要一台计费的服务器,这台服务器一般接在网关服务器上,注册玩家登录和退出事件以记录玩家的游戏时间。 

  任何为用户提供服务的地方都会有日志记录,游戏服务器当然也不例外。从记录玩家登录的时间,地址,机器信息到游戏过程中的每一项操作都可以作为日志记录下来,以备查错及数据挖掘用。至于搜集玩家机器资料所涉及到的法律问题不是我们该考虑的。 

  差不多就这么多了吧,接下来我们会按照这个大致的结构来详细讨论各部分的实现。 

服务器结构探讨 -- 一点杂谈 

  再强调一下,服务器结构本无所谓好坏,只有是否适合自己。我们在前面探讨了一些在现在的游戏中见到过的结构,并尽我所知地分析了各自存在的一些问题和可以做的一些改进,希望其中没有谬误,如果能给大家也带来些启发那自然更好。 

  突然发现自己一旦罗嗦起来还真是没完没了。接下来先说说我在开发中遇到过的一些困惑和一基础问题探讨吧,这些问题可能有人与我一样,也曾遇到过,或者正在被困扰中,而所要探讨的这些基础问题向来也是争论比较多的,我们也不评价其中的好与坏,只做简单的描述。 

  首先是服务器操作系统,linux与windows之争随处可见,其实在大多数情况下这不是我们所能决定的,似乎各大公司也基本都有了自己的传统,如网易的freebsd,腾讯的linux等。如果真有权利去选择的话,选自己最熟悉的吧。 

  决定了OS也就基本上确定了网络IO模型,windows上的IOCP和linux下的epool,或者直接使用现有的网络框架,如ACE和asio等,其他还有些商业的网络库在国内的使用好像没有见到,不符合中国国情嘛。:) 

  然后是网络协议的选择,以前的选择大多倾向于UDP,为了可靠传输一般自己都会在上面实现一层封装,而现在更普通的是直接采用本身就很可靠的TCP,或者TCP与UDP的混用。早期选择UDP的主要原因还是带宽限制,现在宽带普通的情况下TCP比UDP多出来的一点点开销与开发的便利性相比已经不算什么了。当然,如果已有了成熟的可靠UDP库,那也可以继续使用着。 

  还有消息包格式的定义,这个曾在云风的blog上展开过激烈的争论。消息包格式定义包括三段,包长、消息码和包体,争论的焦点在于应该是消息码在前还是包长在前,我们也把这个当作是信仰问题吧,有兴趣的去云风的blog上看看,论论。 

  另外早期有些游戏的包格式定义是以特殊字符作分隔的,这样一个好处是其中某个包出现错误后我们的游戏还能继续。但实际上,我觉得这是完全没有必要的,真要出现这样的错误,直接断开这个客户端的连接可能更安全。而且,以特殊字符做分隔的消息包定义还加大了一点点网络数据量。 

  最后是一个纯技术问题,有关socket连接数的最大限制。开始学习网络编程的时候我犯过这样的错误,以为port的定义为unsigned short,所以想当然的认为服务器的最大连接数为65535,这会是一个硬性的限制。而实际上,一个socket描述符在windows上的定义是unsigned int,因此要有限制那也是四十多亿,放心好了。 

  在服务器上port是监听用的,想象这样一种情况,web server在80端口上监听,当一个连接到来时,系统会为这个连接分配一个socket句柄,同时与其在80端口上进行通讯;当另一个连接到来时,服务器仍然在80端口与之通信,只是分配的socket句柄不一样。这个socket句柄才是描述每个连接的唯一标识。按windows网络编程第二版上的说法,这个上限值配置影响。 

  好了,废话说完了,下一篇,我们开始进入登录服的设计吧。 

登录服的设计 -- 功能需求 

  正如我们在前面曾讨论过的,登录服要实现的功能相当简单,就是帐号验证。为了便于描述,我们暂不引入那些讨论过的优化手段,先以最简单的方式实现,另外也将基本以mangos的代码作为参考来进行描述。 

  想象一下帐号验证的实现方法,最容易的那就是把用户输入的明文用帐号和密码直接发给登录服,服务器根据帐号从数据库中取出密码,与用户输入的密码相比较。 

  这个方法存在的安全隐患实在太大,明文的密码传输太容易被截获了。那我们试着在传输之前先加一下密,为了服务器能进行密码比较,我们应该采用一个可逆的加密算法,在服务器端把这个加密后的字串还原为原始的明文密码,然后与数据库密码进行比较。既然是一个可逆的过程,那外挂制作者总有办法知道我们的加密过程,所以,这个方法仍不够安全。 

  哦,如果我们只是希望密码不可能被还原出来,那还不容易吗,使用一个不可逆的散列算法就行了。用户在登录时发送给服务器的是明文的帐号和经散列后的不可逆密码串,服务器取出密码后也用同样的算法进行散列后再进行比较。比如,我们就用使用最广泛的md5算法吧。噢,不要管那个王小云的什么论文,如果我真有那么好的运气,早中500w了,还用在这考虑该死的服务器设计吗? 

  似乎是一个很完美的方案,外挂制作者再也偷不到我们的密码了。慢着,外挂偷密码的目的是什么?是为了能用我们的帐号进游戏!如果我们总是用一种固定的算法来对密码做散列,那外挂只需要记住这个散列后的字串就行了,用这个做密码就可以成功登录。 

  嗯,这个问题好解决,我们不要用固定的算法进行散列就是了。只是,问题在于服务器与客户端采用的散列算法得出的字串必须是相同的,或者是可验证其是否匹配的。很幸运的是,伟大的数学字们早就为我们准备好了很多优秀的这类算法,而且经理论和实践都证明他们也确实是足够安全的。 

  这其中之一是一个叫做SRP的算法,全称叫做Secure Remote Password,即安全远程密码。wow使用的是第6版,也就是SRP6算法。有关其中的数学证明,如果有人能向我解释清楚,并能让我真正弄明白的话,我将非常感激。不过其代码实现步骤倒是并不复杂,mangos中的代码也还算清晰,我们也不再赘述。 

  登录服除了帐号验证外还得提供另一项功能,就是在玩家的帐号验证成功后返回给他一个服务器列表让他去选择。这个列表的状态要定时刷新,可能有新的游戏世界开放了,也可能有些游戏世界非常不幸地停止运转了,这些状态的变化都要尽可能及时地让玩家知道。不管发生了什么事,用户都有权利知道,特别是对于付过费的用户来说,我们不该藏着掖着,不是吗? 

  这个游戏世界列表的功能将由大区服来提供,具体的结构我们在之前也描述过,这里暂不做讨论。登录服将从大区服上获取到的游戏世界列表发给已验证通过的客户端即可。好了,登录服要实现的功能就这些,很简单,是吧。 

  确实是太简单了,不过简单的结构正好更适合我们来看一看游戏服务器内部的模块结构,以及一些服务器共有组件的实现方法。这就留作下一篇吧。 

服务器公共组件实现 -- mangos的游戏主循环 

  当阅读一项工程的源码时,我们大概会选择从main函数开始,而当开始一项新的工程时,第一个写下的函数大多也是main。那我们就先来看看,游戏服务器代码实现中,main函数都做了些什么。 

  由于我在读技术文章时最不喜看到的就是大段大段的代码,特别是那些直接Ctrl+C再Ctrl+V后未做任何修改的代码,用句时髦的话说,一点技术含量都没有!所以在我们今后所要讨论的内容中,尽量会避免出现直接的代码,在有些地方确实需要代码来表述时,也将会选择使用伪码。 

  先从mangos的登录服代码开始。mangos的登录服是一个单线程的结构,虽然在数据库连接中可以开启一个独立的线程,但这个线程也只是对无返回结果的执行类SQL做缓冲,而对需要有返回结果的查询类SQL还是在主逻辑线程中阻塞调用的。 

  登录服中唯一的这一个线程,也就是主循环线程对监听的socket做select操作,为每个连接进来的客户端读取其上的数据并立即进行处理,直到服务器收到SIGABRT或SIGBREAK信号时结束。 

  所以,mangos登录服主循环的逻辑,也包括后面游戏服的逻辑,主循环的关键代码其实是在SocketHandler中,也就是那个Select函数中。检查所有的连接,对新到来的连接调用OnAccept方法,有数据到来的连接则调用OnRead方法,然后socket处理器自己定义对接收到的数据如何处理。 

  很简单的结构,也比较容易理解。 


  只是,在对性能要求比较高的服务器上,select一般不会是最好的选择。如果我们使用windows平台,那IOCP将是首选;如果是linux,epool将是不二选择。我们也不打算讨论基于IOCP或是基于epool的服务器实现,如果仅仅只是要实现服务器功能,很简单的几个API调用即可,而且网上已有很多好的教程;如果是要做一个成熟的网络服务器产品,不是我几篇简单的技术介绍文章所能达到。 

  另外,在服务器实现上,网络IO与逻辑处理一般会放在不同的线程中,以免耗时较长的IO过程阻塞住了需要立即反应的游戏逻辑。 

  数据库的处理也类似,会使用异步的方式,也是避免耗时的查询过程将游戏服务器主循环阻塞住。想象一下,因某个玩家上线而发起的一次数据库查询操作导致服务器内所有在线玩家都卡住不动将是多么恐怖的一件事! 

  另外还有一些如事件、脚本、消息队列、状态机、日志和异常处理等公共组件,我们也会在接下来的时间里进行探讨。 

服务器公共组件实现 -- 继续来说主循环 

  前面我们只简单了解了下mangos登录服的程序结构,也发现了一些不足之处,现在我们就来看看如何提供一个更好的方案。 

  正如我们曾讨论过的,为了游戏主逻辑循环的流畅运行,所有比较耗时的IO操作都会分享到单独的线程中去做,如网络IO,数据库IO和日志IO等。当然,也有把这些分享到单独的进程中去做的。 

  另外对于大多数服务器程序来说,在运行时都是作为精灵进程或服务进程的,所以我们并不需要服务器能够处理控制台用户输入,我们所要处理的数据来源都来自网络。 

  这样,主逻辑循环所要做的就是不停要取消息包来处理,当然这些消息包不仅有来自客户端的玩家操作数据包,也有来自GM服务器的管理命令,还包括来自数据库查询线程的返回结果消息包。这个循环将一直持续,直到收到一个通知服务器关闭的消息包。 

  主逻辑循环的结构还是很简单的,复杂的部分都在如何处理这些消息包的逻辑上。我们可以用一段简单的伪码来描述这个循环过程: 

    while (Message* msg = getMessage()) 
    { 
      if (msg为服务器关闭消息) 
        break; 
      处理msg消息; 
    } 

  这里就有一个问题需要探讨了,在getMessage()的时候,我们应该去哪里取消息?前面我们考虑过,至少会有三个消息来源,而我们还讨论过,这些消息源的IO操作都是在独立的线程中进行的,我们这里的主线程不应该直接去那几处消息源进行阻塞式的IO操作。 

  很简单,让那些独立的IO线程在接收完数据后自己送过来就是了。好比是,我这里提供了一个仓库,有很多的供货商,他们有货要给我的时候只需要交到仓库,然后我再到仓库去取就是了,这个仓库也就是消息队列。消息队列是一个普通的队列实现,当然必须要提供多线程互斥访问的安全性支持,其基本的接口定义大概类似这样: 

    IMessageQueue 
    { 
      void putMessage(Message*); 
      Message* getMessage(); 
    } 

  网络IO,数据库IO线程把整理好的消息包都加入到主逻辑循环线程的这个消息队列中便返回。有关消息队列的实现和线程间消息的传递在ACE中有比较完全的代码实现及描述,还有一些使用示例,是个很好的参考。 

  这样的话,我们的主循环就很清晰了,从主线程的消息队列中取消息,处理消息,再取下一条消息...... 

服务器公共组件实现 -- 消息队列 

  既然说到了消息队列,那我们继续来稍微多聊一点吧。 

  我们所能想到的最简单的消息队列可能就是使用stl的list来实现了,即消息队列内部维护一个list和一个互斥锁,putMessage时将message加入到队列尾,getMessage时从队列头取一个message返回,同时在getMessage和putMessage之前都要求先获取锁资源。 

  实现虽然简单,但功能是绝对满足需求的,只是性能上可能稍稍有些不尽如人意。其最大的问题在频繁的锁竞争上。 

  对于如何减少锁竞争次数的优化方案,Ghost Cheng提出了一种。提供一个队列容器,里面有多个队列,每个队列都可固定存放一定数量的消息。网络IO线程要给逻辑线程投递消息时,会从队列容器中取一个空队列来使用,直到将该队列填满后再放回容器中换另一个空队列。而逻辑线程取消息时是从队列容器中取一个有消息的队列来读取,处理完后清空队列再放回到容器中。 

  这样便使得只有在对队列容器进行操作时才需要加锁,而IO线程和逻辑线程在操作自己当前使用的队列时都不需要加锁,所以锁竞争的机会大大减少了。 

  这里为每个队列设了个最大消息数,看来好像是打算只有当IO线程写满队列时才会将其放回到容器中换另一个队列。那这样有时也会出现IO线程未写满一个队列,而逻辑线程又没有数据可处理的情况,特别是当数据量很少时可能会很容易出现。Ghost Cheng在他的描述中没有讲到如何解决这种问题,但我们可以先来看看另一个方案。 

  这个方案与上一个方案基本类似,只是不再提供队列容器,因为在这个方案中只使用了两个队列,arthur在他的一封邮件中描述了这个方案的实现及部分代码。两个队列,一个给逻辑线程读,一个给IO线程用来写,当逻辑线程读完队列后会将自己的队列与IO线程的队列相调换。所以,这种方案下加锁的次数会比较多一些,IO线程每次写队列时都要加锁,逻辑线程在调换队列时也需要加锁,但逻辑线程在读队列时是不需要加锁的。 

  虽然看起来锁的调用次数是比前一种方案要多很多,但实际上大部分锁调用都是不会引起阻塞的,只有在逻辑线程调换队列的那一瞬间可能会使得某个线程阻塞一下。另外对于锁调用过程本身来说,其开销是完全可以忽略的,我们所不能忍受的仅仅是因为锁调用而引起的阻塞而已。 

  两种方案都是很优秀的优化方案,但也都是有其适用范围的。Ghost Cheng的方案因为提供了多个队列,可以使得多个IO线程可以总工程师的,互不干扰的使用自己的队列,只是还有一个遗留问题我们还不了解其解决方法。arthur的方案很好的解决了上一个方案遗留的问题,但因为只有一个写队列,所以当想要提供多个IO线程时,线程间互斥地写入数据可能会增大竞争的机会,当然,如果只有一个IO线程那将是非常完美的。 

服务器公共组件实现 -- 环形缓冲区 

  消息队列锁调用太频繁的问题算是解决了,另一个让人有些苦恼的大概是这太多的内存分配和释放操作了。频繁的内存分配不但增加了系统开销,更使得内存碎片不断增多,非常不利于我们的服务器长期稳定运行。也许我们可以使用内存池,比如SGI STL中附带的小内存分配器。但是对于这种按照严格的先进先出顺序处理的,块大小并不算小的,而且块大小也并不统一的内存分配情况来说,更多使用的是一种叫做环形缓冲区的方案,mangos的网络代码中也有这么一个东西,其原理也是比较简单的。 

  就好比两个人围着一张圆形的桌子在追逐,跑的人被网络IO线程所控制,当写入数据时,这个人就往前跑;追的人就是逻辑线程,会一直往前追直到追上跑的人。如果追上了怎么办?那就是没有数据可读了,先等会儿呗,等跑的人向前跑几步了再追,总不能让游戏没得玩了吧。那要是追的人跑的太慢,跑的人转了一圈过来反追上追的人了呢?那您也先歇会儿吧。要是一直这么反着追,估计您就只能换一个跑的更快的追逐者了,要不这游戏还真没法玩下去。 

  前面我们特别强调了,按照严格的先进先出顺序进行处理,这是环形缓冲区的使用必须遵守的一项要求。也就是,大家都得遵守规定,追的人不能从桌子上跨过去,跑的人当然也不允许反过来跑。至于为什么,不需要多做解释了吧。 

  环形缓冲区是一项很好的技术,不用频繁的分配内存,而且在大多数情况下,内存的反复使用也使得我们能用更少的内存块做更多的事。 

  在网络IO线程中,我们会为每一个连接都准备一个环形缓冲区,用于临时存放接收到的数据,以应付半包及粘包的情况。在解包及解密完成后,我们会将这个数据包复制到逻辑线程消息队列中,如果我们只使用一个队列,那这里也将会是个环形缓冲区,IO线程往里写,逻辑线程在后面读,互相追逐。可要是我们使用了前面介绍的优化方案后,可能这里便不再需要环形缓冲区了,至少我们并不再需要他们是环形的了。因为我们对同一个队列不再会出现同时读和写的情况,每个队列在写满后交给逻辑线程去读,逻辑线程读完后清空队列再交给IO线程去写,一段固定大小的缓冲区即可。没关系,这么好的技术,在别的地方一定也会用到的。 

服务器公共组件实现 -- 发包的方式 

  前面一直都在说接收数据时的处理方法,我们应该用专门的IO线程,接收到完整的消息包后加入到主线程的消息队列,但是主线程如何发送数据还没有探讨过。 

  一般来说最直接的方法就是逻辑线程什么时候想发数据了就直接调用相关的socket API发送,这要求服务器的玩家对象中保存其连接的socket句柄。但是直接send调用有时候有会存在一些问题,比如遇到系统的发送缓冲区满而阻塞住的情况,或者只发送了一部分数据的情况也时有发生。我们可以将要发送的数据先缓存一下,这样遇到未发送完的,在逻辑线程的下一次处理时可以接着再发送。 

  考虑数据缓存的话,那这里这可以有两种实现方式了,一是为每个玩家准备一个缓冲区,另外就是只有一个全局的缓冲区,要发送的数据加入到全局缓冲区的时候同时要指明这个数据是发到哪个socket的。如果使用全局缓冲区的话,那我们可以再进一步,使用一个独立的线程来处理数据发送,类似于逻辑线程对数据的处理方式,这个独立发送线程也维护一个消息队列,逻辑线程要发数据时也只是把数据加入到这个队列中,发送线程循环取包来执行send调用,这时的阻塞也就不会对逻辑线程有任何影响了。 

  采用第二种方式还可以附带一个优化方案。一般对于广播消息而言,发送给周围玩家的数据都是完全相同的,我们如果采用给每个玩家一个缓冲队列的方式,这个数据包将需要拷贝多份,而采用一个全局发送队列时,我们只需要把这个消息入队一次,同时指明该消息包是要发送给哪些socket的即可。有关该优化的说明在云风描述其连接服务器实现的blog文章中也有讲到,有兴趣的可以去阅读一下。 

服务器公共组件实现 -- 状态机 

  有关State模式的设计意图及实现就不从设计模式中摘抄了,我们只来看看游戏服务器编程中如何使用State设计模式。 

  首先还是从mangos的代码开始看起,我们注意到登录服在处理客户端发来的消息时用到了这样一个结构体: 

  struct AuthHandler 
  { 
    eAuthCmd cmd; 
    uint32 status; 
    bool (AuthSocket::*handler)(void); 
  }; 

  该结构体定义了每个消息码的处理函数及需要的状态标识,只有当前状态满足要求时才会调用指定的处理函数,否则这个消息码的出现是不合法的。这个status状态标识的定义是一个宏,有两种有效的标识,STATUS_CONNECTED和STATUS_AUTHED,也就是未认证通过和已认证通过。而这个状态标识的改变是在运行时进行的,确切的说是在收到某个消息并正确处理完后改变的。 

  我们再来看看设计模式中对State模式的说明,其中关于State模式适用情况里有一条,当操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态,这个状态通常用一个或多个枚举变量表示。 

  描述的情况与我们这里所要处理的情况是如此的相似,也许我们可以试一试。那再看看State模式提供的解决方案是怎样的,State模式将每一个条件分支放入一个独立的类中。 

  由于这里的两个状态标识只区分出了两种状态,所以,我们仅需要两个独立的类,用以表示两种状态即可。然后,按照State模式的描述,我们还需要一个Context类,也就是状态机管理类,用以管理当前的状态类。稍作整理,大概的代码会类似这样: 

  状态基类接口: 
  StateBase 
  { 
    void Enter() = 0; 
    void Leave() = 0; 
    void Process(Message* msg) = 0; 
  }; 

  状态机基类接口: 
  MachineBase 
  { 
    void ChangeState(StateBase* state) = 0; 

    StateBase* m_curState; 
  }; 

  我们的逻辑处理类会从MachineBase派生,当取出数据包后交给当前状态处理,前面描述的两个状态类从StateBase派生,每个状态类只处理该状态标识下需要处理的消息。当要进行状态转换时,调用MachineBase的ChangeState()方法,显示地告诉状态机管理类自己要转到哪一个状态。所以,状态类内部需要保存状态机管理类的指针,这个可以在状态类初始化时传入。具体的实现细节就不做过多描述了。 

  使用状态机虽然避免了复杂的判断语句,但也引入了新的麻烦。当我们在进行状态转换时,可能会需要将一些现场数据从老状态对象转移到新状态对象,这需要在定义接口时做一下考虑。如果不希望执行拷贝,那么这里公有的现场数据也可放到状态机类中,只是这样在使用时可能就不那么优雅了。 

  正如同在设计模式中所描述的,所有的模式都是已有问题的另一种解决方案,也就是说这并不是唯一的解决方案。放到我们今天讨论的State模式中,就拿登录服所处理的两个状态来说,也许用mangos所采用的遍历处理函数的方法可能更简单,但当系统中的状态数量增多,状态标识也变多的时候,State模式就显得尤其重要了。 

  比如在游戏服务器上玩家的状态管理,还有在实现NPC人工智能时的各种状态管理,这些就留作以后的专题吧。 

服务器公共组件 -- 事件与信号 

关于这一节,这几天已经打了好几遍草稿,总觉得说不清楚,也不好组织这些内容,但是打铁要趁热,为避免热情消退,先整理一点东西放这,好继续下面的主题,以后如果有机会再回来完善吧。本节内容欠考虑,希望大家多给点意见。 

有些类似于QT中的event与signal,我将一些动作请求消息定义为事件,而将状态改变消息定义为信号。比如在QT应用程序中,用户的一次鼠标点击会产生一个鼠标点击事件加入到事件队列中,当处理此事件时可能会导致某个按钮控件产生一个clicked()信号。 

对应到我们的服务器上的一个例子,玩家登录时会发给服务器一个请求登录的数据包,服务器可将其当作一个用户登录事件,该事件处理完后可能会产生一个用户已登录信号。 

这样,与QT类似,对于事件我们可以重定义其处理方法,甚至过滤掉某些事件使其不被处理,但对于信号我们只是收到了一个通知,有些类似于Observe模式中的观察者,当收到更新通知时,我们只能更新自己的状态,对刚刚发生的事件我不已不能做任何影响。 

仔细来看,事件与信号其实并无多大差别,从我们对其需求上来说,都只要能注册事件或信号响应函数,在事件或信号产生时能够被通知到即可。但有一项区别在于,事件处理函数的返回值是有意义的,我们要根据这个返回值来确定是否还要继续事件的处理,比如在QT中,事件处理函数如果返回true,则这个事件处理已完成,QApplication会接着处理下一个事件,而如果返回false,那么事件分派函数会继续向上寻找下一个可以处理该事件的注册方法。信号处理函数的返回值对信号分派器来说是无意义的。 

简单点说,就是我们可以为事件定义过滤器,使得事件可以被过滤。这一功能需求在游戏服务器上是到处存在的。 

关于事件和信号机制的实现,网络上的开源训也比较多,比如FastDelegate,sigslot,boost::signal等,其中sigslot还被Google采用,在libjingle的代码中我们可以看到他是如何被使用的。 

在实现事件和信号机制时或许可以考虑用同一套实现,在前面我们就分析过,两者唯一的区别仅在于返回值的处理上。 

另外还有一个需要我们关注的问题是事件和信号处理时的优先级问题。在QT中,事件因为都是与窗口相关的,所以事件回调时都是从当前窗口开始,一级一级向上派发,直到有一个窗口返回true,截断了事件的处理为止。对于信号的处理则比较简单,默认是没有顺序的,如果需要明确的顺序,可以在信号注册时显示地指明槽的位置。 

在我们的需求中,因为没有窗口的概念,事件的处理也与信号类似,对注册过的处理器要按某个顺序依次回调,所以优先级的设置功能是需要的。 

最后需要我们考虑的是事件和信号的处理方式。在QT中,事件使用了一个事件队列来维护,如果事件的处理中又产生了新的事件,那么新的事件会加入到队列尾,直到当前事件处理完毕后,QApplication再去队列头取下一个事件来处理。而信号的处理方式有些不同,信号处理是立即回调的,也就是一个信号产生后,他上面所注册的所有槽都会立即被回调。这样就会产生一个递归调用的问题,比如某个信号处理器中又产生了一个信号,会使得信号的处理像一棵树一样的展开。我们需要注意的一个很重要的问题是会不会引起循环调用。 

关于事件机制的考虑其实还很多,但都是一些不成熟的想法。在上面的文字中就同时出现了消息、事件和信号三个相近的概念,而在实际处理中,经常发现三者不知道如何界定的情况,实际的情况比我在这里描述的要混乱的多。 

这里也就当是挖下一个坑,希望能够有所交流。 

再谈登录服的实现 

    离我们的登录服实现已经太远了,先拉回来一下。 
    
    关于登录服、大区服及游戏世界服的结构之前已做过探讨,这里再把各自的职责和关系列一下。 

        GateWay/WorldServer   GateWay/WodlServer LoginServer LoginServer DNSServer WorldServerMgr 
                |                     |                     |                 |            | 
      --------------------------------------------------------------------------------------------- 
                                             | | | 
                                             internet 
                                                | 
                                              clients 

    其中DNSServer负责带负载均衡的域名解析服务,返回LoginServer的IP地址给客户端。WorldServerMgr维护当前大区内的世界服列表,LoginServer会从这里取世界列表发给客户端。LoginServer处理玩家的登录及世界服选择请求。GateWay/WorldServer为各个独立的世界服或者通过网关连接到后面的世界服。 

    在mangos的代码中,我们注意到登录服是从数据库中取的世界列表,而在wow官方服务器中,我们却会注意到,这个世界服列表并不是一开始就固定,而是动态生成的。当每周一次的维护完成之后,我们可以很明显的看到这个列表生成的过程。刚开始时,世界列表是空的,慢慢的,世界服会一个个加入进来,而这里如果有世界服当机,他会显示为离线,不会从列表中删除。但是当下一次服务器再维护后,所有的世界服都不存在了,全部重新开始添加。 

    从上面的过程描述中,我们很容易想到利用一个临时的列表来保存世界服信息,这也是我们增加WorldServerMgr服务器的目的所在。GateWay/WorldServer在启动时会自动向WorldServerMgr注册自己,这样就把自己所代表的游戏世界添加到世界列表中了。类似的,如果DNSServer也可以让LoginServer自己去注册,这样在临时LoginServer时就不需要去改动DNSServer的配置文件了。 

    WorldServerMgr内部的实现很简单,监听一个固定的端口,接受来自WorldServer的主动连接,并检测其状态。这里可以用一个心跳包来实现其状态的检测,如果WorldServer的连接断开或者在规定时间内未收到心跳包,则将其状态更新为离线。另外WorldServerMgr还处理来自LoginServer的列表请求。由于世界列表并不常变化,所以LoginServer没有必要每次发送世界列表时都到WorldServerMgr上去取,LoginServer完全可以自己维护一个列表,当WorldServerMgr上的列表发生变化时,WorldServerMgr会主动通知所有的LoginServer也更新一下自己的列表。这个或许就可以用前面描述过的事件方式,或者就是观察者模式了。 

    WorldServerMgr实现所要考虑的内容就这些,我们再来看看LoginServer,这才是我们今天要重点讨论的对象。 

    前面探讨一些服务器公共组件,那我们这里也应该试用一下,不能只是停留在理论上。先从状态机开始,前面也说过了,登录服上的连接会有两种状态,一是帐号密码验证状态,一是服务器列表选择状态,其实还有另外一个状态我们未曾讨论过,因为它与我们的登录过程并无多大关系,这就是升级包发送状态。三个状态的转换流程大致为: 

        LogonState -- 验证成功 -- 版本检查 -- 版本低于最新值 -- 转到UpdateState 
                                          | 
                                           -- 版本等于最新值 -- 转到WorldState 

    这个版本检查的和决定下一个状态的过程是在LogonState中进行的,下一个状态的选择是由当前状态来决定。密码验证的过程使用了SRP6协议,具体过程就不多做描述,每个游戏使用的方式也都不大一样。而版本检查的过程就更无值得探讨的东西,一个if-else即可。

    升级状态其实就是文件传输过程,文件发送完毕后通知客户端开始执行升级文件并关闭连接。世界选择状态则提供了一个列表给客户端,其中包括了所有游戏世界网关服务器的IP、PORT和当前负载情况。如果客户端一直连接着,则该状态会以每5秒一次的频率不停刷新列表给客户端,当然是否值得这样做还是有待商榷。 

    整个过程似乎都没有值得探讨的内容,但是,还没有完。当客户端选择了一个世界之后该怎么办?wow的做法是,当客户端选择一个游戏世界时,客户端会主动去连接该世界服的IP和PORT,然后进入这个游戏世界。与此同时,与登录服的连接还没有断开,直到客户端确实连接上了选定的世界服并且走完了排队过程为止。这是一个很必要的设计,保证了我们在因意外情况连接不上世界服或者发现世界服正在排队而想换另外一个试试时不会需要重新进行密码验证。 

    但是我们所要关注的还不是这些,而是客户端去连接游戏世界的网关服时服务器该如何识别我们。打个比方,有个不自觉的玩家不遵守游戏规则,没有去验证帐号密码就直接跑去连接世界服了,就如同一个不自觉的乘客没有换登机牌就直接跑到登机口一样。这时,乘务员会客气地告诉你要先换登机牌,那登机牌又从哪来?检票口换的,人家会先验明你的身份,确认后才会发给你登机牌。一样的处理过程,我们的登录服在验明客户端身份后,也会发给客户端一个登机牌,这个登机牌还有一个学名,叫做session key。 

    客户端拿着这个session key去世界服网关处就可正确登录了吗?似乎还是有个疑问,他怎么知道我这个key是不是造假的?没办法,中国的假货太多,我们不得不到处都考虑假货的问题。方法很简单,去找给他登机牌的那个检票员问一下,这张牌是不是他发的不就得了。可是,那么多的LoginServer,要一个个问下来,这效率也太低了,后面排的长队一定会开始叫唤了。那么,LoginServer将这个key存到数据库中,让网关服自己去数据库验证?似乎也是个可行的方案。 

    如果觉得这样给数据库带来了太大的压力的话,也可以考虑类似WorldServerMgr的做法,用一个临时的列表来保存,甚至可以将这个列表就保存到WorldServerMgr上,他正好是全区唯一的。这两种方案的本质并无差别,只是看你愿意将负载放在哪里。而不管在哪里,这个查询的压力都是有点大的,想想,全区所有玩家呢。所以,我们也可以试着考虑一种新的方案,一种不需要去全区唯一一个入口查询的方案。 

    那我们将这些session key分开存储不就得了。一个可行的方案是,让任意时刻只有一个地方保存一个客户端的session key,这个地方可能是客户端当前正连接着的服务器,也可以是它正要去连接的服务器。让我们来详细描述一下这个过程,客户端在LoginServer上验证通过时,LoginServer为其生成了本次会话的session key,但只是保存在当前的LoginServer上,不会存数据库,也不会发送给WorldServerMgr。如果客户端这时想要去某个游戏世界,那么他必须先通知当前连接的LoginServer要去的服务器地址,LoginServer将session key安全转移给目标服务器,转移的意思是要确保目标服务器收到了session key,本地保存的要删除掉。转移成功后LoginServer通知客户端再去连接目标服务器,这时目标服务器在验证session key合法性的时候就不需要去别处查询了,只在本地保存的session key列表中查询即可。 

    当然了,为了session key的安全,所有的服务器在收到一个新的session key后都会为其设一个有效期,在有效期过后还没来认证的,则该session key会被自动删除。同时,所有服务器上的session key在连接关闭后一定会被删除,保证一个session key真正只为一次连接会话服务。 

    但是,很显然的,wow并没有采用这种方案,因为客户端在选择世界服时并没有向服务器发送要求确认的消息。wow中的session key应该是保存在一个类似于WorldServerMgr的地方,或者如mangos一样,就是保存在了数据库中。不管是怎样一种方式,了解了其过程,代码实现都是比较简单的,我们就不再赘述了。 

posted @ 2011-09-23 18:21 IT菜鸟 阅读(441) | 评论 (1)编辑 收藏

2011年9月6日 #

1、倒转单链表

     非递归法

    

  1. struct Node  
  2. {  
  3.   int data;  
  4.   Node * next;  
  5. }  
  6. void reverse(Node*& head) {  
  7.   if (head == NULL) {  
  8.      return;  
  9.   }  
  10.   Node * pre, * cur, * nxt;  
  11.   pre = head;  
  12.   cur = head -> next;  
  13.   while (cur != NULL) {  
  14.      pre = cur -> next;  
  15.      pre = cur;  
  16.      cur = nxt;  
  17.      nxt = nxt -> next;  
  18.   }  
  19.   head -> next = NULL;  
  20.   head = pre;  
  21. }  
 

 

 

     递归法

    

  1. Node * reverse(Node * node, Node *&head)  
  2. {  
  3.      if (node == NULL || node -> next == NULL) {  
  4.          head = node;  
  5.          return;  
  6.      }  
  7.      else {  
  8.         Node * nxt = reverse(node -> next, head)  
  9.         node = nxt -> next;  
  10.         return node;  
  11.      }  
  12. }  
  13. 最后要将返回的node的next域设为NULL。  

 

 

2、找出倒数第k个元素(或中间元素):设置快慢指针

3、删除环状单链表的一个节点,只给定指向要删除节点的指针

     思路:无法获取前一个指针,那么可删除下一个节点,交换当前节点和下一个节点的值即可

4、两个有序链表的合并

     归并即可

 

5、判断单链表是否有环?环首?

     是否有环:快慢指针,如果相遇,说明存在环

     环首:可先求出环长。一个圆内的追及问题,两次相遇时慢指针走过的距离即环长;

              此时假设慢指针已走了i步,则快指针已走了2i步;两个指针同时倒退i步的位置,慢指针退到链首,快指针退到i处,则从这两个位置起,指针各走i步既能在环内相遇。

              于是可设置两个指针,一个在链首处,一个在i处,步长为1,第一次相遇的节点即为环首的位置。

 

6、如何判断两个链表是否交叉?如果找到交叉点?
     思路同5

posted @ 2011-09-06 17:33 IT菜鸟 阅读(219) | 评论 (0)编辑 收藏