zoukankan      html  css  js  c++  java
  • 转: 基于netty+ protobuf +spring + hibernate + jgroups开发的游戏服务端

    from: http://ybak.iteye.com/blog/1853335

       半年前跟朋友开始创业做手机游戏.我负责一个人开发服务端,这是一个卡牌类多人在线回合制对战网游.开始时第一考虑的是能快速出实现需求.其次是功能的可扩展性(应对频繁变更的需求),再次才是服务器性能.

       目前游戏开发的主流语言仍是C++,但因为自己最熟悉的是java,转C++的成本太高,且国内页游服务器,已经开始大规模应用java作为后端服务器.最终还是确定使用java来开发.

       这里,有两条路可选,1是使用现有的服务器作扩展,2是自己开发服务器.
       
       市面上的成熟的java 游戏服务器寥寥无几,选择并不像web框架那样丰富,可选的收费服务器以smartfox为代表.还有一些开源的但充其量只能作为demo的框架(比如menacher).当时网易的pomelo也还没有出现.

       虽然smartfox宣传有很多成功案例.但看了文档后.发现smartfox最成功的案例是棋牌类游戏和社交类游戏.这和我们要开发的卡牌类RPG手游的游戏交互方面还是有很大的差异.而且smartfox对于游戏的逻辑封装过多.比如基本的概念就是进入游戏房间等.这种封装并不适合于我们要开发的游戏类型.所以购买smartfox没有太多的性价比.果断放弃之.

       接下来就是自己开发游戏服务器的socket server了.
       

        在实现socket server时,先参考了两个开源的游戏服务器实现.一个是永恒之塔的开源私服实现aionxemu,另一个是简单的socket server实现menacher.

      我在看了许多aionxemu的代码实现后.发现这些代码编写开发者一定都是多年的编程老手.代码不光实现了数量庞大的游戏服务接口,还在可扩展性(OO方面)和性能优化上做了很多考究的设计.aionxemu游戏服务器是分为gameserver loginserver和chatserver在gameserver中,接口协议的解析和序列化直接使用的是java nio来操作字节流.直接使用java nio比较繁琐,而操作字节流解析和封装协议也存在同样的问题.虽然能提高性能,但在开发成本和可维护性上有一定弊端,不适合目前我们对进度要求十分紧迫的情况.值得一提的是:aionxemu虽然只是一个私服,但其中具有完整的mmorpg的逻辑实现. 对于我们游戏中的逻辑开发具有相当高的借鉴意义.

       menacher中使用了netty作为NIO操作类库,在其代码中展示了netty的简洁性.比较有意思的是menacher使用了jetlang作为游戏业务事件驱动的reactor处理框架,可以方便的提供逻辑的扩展并提供性能调整配置.但遗憾的是menacher并没有在服务器的横向扩展上做太多的考虑..


       在参考了以上两个实现,再结合项目的实现情况(服务器提供的服务接口可能多达100多个,而且这些接口后续出现变更),在协议处理上我选择了protoc buffer.   Protbuf的跨平台使使用C++的客户端可以方便的序列化协议对象.使用客户端和服务器双方更关注于游戏协议,而非字节流的处理细节.有利于客户端和服务端快速交流. 最后在socket server上采用了netty+protbuf两个框架来实现.netty负责socket处理,protbuf负责处理游戏协议解析.
       协议的基本结构如图:

       

       protobuf的客户端消息协议基本如下所示.

        

    Java代码  收藏代码
    1. message PBClientPacket {  
    2.     required PBClientRequestType clientRequestType = 1; //请求类型  
    3.     optional bytes requestData = 2; //请求数据  
    4. }  
    5.   
    6. enum PBClientRequestType {  
    7.   
    8.     DUMMY_REQUEST = 0; // PBDummyRequest  
    9.     UPDATE_REQUEST = 1; // PBUpdateRequest  
    10.     REGIST_REQUEST = 2; // PBRegistRequest   
    11.     LOGIN_REQUEST = 3; // PBLoginRequest   
    12. ......  
    13. }  
    14.   
    15. ....  
    16.   
    17. message PBDummyRequest {  
    18.     optional string payload = 1;  
    19. }  
    20.   
    21. message PBUpdateRequest {  
    22.     required string clientVersion = 1;   
    23. ......  
    24. }  
    25. ......  

          protobuf的服务端消息协议基本如下所示.

    Java代码  收藏代码
    1. message PBServerPacket {  
    2.     required PBPayloadType payloadType = 1; //服务端报文类型  
    3.     optional PBResponseCode code = 2; //  
    4.     optional string desc = 3; //  
    5.     optional bytes payloadData = 4; //此字段中包含服务端返回的响应内容  
    6. }  
    7.   
    8. enum PBPayloadType {  
    9.     DUMMY_RESPONSE = 0;   
    10.     UPDATE_RESPONSE = 2; //PBUpdateResponse  
    11.         ......  
    12. }  
    13.   
    14. enum PBResponseCode {  
    15.     OK = 0;  
    16.     ERROR_UNKOWN = 1;  
    17.     SERVER_ERROR = 2;  
    18. ......  
    19. }  
    20.   
    21. message PBUpdateResponse {  
    22.     optional string resourceURL = 1;  
    23.     optional bytes resoruces = 2;  
    24.         ......  
    25. }  

     客户端和服务器端的消息结构类似.,第一个字段表示消息类型,程序在知道消息类型后.就可以根据协议来化序列化后面的报文字段.在具体的请求对象外再封装一层统计的交互对象,会有利于netty与protobuf的集成.

    netty本身提供的protobuf demo展示了如何方便的与protobuf的集成方式.

    对于每种客户端消息,需要找到对应的业务逻辑处理单元.而作为JavaEE开发者,习惯于使用spring来管理这些逻辑处理单元.在spring管理的bean上可以,轻松的添加业务监控功能.服务逻辑上需要使用一种方法来将消息和业务bean映射起来.这里使用了一种比较讨巧的方式来处理,将消息类型作为bean的名称.

     使用javameody监控spring beans

    比如对于clientRequestType类型为LOGIN_REQUEST的报文.定义了LoginRequestHandler来处理:

    Java代码  收藏代码
    1. @Component("LOGIN_REQUEST")  
    2. public class LoginRequestHandler extends AbstractRequestHandler {  
    3. @Override  
    4.     public void handle(ByteString packetData, Channel channel) throws InvalidProtocolBufferException {  
    5.         PBLoginRequest request = PBLoginRequest.parseFrom(packetData);//反序列化请求  
    6. .......  
    7. }  
    8. }  

     这样在服务器收到报文后.可以方便的根据报文类型选择对应的handler来进行处理.

    Java代码  收藏代码
    1. @Component  
    2. public class RequestDispatcher {  
    3. private Map<PBClientRequestType, RequestHandler> handlerMap = new HashMap<PBClientRequestType, RequestHandler>();  
    4.   
    5. public void dispatchClientPacket(final PBClientPacket clientPacket, final Channel channel) {  
    6. PBClientRequestType clientRequestType = clientPacket.getClientRequestType();  
    7. try {  
    8.      handlerMap.get(clientRequestType).handleRequestData(clientPacket.getRequestData(), channel);  
    9. catch (BadRequestException e) {  
    10. }  
    11. }  

     以下是集成测试中,模拟客户端构造请求报文的代码:

    Java代码  收藏代码
    1. PBLoginRequest loginRequest = PBLoginRequest.newBuilder().setUsername(username).setPassword(password).build();  
    2.        PBClientPacket clientPacket = PBClientPacket.newBuilder().setClientRequestType(PBClientRequestType.LOGIN_REQUEST)  
    3.                .setRequestData(loginRequest.toByteString()).build();  
    4. hannel.write(clientPacket );  

      

    TBD

  • 相关阅读:
    Dede 自定义频道解析
    没有什么想说的,但既然来了,也就留下一句
    Title和META标签参数详解,SEO优化中的title和META标签的重要性
    Jsoup HTML 解析器 用法介绍
    php 相关方面内容、
    Html.ActionLink Url.Action的用法
    Asp.Net MVC2.0 Url 路由入门实例篇
    asp.net mvc 在View中获取Url参数的值
    SSH2实现数据库和界面的分页
    struts2中的ModelDriven使用
  • 原文地址:https://www.cnblogs.com/jhj117/p/5784282.html
Copyright © 2011-2022 走看看