android開發之網絡棋牌類在線遊戲開發心得(服務器端、Java) 好文章值得收藏

標籤: android服務器
分類:

轉自:http://blog.csdn.net/bromon/article/details/253330 Bromon原創 請尊重版權java


一個多人在線的棋牌類網絡遊戲的項目臨近尾聲,我參與了該項目的整個設計流程,而且完成了90%的核心代碼。關於這個項目,有不少地方值得聊一聊。本系列不打算把這個項目將得多麼詳細規範,那是設計文檔應該描述的,我打算只說說一些值得注意的地方。android

  這個項目的一個特別之處是,客戶端是手機,用戶經過移動網絡與服務器通訊。和PC相比,手機的處理能力極弱,並且網絡流量費用昂貴。由於除了要考慮普通網絡遊戲的一些問題以外,這兩點也須要在設計中充分考慮。

  首先是開發語言的選擇,因爲服務器是Linux的環境,MS的技術直接排除,至於MONO嘛,我實在不放心。可供選擇的是C++和Java,Java勝在網絡能力強大,開發週期短,有衆多框架和開源庫的支持,要寫出爛得不可接受的代碼也不容易;C++則勝在速度快。綜合各方面因素,C++更容易把這個項目變成一堆代碼噩夢,咱們選擇了Java。


1、網絡

  網絡遊戲,首先面臨的問題固然是如何進行網絡通訊。首先考慮的是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是異步通訊的,因此沒有複雜的線程管理。


