盗賊の極意

Feed Rss

服务器端写烦了,写写日志帮助一下思考

07.23.2014, 未分类, by , 3,287 views.

公司没有写服务器端的经验,之前用某服务器端引擎提供商提供的示例代码改改出了一个游戏。这次轮到我当服务器端程序(一个人负责C++全部,哈哈),看原来的代码没用到的示例功能特别多,很碍眼,想重写一个清爽简洁版的服务器端,但是这需要好好理解哪些代码可以删,哪些不能动,偏偏引擎是闭源的,我只有header和示例的注释可以看,初始化时七八个管道啊连接器什么的实在猜不出是干什么用的,读所有那些header也很花时间,犹豫了。
从吸收知识的角度来说,肯定是好好理解一下比较好。但是我毕竟是拿钱工作的,消化任务列表的速度太慢,可能被公司添加新人进来——其实已经听说准备招人加入服务器端开发了——我独占服务器端开发的时间有限,在别人来以前尽量多推进一些也许比较好?比如公司看我一个人也写得挺快,就让我自己干了什么的。我这段时间刚投了简历准备跳槽,挺希望能在走之前给这个项目搭好稳固的地基(最好干脆让我干完再走更有成就感)。反正就是各种时间不够用。偏偏我上一个项目的php工作太悠闲,已经很难集中精神读大段代码了(写还是没什么问题,写比较容易投入)。坐在座位上开开微博,看看邮箱,干着急就是不去读代码。
现在也是,我看我实在是不肯干,就索性出来写写日志放松一下,也让文字帮助我思考,看看这个问题到底出在哪。
这个服务器端怎么写,我心里大致已经有数了,上周也刚写好对战部分提交了。今天开始要写游戏房间、匹配等等进入战斗前的部分。虽然代码还没细看,但我们有rpc定义文件,仔细读了一遍,知道客户端可以调用哪些功能,对这个匹配服务器怎么做也就有了大致的印象。就像笃志说的,架构一看就懂的感觉。
架构虽然懂了,落到实现还是很犹豫的,毕竟实现方法太多了。而我又好多想,希望在架构设计阶段就把之前困扰我的服务器端调试功能也搞定,这下更不敢动手了。其实服务器端调式,我的终极理想是做成跨平台,让vs也能编译运行服务器端,这样调试就不是问题了。不过我们的引擎商只给了我一个老版的windows端lib,只有linux版的.a是最新的,这链接上去谁知道跑得对不对呢。还有我们现在的服务器端主要靠引擎商的示例代码,里面直接用到了fork函数,这个我真是没法在windows下模拟,平台跨不起来。要么大改逻辑,但那样工时就会花很多,上头问下来我说我搞个人研究重构着玩儿掉了5个工作日什么的,那还得了。我现在能做的,就是一层一层的分开,让这个项目尽可能多的部分可以被windows编译运行。战斗部分因为可以独立运行,现在已经实现在windows下运行了,不过debug还是不是很顺手,看来只有vs也不能解决问题。我们公司是没有这方面的经验,真没有,连gdb的存在和用法都是我教他们的,在那以前他们完全靠log来debug服务器端。
好了,再说回来,这个服务器端调试不顺手。因为客户端和服务器端通信有一个顺序和时间间隔,我想做一个操作顺序就要改一次客户端代码,不方便。这里我的理想解决方案肯定是导入lua,毕竟以后也需要测试机器人这一点已经确认了,早晚要做这个功能。不过做测试机器人的任务有它自己的工时,现在做是占用我做匹配服务器的工时,不好。另外还有一点,是不能同时停下两边。一边下了断点停下了,另一边很容易超时。尤其gdb我玩得还不是很得心应手,比起下条件断点,我更常做的是最开始就下好断点,然后每次被断了就continue,直到我想要的那个条件的到来。说是每次,其实也就两三次啦,但客户端那边是有固定时序的。我每次打continue或者c的时候,时间误差都会累积,最后时序就不是我预期的样子了。这一点真的烦,习惯了vs上开发单机游戏时尽在掌握的调试能力,现在是各种憋屈恼火。服务器端调试已经是太钝的斧头了,需要我磨刀一下才不会耽误今后大量砍柴的任务。当然,这个耗费的工时只能自己挤出来了。问题是,我肯出这个时间,也不知道该怎么改良。如果断下来之前先通知另一端,就需要一个类似break()函数,里面是rpc通信,但这就失去了断点可以实时添加的便利。我每加一个断点,都要重新编译一遍的话,我宁可自己读代码找bug。一个想法是,两边的超时行为应该默认不发生,直到我手动发出一个超时信号,才出发当前阶段的超时。这样一边进入断点也不用着急,算是为调试专门设计的环境,可能需要定义特别的编译宏来开关这个功能什么的。为了今后的等待也能被这个调试方式管理,程序中所有的超时管理应该使用统一的方法。在服务器端,我估计会有控制时间流这方面的需求,所以已经做了一个GameTimer类,游戏中所有的时间都是询问GameTimer来获得的。那么调试模式下,每次启动GameTimer时它都不动,直到收到debug超时信号,就一口气走过一个巨大的数字,保证什么逻辑都超时。甚至debug超时信号中可以自带走过的时间,让时间的流动尽在掌握。嗯,听起来有点靠谱了,不愧是靠写字思考的男人,写博客果然有效,哈哈。
哎,我就喜欢琢磨这些基础建设,因为以后都能从中获益。游戏逻辑的具体实现是写过就算,对我来说属于用过即弃的代码,没有沉淀,没什么兴趣。

