我是个写游戏的程序员

来源:互联网 发布:java打印水仙花数 编辑:程序博客网 时间:2024/06/06 01:57

以下这些文字献给我还念念不忘的你

从一个笑话开始
    夏天和同事吃饭的时候聊到了女人要嫁怎样的IT男。
    不能嫁服务器开发人员,虽然稳定但是太闷,缺少美丽的形貌。更要命的是,和你联系的同时可能还和其他MM保持着联系。也不能嫁客户端开发人员,这些看上去很美的人,除了能露脸参加Party外,却是甩手掌柜,遇事只管推给服务器处理。如果素质比较低的话,还会没来由的占用你大量的时间和精力。不能嫁美术设计师,那似乎不是IT男。不能嫁DBA,他们为人刻板,死守着他的生活范式,强制的要求你如何如何。他为了能在人海中快速找到你,会在你的脸上贴上标签。不能嫁系统分析师,永远夸夸其谈,他们会把生活设计的很精彩,却不会处理鸡毛蒜皮的小事。不能嫁架构师,因为在中国贴这类标签的人都是骗子。不能嫁项目经理,他的眼里只有成本和进度,你们的生活只能是快节奏低质素。不能嫁测试工程师,除非你想天天吵架。。。。。。。。
    结论要嫁就嫁IT公司的Boss。
    呵呵,言归正传。

我们必须谈谈
    你控制的那个小人在地图上走来走去,他不单单是鼠标事件的结果,他还要和远处的服务器进行通信。客户端把小人要做的动作变成一个字节序列。这个序列里包含了这个动作涉及到的数据,比如说一个攻击动作包含了被攻击小人的姓名,我使用的招式。然后在这个序列的前面加上一个唯一标识,告诉服务后面的数据说明了什么,应该如何处理。当然我们会命令我们的小人作出各种动作,那么每个动作的字节序列长度是不同的,这个序列没有长度说明的话,服务器无法判断那些字节是属于这动作。所以我们客户端发往服务器的字节序列最后会是这样的结构  
消息{消息头{消息体的字节数目},消息体{动作标识,数据1,数据2。。。。数据n}}
    同理,服务器返回给客户端的内容也是这样的结构。一旦消息组装完毕会从客户端的应用程序被送到操作系统,再从操作系统送到网卡硬件,然后离开你的计算机,走在互联网的大路上(中国的网络环境没有国道)。那些NB的美国人保证了在互联网上的数据最终会被送到正确的目的地(注1)。服务器的网卡收到消息,交给操作系统,操作系统再把消息交给服务器的游戏应用程序。这是一个粗略的过程描述,其中操作系统不会明确告诉游戏应用程序下面将是一个完整的消息(注2)。操作系统只是把他收到的字节断断续续交给应用程序,应用程序每次得到的字节序列可能是一个完整消息和下一个消息的前半部分,也可能是一个消息的后半部分,也可能是一个消息的中间部分。所以我们应该记下来消息头部分的长度标识数据,累计后面的字节,拼凑成一个消息时,开始处理这个动作(注3),否则保持循环,不停的从操作系统得到字节序列,累计。当服务器对一个消息处理完毕时,会向客户端发送一条消息,告诉他这个动作是否成功,或者告诉他下一步该怎么做。
    注1:TCP能保证,UDP无法保证。
    注2:UDP能保证,TCP无法保证。文章假设使用TCP协议。
    注3:接收字节,拼凑消息,处理消息,发送回复这个过程是同步的,处理A客户端时,阻塞了对B客户端的处理,同时造成cpu的浪费。后面会介绍改进的思路。

该干嘛干嘛去
    一旦服务器拼凑出一个消息,就应该针对动作标识,做计算。
    我们可以在一个函数内用if(){}else if(){}else.......块来完成计算,其实可以重构一下
    我们可以实现多个函数,分别处理不同的消息,然后由if(){}else if(){}else.......块来调用,当然这还不够优雅
    我们可以抽象这些代码==》处理消息。运用所谓的对象编程思想,抽象出一个接口IMsgHandler,针对不同的消息有不同的实现。将动作标识和相应的实现一一配对形成一张映射表。
    IMsgHandler handler=map<actionId,handler>.getHandler(actionId);
    handler.hande(msg);
    消灭繁冗的分支判断,换以顺序的执行流是重构第一步。(注1)
    利用处理器的不同的实现来处理不同消息仅是这个话题的第一部分。我们发现先是拼凑了消息,然后立即处理消息。如果有多个消息呢?服务器连接着多个客户端,不应该处理某一个客户端而阻塞了其他客户端。
    很久很久以前,会多线程编程是你拔份的利器,今天,不会多线程编程将是你致命的短板。按照流水线的思路我们可以设计两个线程。一个线程负责从操作系统读取字节拼凑消息,一个线程(注2)负责处理这些消息。负责将他们联系起来的是一个队列数据结构。读取线程在队列的尾部加一个消息,处理线程从队列的头部取出消息处理。这个模型又被称为生产-消费模型。你必须留心的地方是多线程环境下的数据同步,这是使用多线程必须的代价。
    如果生产和消费的速度不相等,可适当调整他们之间的比例--一个生产对多个消费或者多个生产对一个消费--来达到平衡。
    注1:一个有鸡肋嫌疑的技巧。可以利用整数做消息动作标识,将处理器的不同实现组成一个数组,利用动作标识作下标,直接定位到消息器,绝对代码优雅。^_^
    注2:这里使用线程池更好。

有状态的人
    你控制的那个小人和现实生活中一样,每个动作都有着上下文语境。这些语境在游戏编程叫状态,状态决定了你的小人能不能顺利完成你预期的动作。举个例子,我的小人有点穴技能,对你的小人使用后,你的小人在一定时间内不能作出任何动作,包含移动,吃药等等。我们把这个语境暂时叫做定身状态,这时你只有三种选择1等待时间的流逝,2别人对你使用一个解穴的技能,3自己对自己使用一个解穴技能。除此之外,你的任何指令在这个状态下都是枉然。
    同时,我们还应该注意到,状态必须能够成其他状态,以形成回路。如果某个状态不能切换到其他状态,那么这个小人不是无敌就是只有等死。
    既然状态决定了一切,代码就是建立在判断基础上的,此时为避免if分支,应采用状态模式。

你做的那些事我都知道
    我就在你的旁边,发生的你身上的悲喜剧,我都看到了。但同时,距离远一些的人他们对你身发生的一切一无所知。以小人的坐标为圆心,圈一个屏幕大小的范围,在这个范围里的人都会看到小人的行为。每次移动一个单位这个范围就会随之移动,我们需要计算出新进入范围的人和脱离范围的人。服务器关于新来的人有两个消息1告诉新来的人,你看到小人了2告诉小人的客户端我看到了她了,关于脱离的人也是两个消息1告诉走掉的人,你不用关注我了2告诉自己的客户端我也不关注他了。
    我看到你的同时,你也看到了我。这样就是一个打坐的人也能看到身边有两人正在搏斗。

地主家也没有余粮啊
    服务器要长时间的为多人服务,资源都应该有效利用。虽然java有GC功能,但是防止内存泄漏是每个程序员必须的意识。对象复用,减少构造和gc的系统占用。减少数组拷贝。不允许一个对象的生命周期只在一个函数内。建立高命中缓存不做重复计算。

原创粉丝点击