2、通訊協議

  這個項目並無複雜的通訊指令,命令數量頗有限,可是仍是有個關鍵問題須要關注:流量。爲了儘可能減少流量,咱們使用字節代替字符串來保存系統指令,這樣可使流量減小一半,好比使用一個字節來保存一張撲克牌,字節高位表示花色,字節低位表示數字,若是0表明黑桃,那麼黑桃三就應該是0x03,這個須要靠位操做來實現:

    int m=0;
    int n=3;
    byte card=(byte)(m)<<4)|((byte)n; //m左移四位,而後與n左或操做

  遊戲中須要傳遞用戶的積分,這是一個大整數,使用四個字節來保存比較保險,將整數轉換爲四個字節的操做以下:

public static byte[] translateLong(long mark)
{
    byte[] b = new byte[4];
    for (int i = 0; i < 4; i++)
    {
        b[i] = (byte) (mark >>> (24 - i * 8));
    }
    return b;
}

  將四個字節轉回來的操做以下:

public static long translateByte(byte[] b)
{
    int mask = 0xff;
    int temp = 0;
    int res = 0;
    for (int i = 0; i < 4; i++)
    {
        res <<= 8;
        temp = b[i] & mask;
        res |= temp;
    }
    return res;
}


3、數據庫鏈接池

  因爲沒有一個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項目下下載,加入到工程中便可。

構造一個數據庫鏈接池的代碼以下:

    import java.sql.*;
    import com.gwnet.games.antiLord.util.*;
    import org.apache.commons.dbcp.ConnectionFactory;
    import org.apache.commons.dbcp.BasicDataSource;
    import org.apache.commons.dbcp.DataSourceConnectionFactory;

    private static BasicDataSource bds=new BasicDataSource();
    private static ConnectionFactory fac=null;

    //初始化鏈接池
    bds.setDriverClassName(「org.postgresql.Driver」); //數據庫驅動程序
    bds.setUrl(「jdbc:postgresql://localhost:5432/myDB」); //數據庫url
    bds.setUsername(「postgres」); //dba賬號
    bds.setPassword(「XXXXXXXX」); //密碼
    bds.setInitialSize(100); //初始化鏈接數量
    bds.setMaxIdle(10); //最大idle數
    bds.setMaxWait(1000*60); //超時回收時間

    fac=new DataSourceConnectionFactory(bds); //獲得鏈接工廠
    Connection conn=fac.createConnection(); //從池中得到鏈接
    conn.close(); //釋放鏈接,回到池中

    //銷燬鏈接池
    bds.close();
    bds=null;
    fac=null;

請自行處理操做中的各類異常。


4、撲克牌的生成

  遊戲中須要爲用戶生成隨機的撲克牌,首先咱們須要初始化一副牌,放到一個Hashmap中,每張牌以一個字節表示,高爲表明花色,的爲表明數字,生成整副牌:

    private static HashMap cards = new HashMap();

    int tmp=0;
    for (int i = 0; i <4; i++) {
        for (int m = 0; m < 13; m++) {
            tmp=((byte)(i)<<4)|((byte)m); //使用位操做構造一張牌
            cards.put(new Integer(i * 13 + m),new Byte((byte)tmp));
        }
    }

    cards.put(new Integer(53), new Byte((byte)0x4d)); //大王
    cards.put(new Integer(54), new Byte((byte)0x4e)); //小王

  如何隨機地獲得其中的N張牌呢?咱們的作法是生成一個0-55的隨機數,用這個隨機數做主鍵從Hashmap中得到對象,取得以後,把該對象從隊列中刪除,以避免重複取得。因爲java中的隨機數是根據時間生成的,因此有可能致使用戶獲得的牌不夠散,每一個用戶都摸到一條龍豈不是笑話?因此在生成隨機數的時候咱們加入了一個大素數來做運算:

    long cardId=new Long((Math.round(Math.random() * 87) % 55)).intvalue();

經過修改這個大素數,能夠控制某個用戶的牌比較好。


5、線程

  實際上本系統並無複雜的線程管理,可是我想提供一個控制檯讓管理員能夠管理遊戲主線程,可讓它中止、中段、恢復、重啓動,原本的設計是管理員經過與線程A打交道,經過A去管理主線程B,可是熟悉java線程的朋友都知道,線程互相管理基本上就是不實際的,舉個最簡單的例子,A如何銷燬B?也許你會說調用B的destroy()方法就行了,網上不少講解java線程的資料也確實是這麼說的,可是他們都是鬼扯的,本身去看看java源代碼吧,Thread.destroy()方法的實際代碼以下:

public void destroy() 
{
    throw new NoSuchMethodError();
}

  事實真相是,Thread.destroy()方法自始至終就沒有被實現過。全部寫文章,教別人用這個方法銷燬線程的人,都去撞牆吧,丟人丟大了。

  最好的辦法是A負責生成一個B而且啓動它,而後B本身管理生存週期,A和B經過使用可共享的方法來通訊,這是sun推薦的作法。


6、異步消息

  用戶玩牌的過程當中,有不少東西須要記錄下來,好比記錄用戶的積分、等級變化,記錄玩牌日誌供數據統計等,當用戶數量不少的時候,在數據庫中記錄這些信息會很耗費資源,用戶玩了一局以後會可能會等待很長時間。解決這個問題的方法是利用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 )。


7、啓動與退出

  爲了讓系統具有讓人滿意的性能,應該儘可能多的重用對象,減小建立新對象。好比上面提到的消息發送,咱們的操做是提供一個靜態類,在系統啓動的時候就初始化,保持與JMS服務器的鏈接,系統發送消息的時候,不用再去查詢JNDI和生成QueueConnectionFactory,這樣能夠提升系統響應速度。

  在數據庫鏈接池的問題上,咱們也採用一樣的操做,啓動的時候初始化N個鏈接。可是若是在關閉進程的時候不作任何操做,會致使JMS拋出socket異常,雖然沒什麼大的影響,但總顯得不專業,並且池中的鏈接不被釋放的話,也可能致使問題。最好可以讓系統像jboss等控制檯程序同樣,ctrl+c以後可以執行操做,釋放資源再退出。咱們能夠經過給進程/線程加上一個Hook來實現,windows程序員應該對這個很是熟悉。

Hook應該是一個線程方法,以下:

public class Hook extends Thread
{
    public void run()
    {
        //釋放數據庫鏈接,銷燬鏈接池
        //關閉與JMS的鏈接
    }
}

在主線程中加入:

    Runtime.getRuntime().addShutdownHook(new Hook()) ;

那麼進程/線程會在退出的時候執行Hook的run方法,清理資源。
相關文章
相關標籤/搜索