java游戏服务端实现

标签: java 游戏 服务 | 发表时间:2014-09-26 08:32 | 作者:kavy
出处:http://www.iteye.com

 

转自: http://kakaluyi.iteye.com/blog/1489592

 

一个多人在线的棋牌类网络游戏的项目临近尾声,我参与了该项目的整个设计流程,并且完成了90%的核心代码。关于这个项目,有很多地方值得聊一聊。本系列不打算把这个项目将得多么详细规范,那是设计文档应该描述的,我打算只说说一些值得注意的地方。

  这个项目的一个特别之处是,客户端是手机,用户通过移动网络与服务器通信。和PC相比,手机的处理能力极弱,而且网络流量费用昂贵。因为除了要考虑普通网络游戏的一些问题之外,这两点也需要在设计中充分考虑。

   首先是开发语言的选择,由于服务器是Linux的环境,MS的技术直接排除,至于MONO嘛,我实在不放心。可供选择的是C++和Java,Java胜 在网络能力强大,开发周期短,有众多框架和开源库的支持,要写出烂得不可接受的代码也不容易;C++则胜在速度快。综合各方面因素,C++更容易把这个项 目变成一堆代码噩梦,我们选择了Java。


一、网络

  网络游戏,首先面临的问题当然是如何进行网络通信。首先考虑的是HTTP协议,因为所有的J2ME手机都支持这个,我们当然想尽可能的兼容用户。而且HTTP协议封装程度已经非常高了,不用去考虑线程、同步、状态管理、连接池,不过HTTP协议有两个不爽的地方:

   ◇ 协议无状态,这个问题已经困扰过很多人很多次了。我曾考虑过的解决办法是改造HTTP协议,在数据传输完成之后不关闭socket,但是这样做工作 量非常大,在项目周期中,基本上就是Mission impossible,不予考虑。那么客户也就只能通过轮询的方式向服务器请求数据。

   ◇ 网络流量过大。就这个项目来说,网络间传递的只是指令,但是每次传递都要加上一堆毫无用处的HTTP Head,再加上客户端需要做轮询,这个流量 对于手机来说简直恐怖,经简单测试,按照0.03元/K的GPRS网络费用计算,一局牌居然要消耗1元多的费用(每秒轮询),实在不可接受。也许我们可以 采用流量费包月的资费方式,不过这个话题与技术无关。

  以上问题导致我们选择了Socket,这意味着我们将没有一个web环境,很多东西都要靠自己去实现:线程管理、客户状态监控、对象池、控制台……….

   网络部分打算采用Java NIO来实现,这是一种新的网络监听方式,基于事件的异步通信,可以提高性能。每个客户端连接之后,会有一个独立的 SocketChannel与它通信,这个SocketChannel会在用户的整个生存周期中存在。用户如果断开连接,服务器会得到-1,并且会抛出 Connection reset异常,通过捕获这两个特征,可以在用户意外断开连接后清理相关的资源。由于NIO是异步通信的,所以没有复杂的线程管 理。


