在咱们初期学习Unity3D培训目标:让U3D初学者能够更快速的掌握U3D技术,自行制做修改素材,能够独立完成2D、3D小规模游戏及网页游戏开发。后面就应该朝着主程的方面前进php
今天给你们讲一下如何作一个好的主程前端
假如,我如今接手一个新项目,个人身份仍是主程序。在下属人员一一到位以前,在和制做人以及主策划充分沟通后,我须要先独自思考如下问题:java
一、服务器跑在什么样的操做系统环境下?
二、采用哪几种语言开发?主要是什么?
三、服务器和客户端以什么样的接口通信?
四、采用哪些第三方的类库?mysql
除了技术背景以外,考虑这些问题的时候必定要充分考虑项目需求和所能拥有的资源。程序员
我以为,先不要想一组须要几台机器各有什么功能这样的问题,也不要想须要多少个daemon进程。假设就一台服务器,就一个进程,把所须要的资源往最小了考虑,把架构往最简单的方向想,直到发现,“哦,这么作没法知足策划要求的并发量”,再去修改设计方案。web
操做系统:越单一越好。虽然FreeBSD的网络性能更好、虽然Solaris很是稳定,但选什么就是什么,最好别混着来。前端是FreeBSD,后端是Solaris,运营的人会苦死。也不要瞧不起用Windows的人,用Windows照样也能支持一组一万人在线,总之,能知足策划需求,好招程序员,运营成本低是要点。不一样的操做系统有不一样的特性,若是你真的对它们都很熟悉,那么一定能找到一个理由,一个足够充分的理由让你选择A而不是B而不是C。但作决策的时候要注意不要因小失大。面试
Programming Language:传统来讲,基本都是C/C++。可是你也知道,这东西门槛很高,好的C/C++程序员很难招。用Perl/Python/Lua行不行?固然能够。可是纯脚本也很差,一般来讲是混合着来。你要明白哪些是关键部分,我是说执行次数最多的地方而不是说元宝,这些必须用性能高的语言实现(好比C/C++好比Java),其它像节日活动这样好久才执行一次的,随便吧。脚本的好处是,能够快速搭原型。因此,尽早的,在你作完基本的地图和战斗模块以后,立马跑机器人测试吞吐量。这时候项目开发进度还不到10%,不行就赶忙改。
此处特别举个例子就是Java GC的问题。既然你要用java,而jvm须要经过执行garbage collection来回收内存,而garbage collection会使整个应用停顿,那你不妨试一试,内存在达到峰值的时候会停多久?策划能够接受吗?若是不能够,你能够采用其它的GC策略再试一试。这个问题应该不是Java独有的。网游和网站应用相比它很注重流畅性。这是你务必须要考虑的。sql
至于选择什么样的脚本语言,以及脚本在你的游戏中到底是占80%仍是20%?须要根据需求来看。有没有游戏彻底不用脚本?有。有没有游戏滥用脚本?也有。若是你引入脚本的目的是由于策划不会C/C++而你但愿策划能本身独立实现更多的游戏功能。你但愿策划去写脚本?脚本也是程序,策划写的脚本难道就比程序员写脚本好?仍是由于策划工资便宜?策划由于脚本写错了致使大故障还少吗(此处特别以网易的产品举例)?综合权衡下,仍是算了吧。问问你一块儿工做的程序员哥们儿,他们最喜欢什么语言,什么用起来最顺手,就用什么当脚本。注意不光要考虑开发速度快,还要考虑调试方便。数据库
整体来讲,操做系统和编程语言的选择,随大流便可。标新立异没什么好处。小地方的实现你能够玩玩,总体仍是要越保守越好。编程
而后说通信的问题。服务器和客户端怎么链接上的?
往最下面看,物理和链路层。有多是以太网,有多是ADSL,在北京还有不少像歌华宽带这样的采用75欧同轴电缆或者电力线上网的。你不要企图在这一层作什么优化,你要充分考虑的是不一样的网络传输媒质网络延迟不同。更恶心的是你正常的数据包可能会被某些网吧的SB路由器当作P2P数据包给封掉,或是甚至被解析成Wake-On-Lan这样的含义。杨建还会给你讲,什么是MTU,把数据包限制在多大才能尽可能让请求在一个包内发完。是的,这些很精细的东西,等咱游戏作的差很少了再慢慢研究。先略过。
往上看,IP层。再往上,你要考虑用TCP仍是UDP或是两者混合。UDP的优点是overhead小、延迟低,典型的用例就是《天下贰》,听说是纯UDP。再好比《龙之谷》,听说是有小部分是UDP。负面的一点呢,就是它太过于简单因此用起来太过于复杂。你要是对本身没信心,TCP吧,随大流就好。
往上,采用什么样的应用协议。大多数rpc协议都是既支持TCP又支持UDP的。我所用过的有sun rpc、corba、webservice、json、java RMI以及一些专有协议。若是你有精力,仍是本身搞一套吧,网游所用的东西,仍是越专有越好,给抓包作外挂的人加一点门槛。这里很是强调的一点,你采用什么样的序列化方式与你采用什么样的网络协议是无关的,你的应用协议和你传输协议应该也是无关的(既支持TCP又支持UDP的)。若是作框架的人把本身限制的太死或者耦合太紧,那么用框架的人会很是痛苦。因此,不必在此为了性能作过多优化。结构简单清晰是王道。
不少人对网络开发的认识还停留在定义一个struct、memcpy到socket buffer、send,而后一个劲的给别人强调遇到指针怎么办、数组的长度不能超过多少、整个包的长度不能超过多少等等。序列化实际上是面向对象程序设计的一个很核心的要素。连glib/gtk/Berkeley DB这些纯C的框架都是基于OOP设计的,因此我以为您就算是C程序员也不必排斥它。我讲这个是说,你应当作应用的人尽量的避免用memcpy/memset这样的方式初始化数据、传送数据。若是你是C程序员,你多提供一些g_object_new这样的函数;若是你是C++程序员,写好你的构造和析构函数;若是你是JAVA程序员还死活不懂OOP,那算了吧,改行吧。
网络这一层有些很精妙的东西,尤为是当你规模扩大须要分布式扩展的时候。你想一想看为何sun rpc须要先去rpcbind询问一次而后才连真正的进程呢?RMI返回的时候为何须要同时返回IP和端口号呢?web service那么通用,大部分浏览器都支持直接从浏览器调用web service那么为何主流的方式倒是json呢?
sun rpc是全部RPC机制中历史最久的吧?它在设计初版的时候,每一个rpc调用都是由一问一答来组成,称为two-way messaging。客户端在发出请求以后,一直等服务器的答复,若是一直到指定时间后依然没收到答复,那么执行timeout逻辑。在第一个请求收到答复(或者timeout)以前,没法发起第二个答复。直到某一天,Sun的程序发现他们须要异步处理一些事情,因而设计了one-way messaging,客户端在发起请求的时候,只要把这个东西塞到本地的IO队列里,就返回。可是若是socket buffer满了怎么办?仍是会等在那里。因而以为这个还不完全,因而又作了Non-Blocking Messaging,在kernel的socket buffer前面加了一个用户态的rpc buffer,大多数时候它都是空的,当socket buffer堆满了的时候,再往这里面塞。若是这个buffer也满了怎么办?我以为无非就三种处理手段:
1、阻塞。若是这么作,就是说原本是套非阻塞的设计可是某些状况下仍是会阻塞?那么给用的人解释起来太麻烦用起来也太麻烦。算了。
2、悄然丢弃。 不是全部的数据均可以丢。聊天的无所谓,可是交易的就不行。因此须要在消息类型上加判断。
3、关闭链接。 最简单粗暴,却也最有效。
在使用two-way messaging的时候,必定要记住设置超时,免得像某些傻瓜同样由于一个请求把整个server堵死。可是我以为timeout设多久彻底是个经验值,太大了没做用,过小了失败的太多。
至少在有一点咱们能够大松一口气,就是不用担忧数据量大到须要多网卡同时分担中断。一般来讲网络游戏的流量都是很小的,对玩家来讲一个56K的猫或者128K的DSL就够了。若是你的策划给你提了一个很BT的需求致使要耗费大量带宽,那么你最好把这个应用分到单独的tcp 链接上,免得由于它阻塞而致使关键的业务(好比地图消息)停滞。
我一直想把rpc的部分实现塞到kernel里。对客户端的好处是增长了逆向工程的成本,对服务器的好处是网关能够很高效。就像LVS那样,前端收完包以后在kernel里处理完而后马上转出去,不用切换到用户态。而GameServer处理完以后,甚至不用通过网关,直接回复。目的不在于分担网关的压力,而是说下降响应延迟。就算让GameServer承担部分加密和压缩的计算量,它的CPU也足够用。
不过对于网游,考虑动态扩容为时太早。通常都是新开几组服务器。
我在作服务器安装包的时候,分的很清楚:程序、配置文件、数据库。
程序,就是编译好的二进制文件。最好是全静态编译,由于它简单。动态连接的优势以及其它一些高级话题我后面讲,可是一般来讲,动态的复杂的结构得不偿失。
配置文件整体来讲能够分为文本文件和二进制文件(废话)。文本文件的好处是开发过程当中易于调试和修改,最终发布后也易于追踪问题。二进制文件的好处是小、精巧、不易把信息泄露给外人知道。java的打jar包的技术算是一个折衷的优点吧?我最看重的是易于调试和修改,因此基本都用文本文件。而这其中,表现力最强的就是xml,因此基本都是xml。
可是xml多了怎么管理就是个问题。我得整理份文档,每一个xml都是什么格式,作什么用途的,最好每一个xml再写一个xsd。事实是配置文件是随着需求变化最频繁的部分,而换个角度说我以前强调的序列化。因此,正确的思路是这样:
一、程序员分析需求文档,肯定须要什么样的对象来表示配置
二、某套序列化框架,它利用某种xml解析库把xml变成内存中的对象
三、策划提供xml
只要这个框架作的好,根本不须要文档或xsd来描述xml。我这里说策划提供xml,那么策划怎么提供xml呢?按照我所看见的策划的习惯,他们最喜欢的是两种方式:
一、对于结构简单的数据,编辑excel表
二、对于结构复杂的(如涉及树、环的),提供专门的编辑工具
对于1,咱们能够给excel作plugin,或者作一个工具从excel表导出成xml。对于2,让编辑工具能够导出成xml。可是最终很重要很重要很重要的一点就是要让全部的工具集成在一块儿,作好版本管理以及跨版本diff和merge。如何管理数据要好比何定义数据如何描述数据更难更重要。
不少同事和个人共识都是:要作一款好游戏,工具很重要。多个项目作完后,外人能看见的最大的积累就是工具和流程。
数据库在游戏中的重要性,是一个很使人玩味的东西。你能够听见不少人告诉你说,咱们作游戏根本不须要数据库。是的,像单机游戏那样,在某个目录下建立一个文件,save/load就好了。这就是我所看到的当今的大型网游的主流作法。
哦,你要反对了。你说你知道某某游戏用的是mysql,某某游戏用的是oracle,等等。是的,你手上的信息可能比我多不少不少倍,可是关键点在于,数据库在整个系统中的角色究竟是什么?
典型的场景是这样:启动一个单独的进程称之为DB Gate。当用户登陆的时候,逻辑服务器找DB Gate要数据,DB Gate没有因而就去找后面的Mysql要,而后读过来以后就放在这里,DB Gate就是一个相似于memcached的东西。因此后面不管是用mysql仍是oracle仍是plain text均可以,但实际上会在其它方面有些细微的差异。
它和网站应用相比,数据更容易作cache,把握好上线和下线这两个点便可,cache的命中率很容易达到4个9或者更高。可是从另外一个方面,网络游戏的数据关联逻辑远远比网站复杂,并且对原子性、一致性、隔离性要求更高。如今是你本身来管理cache,因而并发控制就没办法交给数据库来作。
问题一:我不本身作cache,我就直接读写数据库。就像php+mysql那样,中间也不套memcache,行不行? 我不知道。你能够试一试。
问题二:SQL or NoSQL ? 我仍是回答不了。你作个demo跑机器人试一试。
总之,东西是活的。没有必要非要怎么着非不能怎么着。检验的标准很简单:一、是否完成了策划提出的功能需求 二、效率是否达到了预期目标
对于第一个,QA和策划都会去检查。对于2,跑机器人以及封测期间调优是王道。
对于数据库开发,我仍是很强调面向对象那套观点。把数据库里的表映射到对象,把对象抽象成接口,每一个模块以接口对外提供服务,不一样模块不要直接经过表共享数据。或者,你能够读个人表,但不要写!由于数据的约束条件未必是能够由DBMS彻底保证的,某些约束是难以用数据库自己的语言表述的。
数据是网游的核心,网游基本都是数据驱动的,因此数值策划才会这么吃香。
或者换个角度想,DBMS它是什么?
一、它管理数据。帮助咱们高效的读取和修改数据。由于数据的动态性,因此咱们须要Btree这样的结构,而不是随便找个TXT追加写。可是换个角度想,网络游戏有什么特色?插入多,可是删除操做极少极少。那么是否能够采用其它的结构呢?顺序重要吗?为何不用Hash呢?
二、它负责备份和恢复数据。这基本是任何现代的数据库系统必须提供的基本功能。可是网络游戏又特殊一点,它要求能按指定时间“回档”。时间能够有半小时的偏差,可是这个功能必须有。因而数据库能支持增量备份,或者它的备份能支持版本很重要。
三、它使用logging system保证在忽然宕机的时候数据依然是完整和一致的。但是若是咱们要本身作cache,那么就要求咱们在应用层面所作的原子性保证必须在cache中也能体现出来。这些cache要么全刷,要么全不刷。
四、它提供并发功能。拿传统的php+mysql架构来讲,为何同一个应用能够被分布式的部署在多台机器上?魔力就在数据库上。
既然有人轻视数据库,那么也可反其道重视数据库。把90%的逻辑都放在数据库里完成。多招一些熟悉SQL熟悉存储过程的,主要的逻辑都由他们完成。
接着说我在并发上的考虑。
一台机器仍是多台机器?单进程仍是多进程?单线程仍是多线程?等等。
我以为并发问题是最没章法可循的问题。你能够这么作也能够那么作。网络游戏的重点是在逻辑开发上,而作逻辑开发的人不要关心究竟是epoll仍是select。总之制定框架的时候须要定好一个规矩:单线程仍是多线程、访问哪些数据的时候须要加锁(可能还须要跨进程的加锁)、谁来作load balancer、若是有一台机器宕了怎么办、哪些任务必需要以特定的顺序执行,等等。规矩定下来,一切都顺了。可这个规矩要足够的简单。
若是是多线程,我想过两种模式:Thread per Connection和Task based thread pool。如今机器的内存愈来愈大了,因此前者的开销是能够忍受的,1000人在线,就算每一个线程要被系统占去2M,那么也才2G。而通常的3D游戏作个 3-4千人在线就好了,配个大内存的机器,还剩下足够多的内存给应用使用。多简单啊!网络游戏中,不少请求都是只须要访问单个角色的数据就够了,反过来讲不少数据均可以作成Thread Local的,免去了同步代价。
而Task based thread pool的伸缩性相对来讲就好的多,可是并发问题也麻烦一些,何况从rpc请求被unmarshal完到扔到task pool里面又多了一次线程切换,若是换成Leader-Follower那样的模式,少了切换可是模型又更复杂了一些。
若是是单线程的,那么一切都是事件驱动的而且事件的处理都是非阻塞的。那么就得避开数据库读写或者在处理的过程当中再产生新的rpc请求,不然很是麻烦。
并发问题的瓶颈每每是在于怎么下降锁冲突上。Task Pool里面的全部线程都在执行Task,可是都在等同一把锁,多悲剧啊。难点在于下降模块耦合、采用适当的排队机制等等。我以为这里没有什么万金油,下降模块耦合原本就没什么套路可循,而排队机制有不少种,没有最好的,各有利弊。
对于死锁,个人容忍度比之前大了不少。我以为每台机器天天的死锁数量在10个之内都是能够忍受的,要有死锁检测、打断机制而且重作的时候不会产生反作用。对玩家的感觉而言就是忽然卡了一下,但是网络不也常常会忽然卡一下吗?不频繁就好。
我最钟爱的模式就是“生产者-消费者”模式,万能的利器。例如Task Pool就是基于这样的模式。它的核心东西无非就是一个队列,若是要支持定时,那么就是一个优先队列(deadline time做为优先级)。讲个细节,我面试的时候问了不少面试者,优先队列应该用什么样的数据结构实现,结果都挺让我失望的。
顺便发个牢骚,Sun JDK的executor的实现,BUG太多了。还那么巧,都被我赶上了?
说些杂七杂八的东西吧。
我刚入行的时候就一直在问,为何网游服务器常常要停机维护?为何常常都是好几个小时?为何非要分红不一样组的服务器而且数据基本不互通?为何不构造一个大世界把全部玩家放在一块儿?
我如今不问了,这些问题基本都找到了答案。不是技术作不到,并且有不少它之外的东西在左右这些。至少我在尽力不回档这件事情上已经作的比较好了。
我想说的就是,入这行就得遵照这行的规矩。若是你是个老手了,根本不必来看我这一系列的P话。若是你是新手,那么我是在向你介绍现状。策划是甲方,咱们是乙方,在尽力知足策划的需求且不会显著增长成本的前提下作有限的创新,这是我给本身定的设计原则。
(支付宝刚通知我,我又收到了5块钱的捐赠。谢谢,谢谢你们)
若是你是一个受过良好训练的程序员,那么如下基本规则是懂的:
一、不要把须要翻译的常量字符串写在代码里
二、不要直接在代码中间写498595这样的magic number
三、向版本控制系统提交代码的时候应该写注释
四、需求是常常变的,而且常常是灾难性的
可每每知道是一回事儿,作又是另一回事。尤为是不要相信策划那张嘴,写成word文档才算数。
和你们分享一些我在版本控制上的经验和教训。
最先接触这个问题,是在sina的时候,由QA部门的同事以及周琦单独专门给我讲jira、svn。当时受益很大。
周琦一再给我强调,在产品生命周期中,源代码版本管理和发布部署是独立的两套东西。源代码版本管理是用subversion这样的东西来作(更早一点咱们还在用cvs)。发布部署,一是编译的过程,二是对外推送部署的过程,是一套相对独立的东西。周琦的特点在于他把这两者经过svn hook脚本的方式给自动串起来了。
我一直想要作一套OBS这样的东西找一台服务器专门做build server,惋惜一直没时间去写。就本身写了一个脚本(原本是sh的,后来成perl,后来成groovy),它的做用是根据分支名和版本号从subversion下载代码,而后编译,而后放到指定位置。而后通知发布服务器从那里拿东西推到外边。缺点它缺少并发控制,而且没有UI界面。致使作完以后就成我的专属的了。
为何每次要选择一个空目录checkout而后编译,而不是在上次的基础上svn up而后编译?这个和Java/Ant有点关系。在写Makefile的时候,尽管能够指定把当前目录下的.cpp文件所有都编译,可是这是不推荐的作法。由于相比于写代码的时间,把代码文件添加到Makefile中的时间能够忽略不计。而我当时给ant写build.xml时,是用**/*.java的方式去匹配,因而把src下的全部能编译的全编译了。可我在编译以前会执行一些脚本用于生成一些代码,某些是单独存放的,可是某些和其它手写的代码放在了一块儿。因此为了保持最终的jar包干净,宁肯牺牲编译的时间。
在提供给QA的测试环境中能够很方便的经过GM指令获得版本号,这个是编译的时候打包工具写进去的。而编译系统务必保证相同版本号的东西每次编译出来都是相同的东西。虽然二进制比对结果可能不一致,可是逻辑功能上是一致的。
对于svn的分支管理,有两种广泛策略:
一、每一个人一个单独的分支。作完本身的功能后往主干merge
二、都在主干上工做。须要发版本的时候建立新分支。
前一种须要你们都比较熟悉svn的用法,熟悉版本管理的基本概念。后一种则把全部活堆给一个专门发版本的人。他来建立分支,他来merge(或是谁的功能谁merge)。而且这样的话,绝大多数代码是不须要merge的,因此我根据实际状况选择了后一种。
因而在正在运行的系统中发现bug的时候,立马获取版本号,从那个版本上建立分支而且把分支名喊一声告诉你们,而后找问题,把补丁merge到过去,编译,发布,测试,推到外面。
发版本很累,这件事情在去年秋天上线后,一直到春节,占去了我90%的精力。其中最重要的就是比对功能和bug列表。常常,你分不清楚这到底算是一个bug呢,仍是提需求的时候就没说清楚因此这是一个新功能,反正都列一块儿的。挨个和svn提交记录比对。
部署也是一个颇有讲究的过程。个人原则是,先删除老的程序和配置文件,而后复制新的过去,数据库的数据和日志文件保留,审计日志保留。这件事情原本还争论过老的要不要删,可不能够直接覆盖,最终他们答应了个人需求。过程挺曲折的,中间有不少恶心的细节问题,好比NFS的本地cache的问题。
对于数据库,咱们能智能的感知数据库结构更改并自动生成升级脚本(天哪,我这算不算泄密)。这竟然也是一把双刃剑。优势是减轻了开发人员的工做量,缺点是更改数据库变得太随意,随意的添表添字段致使数据膨胀的厉害。
个人遗憾是没有把上面这些东西和数据编辑器串起来。那么作有点是数值策划调整数据更容易看到真实效果,缺点是也很容易乱来。若是这中间要通过svn,那么太慢太曲折。若是这中间不通过svn,那么鬼知道他们如今测的是什么版本的东西,他常常会发现最终出去的东西跟他当时测的仍是不同,毕竟,是不少人在同一个服务器上测试。很难给他们解释这个事情。
因此我当时还漏了一个东西一直想作可是没作,就是一个很简单的web gui能让全部策划本身启动、中止服务器,本身编译、同步数据。各弄各的,互不干扰。可是吧,策划毕竟是策划,它们缺少基本的QA知识。他们不明白为何一个底层功能好好的怎么忽然就很差使了(由于上层某处要加新功能,因此底下的代码要重构),他们不明白为了一个bug被改掉以后反复又出现了,甚至对于分支和版本号这个东西,绝大多数策划都理解起来困难。可是整个产品的开发、发布模型就是这样,因此这些概念必须从一开始就沟通好、贯彻好。相比而下,这些倒和美术没什么事儿。
都是些小活儿。
另外我一直在想要不要在配置文件和game server之间套一个gconf这样的东西,外部更改配置,gconf通知listener也就是game server,呃,一个很不成熟的想法。
另外不少人一直想,在不重启进程的状况下,替换掉映像中的某个函数,修BUG。若是这个daemon程序是用C/C++写的,这个时候用dlopen加载一个so,设置一个参数就能够了。若是是JAVA而且用JDWP开了DEBUG,那么too easy。若是没有,那么unload jar/load jar吧。
我一直在构思一个可动态拆卸/替换/装载的架构,一个简单的不像OSGi那么复杂的东西,但是想法一直不大成熟,由于没有找到太简单的方法。个人基本想法是有一个object container,把service抽象成object,service和serivce之间的交互都要去这个object container中经过name lookup的方式获得一个句柄,而后通信。配置文件不能视成一成不变的,它们也是动态数据的一部分,不能再经过静态的getInstance得到,也必须经过这个object container查找。可是未必是一个global object container,每一个module能够有本身的object container。或是module instance持有reference,请求派发给module,module派发给object的时候把须要的reference传给过去,意思就是module就是一个object container,不过不是被lookup,而是主动构造好塞进去。
更多精彩点击http://www.gopedu.com