服务器端写烦了,写写日志帮助一下思考 有 6 条回应

  1. 2014-08-13 在 10:52 FreeKnight

    闭源的玩意儿就不要浪费时间在上面了,现在的时代不是开源优秀代码稀缺的年代了,好钢要用到刀刃上。
    说到敬业心,我现在真的是越来越不想有了,(虽然在上家公司还是拖了我半年才放我离职)。原因很简单,自己认为很努力,尽力去做好,但或许那仅仅是自己的一厢情愿的表达而已。和男生追女生一样,你玩命去表达,跑几千米冒着大雨买个足球为她开心,或许妹子在意的完全不是这东西,她或许会觉得这货很二。要让对方接受,就必须用对方的理解方式,想下该怎么做。(当然了,我不直接告诉你,太狡猾了,哈哈)
    然后呢,说到代码本身的话,就你当前描述就已经是令人恐惧的开发。我不知道你在什么公司,但就整体框架看来,你们公司的代码有不轻的问题。理由非常简单,程序员实际开发时,30%左右时间用于思考如何处理问题,20%时间用于编码,50%以上时间用于DEBUG调试。而且当经验越丰富,第一项占用时间越长,第二项占用时间则越少。(例如我现在非常推崇的是简约便捷的代码,接口编程,数据驱动,做生成代码的代码,这些可以之后详细解释或者kira君自行百度)。总而言之,对于不友善的DEBUG调试和性能分析,将导致程序员付出昂贵的代价得到很少的回报,不是项目进程中应当如此明显的出现的。
    再说机器人。其实这玩意儿前期就需要有了,他和单元测试代码应当是一起被创建出来。C/S的交互其实有且仅有一个,就是网络通讯。如果C/S各自有一个模拟消息回报器之类的东西,双方的开发就可以完全解除耦合。例如S端有这么一个东西“按一个按钮,就发送一个登陆请求消息,其中的数据信息甚至可以写死(我推荐使用配置脚本),服务器收到给予应答,模拟器显示出来,并自动给予预计的应答”整个服务器只要把消息定义正确了,自己就可以冲着开发了,没必要等客户端的进度。C/S双方不再牵制,这简直是必须的。
    另外你提及的延迟消息间隔,这个通常是为了减少TCP包个数而做的故意的延迟粘包。一般写这个底层的应该给一个开关的……
    额= =突然不想写了,貌似下面Kira和我上面说的差不多……果然英雄所见略同啊,哈哈哈
    潜水敲代码了。过一段开放一个cocos2dx封装框架。

    回复
    • 2014-08-15 在 14:42 基拉铃妖

      有很多想说,不过现在就只说C/S作业分离吧。
      今天和客户端的人商量了一下,想做成把客户端的通信部分透明化的方案。
      在我的通讯部分代码,和他的客户端逻辑代码中间,有一层代理层,把从通信部分收到的有用信息保存在客户端自己的Command类里。
      具体来说就是,我的通讯是发送Command,客户端收到Command后保存在队列里,Command的处理是同步阻塞的,前面的命令结束前,后面的命令不会进入update处理。代码结构类似这样:
      class AtkCmd : public Cmd{
      int var;
      public:
      void Execute(){ Proxy.AddCmd( new ProxyAtkCmd(var) ); }
      bool IsFinished() override { return Proxy.IsCurrentCmdFinished(); }
      };
      具体的执行和判断命令是否执行结束都在ProxyCmd内部来做。
      这样,Cmd类我管,ProxyCmd类客户端来写。客户端的MockServer他用自己的ProxyCmd来写,最后我们写一些代理函数接通。这期间各自写程序就好。

      客户端这个写法是今天我刚知道的,一下子也没觉得有太不妥的地方,有点代码重复、多余的中间层的感觉,但好处是可以各自工作,他可以自己写MockServer,也不用太在意我的Cmd存储消化阻塞相关逻辑。
      闻起来这个实现还是很不优雅,但是公司实在没有有经验的人,只能自己摸索了。希望能快点跳槽到一个有网络编程经验的公司去学点网络编程的idiom回来。

      我拜托研究生时代的前辈帮我写一下内推信,结果他太负责人了,帮我修正就职经历书的日语,告诉我哪里应该详细,具体什么是需要的,怎么写格式更好。几番修改,现在我就职履历书里几乎每一行都有他的修改甚至重写,有我写论文时教授的批改力度了,感动T_T。昨天把最新版交上去了,现在感觉压力好大,这么帮我我要是面试落了,会觉得特别对不起他的= =

      回复
  2. 2014-08-18 在 11:16 FreeKnight

    我猜测你的意思如下。其实proxyCMD是一个环状队列wrapper。svr发来的消息,不是客户端立刻处理(这也是必须的),先放置在一个缓冲区(一般是环状的,可以避免重复内存分配),客户端每帧处理一定量的消息,前面没处理完,后面的将继续放置在缓冲列表中。当然看得出来,你们客户端刻意的将Svr的cmd 以某种方式转换为客户端的ProxyCMD,其意义现在我还想不到,感觉略有冗余。
    另外使用缓冲队列的时候,你们client是前面不处理完,后面就不处理,属于阻塞,这是相当不安全的。我不清楚你们服务器的包是否分紧急包,如果分的话,那么客户端就不能做一个简单队列,起码要俩个,相信你大致明白原因。
    嘛,说到面试内推啊~~帮你改简历的是好人啊,在国内这边,只有我很看好的人我才帮改简历哦,因为很简单,我很清楚面试官和BOSS关心什么,需要看什么,甚至他们兴趣性格是什么样的。而来面试的人没有这么强的针对性,我改完成功率会大增哦。恩~Kira君遇到好人了= =至于进不进嘛,更多看运气咯~~只能远程说句good luck咯~
    哈哈,话说要是成功了,告诉我是去哪家了哦~

    回复
    • 消息可以带队列名,不带的统一进默认队列。客户端每次更新都会从所有队列中读取消息,不同队列互相不影响,只阻塞自己。阻塞有各种限制我也感觉到了,不过现在这个游戏是回合制,通信内容相当单纯,就没花太多时间给它做框架,整个map<字符串,队列>,能胜任就好。
      这次因为我们被引擎商绑架,只能用他们指定的老版本g++,没有std::function,连auto都不能用,我就自己写了一个简单的函数对象类。代码不长,100行不到,效果不错,可以保存成员函数作为回调函数对象,成员函数可以无参或带1个参数,带参数的成员函数对象执行时填入参数调用,带参数成员函数对象可以Bind一个参数,变成一个无参成员函数对象。我用起来特别过瘾,有种在用javascript的自由感,就介绍给我原来的程序上司,他看了看说这个东西比较晦涩(模板类),别人恐怕一眼看不懂怎么用... 我在那个函数对象类周围写了详细的注释,包括完整的可执行代码示例,但我能想象,还是不会有人愿意读,愿意用的。可能因为绝大多数同事都是大专生的关系,我现在的公司对技术、架构之类毫不在意,就是血拼无偿加班工作量。我能聊聊架构,聊聊语言特性,代码习惯什么的就只有这个前上司。但他还属于对工作非常负责的人,对我用工作时间搞研究还有点介意orz。环境不太好啊,所以想跳槽。我现在跳槽就这么一个目标,失败了得特别受打击。面试这事儿,比起努力,我还是多花点时间做失败的心理准备比较靠谱。这家不行就把标准一点点往下降,反正不能留在这里等待腐朽了。

      回复
  3. 2014-08-18 在 15:54 FreeKnight

    = =首先你那做法很常见,算不上晦涩(模板也是必须的)。突然觉得日本是不是程序也没多强……哈哈哈哈
    另外,没啥好吐槽的了= =加油咯~等你当BOSS了,带我过去咯,哇哈哈

    回复
    • 是我这个公司水平太低了,都是学完C++就直接上去写游戏的...orz。等你创业有成,回去投奔你了TAT

      回复

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>