二、通信协议

  这个项目并没有复杂的通信指令,命令数量很有限,但是还是有个关键问题需要关注:流量。为 了尽量减小流量,我们使用字节代替字符串来保存系统指令,这样可以使流量减少一半,比如使用一个字节来保存一张扑克牌,字节高位表示花色,字节低位表示数 字,如果0代表黑桃,那么黑桃三就应该是0x03,这个需要靠位操作来实现:

  1. int  m=0;
  2.      int  n=3;
  3.      byte  card=( byte )(m)<<4)|(( byte )n;  //m左移四位,然后与n左或操作



  游戏中需要传递用户的积分,这是一个大整数,使用四个字节来保存比较保险,将整数转换为四个字节的操作如下:

  1. public   static   byte [] translateLong( long  mark)
  2. {
  3.      byte [] b =  new   byte [4];
  4.      for  ( int  i = 0; i < 4; i++)
  5.     {
  6.         b[i] = ( byte ) (mark >>> (24 - i * 8));
  7.     }
  8.      return  b;
  9. }


  将四个字节转回来的操作如下:

  1. public   static   long  translateByte( byte [] b)
  2. {
  3.      int  mask = 0xff;
  4.      int  temp = 0;
  5.      int  res = 0;
  6.      for  ( int  i = 0; i < 4; i++)
  7.     {
  8.         res <<= 8;
  9.         temp = b[i] & mask;
  10.         res |= temp;
  11.     }
  12.      return  res;
  13. }



三、数据库连接池

   由于没有一个web环境,所以我们需要自己实现一个数据库连接池,apache有一个项目叫做commons DBCP,这是一个基于apache自己 的对象池(apache commons pool)实现的数据库连接池,我们可以直接拿来使用,apache的软件未必是最好的,但是极大可能比我们自 己写的要好。

Commons DBCP需要三个.jar:

    commons-collections-3.1.jar、commons-dbcp-1.2.1.jar、commons-pool-1.2.jar

这三个文件都可以在apache ? Jakarta ? commons项目下下载,加入到工程中即可。

构造一个数据库连接池的代码如下:

    
  1. import  java.sql.*;
  2.      import  com.gwnet.games.antiLord.util.*;
  3.      import  org.apache.commons.dbcp.java.html" target="_blank">ConnectionFactory ;
  4.      import  org.apache.commons.dbcp.java.html" target="_blank">BasicDataSource ;
  5.      import  org.apache.commons.dbcp.java.html" target="_blank">DataSourceConnectionFactory ;
  6.      private   static  BasicDataSource bds= new  BasicDataSource();
  7.      private   static  ConnectionFactory fac= null ;
  8.      //初始化连接池
  9.     bds.setDriverClassName(“org.postgresql.Driver ”);  //数据库驱动程序
  10.     bds.setUrl(“jdbc:postgresql: //localhost:5432/myDB”); //数据库url
  11.     bds.setUsername(“postgres”);  //dba帐号
  12.     bds.setPassword(“XXXXXXXX”);  //密码
  13.     bds.setInitialSize(100);  //初始化连接数量
  14.     bds.setMaxIdle(10);  //最大idle数
  15.     bds.setMaxWait(1000*60);  //超时回收时间
  16.     fac= new  DataSourceConnectionFactory(bds);  //得到连接工厂
  17.      java/sql/Connection.java.html" target="_blank">Connection  conn=fac.createConnection();  //从池中获得连接
  18.     conn.close();  //释放连接,回到池中
  19.      //销毁连接池
  20.     bds.close();
  21.     bds= null ;
  22.     fac= null ;



请自行处理操作中的各种异常。


四、扑克牌的生成

  游戏中需要为用户生成随机的扑克牌,首先我们需要初始化一副牌,放到一个Hashmap中,每张牌以一个字节表示,高为代表花色,的为代表数字,生成整副牌:

    
  1. private   static  HashMap  cards =  new  HashMap ();
  2.      int  tmp=0;
  3.      for  ( int  i = 0; i <4; i++) {
  4.          for  ( int  m = 0; m < 13; m++) {
  5.             tmp=(( byte )(i)<<4)|(( byte )m);  //使用位操作构造一张牌
  6.             cards.put( new   java/lang/Integer.java.html" target="_blank">Integer (i * 13 + m), new  java/lang/Byte.java.html" target="_blank">Byte (( byte )tmp));
  7.         }
  8.     }
  9.     cards.put( new   java/lang/Integer.java.html" target="_blank">Integer (53),  new  java/lang/Byte.java.html" target="_blank">Byte (( byte )0x4d));  //大王
  10.     cards.put( new   java/lang/Integer.java.html" target="_blank">Integer (54),  new  java/lang/Byte.java.html" target="_blank">Byte (( byte )0x4e));  //小王



   如何随机地得到其中的N张牌呢?我们的做法是生成一个0-55的随机数,用这个随机数作主键从Hashmap中获得对象,取得之后,把该对象从队列中删 除,以免重复取得。由于java中的随机数是根据时间生成的,所以有可能导致用户得到的牌不够散,每个用户都摸到一条龙岂不是笑话?所以在生成随机数的时 候我们加入了一个大素数来作运算:

    
  1. long  cardId= new   java/lang/Long.java.html" target="_blank">Long (( java/lang/Math.java.html" target="_blank">Math .round( java/lang/Math.java.html" target="_blank">Math.random() * 87) % 55)).intvalue();



通过修改这个大素数,可以控制某个用户的牌比较好。


五、线程

   实际上本系统并没有复杂的线程管理,但是我想提供一个控制台让管理员可以管理游戏主线程,可以让它停止、中段、恢复、重启动,本来的设计是管理员通过与 线程A打交道,通过A去管理主线程B,但是熟悉java线程的朋友都知道,线程互相管理基本上就是不实际的,举个最简单的例子,A如何销毁B?也许你会说 调用B的destroy()方法就好了,网上很多讲解java线程的资料也确实是这么说的,但是他们都是鬼扯的,自己去看看java源代码 吧,Thread.destroy()方法的实际代码如下:

  1. public   void  destroy() 
  2. {
  3.      throw   new   java/lang/NoSuchMethodError.java.html" target="_blank">NoSuchMethodError ();
  4. }



  事实真相是,Thread.destroy()方法自始至终就没有被实现过。所有写文章,教别人用这个方法销毁线程的人,都去撞墙吧,丢人丢大了。

  最好的办法是A负责生成一个B并且启动它,然后B自己管理生存周期,A和B通过使用可共享的方法来通信,这是sun推荐的做法。


六、异步消息

   用户玩牌的过程中,有很多东西需要记录下来,比如记录用户的积分、等级变化,记录玩牌日志供数据统计等,当用户数量很多的时候,在数据库中记录这些信息 会很耗费资源,用户玩了一局之后会可能会等待很长时间。解决这个问题的方法是利用J2EE的消息bean来提供异步通信的机制,需要记录数据的时候,系统 会封装一个值对象,发送给J2EE容器,这个操作是很快的,完成之后就返回,用户可以继续操作,不用关心消息何时被处理。

  J2EE的消息框架具备如下特征:

  ◇ 消息一定会被阅读,而且只阅读一次。JMS框架有自己的算法,把消息缓冲到硬盘,就算J2EE服务器死掉,消息也不会丢失。

  ◇ 系统采用点对点的Queue消息队列,可以保证同等优先级的消息先进先出。

   在Jboss 4.0中,部署消息Bean和Queue队列,都比weblogic 8.1来的容易,只需要在jboss.xml中声明消息目的地,如 果jboss发现该目的地不存在的话,会自动建立一个,实在很简单。关于消息bean的开发与部署,我有专门的文章描述(参见我的 blog:http://blog.csdn.net/bromon )。


七、启动与退出

  为了让系统具备让 人满意的性能,应该尽量多的重用对象,减少创建新对象。比如上面提到的消息发送,我们的操作是提供一个静态类,在系统启动的时候就初始化,保持与JMS服 务器的连接,系统发送消息的时候,不用再去查询JNDI和生成QueueConnectionFactory,这样可以提高系统响应速度。

   在数据库连接池的问题上,我们也采用同样的操作,启动的时候初始化N个连接。但是如果在关闭进程的时候不做任何操作,会导致JMS抛出socket异 常,虽然没什么大的影响,但总显得不专业,而且池中的连接不被释放的话,也可能导致问题。最好能够让系统像jboss等控制台程序一样,ctrl+c之后 能够执行操作,释放资源再退出。我们可以通过给进程/线程加上一个Hook来实现,windows程序员应该对这个非常熟悉。

Hook应该是一个线程方法,如下:

  1. public   class  Hook  extends   java/lang/Thread.java.html" target="_blank">Thread
  2. {
  3.      public   void  run()
  4.     {
  5.          //释放数据库连接,销毁连接池
  6.          //关闭与JMS的连接
  7.     }
  8. }


在主线程中加入:

    
  1. java/lang/Runtime.java.html" target="_blank">Runtime .getRuntime().addShutdownHook( new Hook()) ;



  那么进程/线程会在退出的时候执行Hook的run方法,清理资源。



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [java 游戏 服务] 推荐:

java游戏服务端实现

- - 企业架构 - ITeye博客
一个多人在线的棋牌类网络游戏的项目临近尾声,我参与了该项目的整个设计流程,并且完成了90%的核心代码. 关于这个项目,有很多地方值得聊一聊. 本系列不打算把这个项目将得多么详细规范,那是设计文档应该描述的,我打算只说说一些值得注意的地方.   这个项目的一个特别之处是,客户端是手机,用户通过移动网络与服务器通信.

Java NIO服务器实例

- - ImportNew
我一直想学习如何用Java写一个 非阻塞IO服务器,但无法从网上找到一个满足要求的服务器. 我找到了 这个示例,但仍然没能解决我的问题. 还可以选择 Apache MINA框架. 但我的要求相对简单,MINA对我来说还稍微有点复杂. 所以在MINA和一些教程(参见 这篇和 这篇)的帮助下,我自己写了一个非阻塞IO服务器.

文章: Java Remoting远程服务(下)

- - InfoQ cn
上篇分别介绍了Java Remoting远程服务中的RMI、EJB、Web Service等技术,下篇继续分享其他的内容. 百度技术沙龙第二十三期:一站式的前端开发框架(2月18日 周六). QCon北京2012:语言代码之美. 盛大云计算创意&开发大赛火热报名中. Hessian(http://hessian.caucho.com)是一种轻量级的Web Service, 采用的是二进制的RPC协议.

Java Web 服务性能优化实践

- - 博客 - 伯乐在线
来源: IBM developerworks. 简介: 本文介绍如何提升 Java Web 服务性能,主要介绍了三种方法:一是采用 Web 服务的异步调用,二是引入 Web 服务批处理模式,三是压缩 SOAP 消息. 重点介绍在编程过程中如何使用异步 Web 服务以及异步调用和同步调用的差异点. 本文还示范了如何在项目中使用以上三种方法,以及各种方法所适合的应用场景.

如何使用 Java 构建微服务?

- - SegmentFault 最新的文章
【编者按】微服务背后的大理念是将大型、复杂且历时长久的应用在架构上设计为内聚的服务,这些服务能够随着时间的流逝而演化. 本文主要介绍了利用 Java 生态系统构建微服务的多种方法,并分析了每种方法的利弊. 在 Java 生态系统中构建微服务的策略主要有:container-less, self-contained 和 in-container;.

Java 开发 2.0: 使用 Gretty 的超轻量级 Java Web 服务

- 圣斌 - IBM developerWorks 中国 : 文档库
Gretty 是构建 Web 服务超轻量级框架的新学派之一. 构建于极快的 Java NIO API 之上,Gretty 将 Groovy 用作 Web 端点和 Grape 的 Maven 式依赖关系管理的一种域特定语言. 本文介绍如何开始使用 Gretty 来构建和部署 Java Web 服务应用程序.

JAVA安全之JAVA服务器安全漫谈

- - WooYun知识库
本文主要针对JAVA服务器常见的危害较大的安全问题的成因与防护进行分析,主要为了交流和抛砖引玉. 以下为任意文件下载漏洞的示例. DownloadAction为用于下载文件的servlet. 在对应的download.DownloadAction类中,将HTTP请求中的filename参数作为待下载的文件名,从web应用根目录的download目录读取文件内容并返回,代码如下.

用gdb调试游戏服务端

- - CSDN博客编程语言推荐文章
一般来说,提到gdb,都是用命令来调试. “命令”,这个对于用户来说几乎等同于繁杂的词语. 尽管事实确实如此,但实际的开发调试必须用到gdb. 现在,大多数Linux系统是存在于服务器当中. 我们想操作这些系统时,一般是通过Terminal来操作. 也就是说这些Linux系统不具有图形界面. 而调试一般分两部分,开发时调试和运行时调试.

java游戏开发入门2_基于netty+protobuf的游戏框架

- - 行业应用 - ITeye博客
/** 刚开始学习游戏开发时想找一个基于netty的游戏demo十分困难,工作一段时间后了解框架后将其分享出来; 该框架是从别人框架移植修改完善而来,不是我一个人写,算是借花献佛; 实际业务开发比此框架要复杂得多,去繁从简主在体现核心思想; 这是游戏开发入门的第2篇,如果有不完善的地方请多多指导.  框架示意图如下,源代码参看: github:.

Java数字抽奖游戏核心代码及分析

- - BlogJava_首页
最近,正在看《core java 8th》这本书. 自己按照书上作者一段代码的思想,自己动手写了一个小代码,深刻的感觉到作者Horstmann超高的代码思想. 《core java 8th》真的确实是一本好书,值得慢慢品读,会给人带来无穷的惊喜. 自己写的代码与注释如下,废话不多说,上代码.  * 一个简单的数字彩票游戏类 .