Comet技術應用

原文轉自:http://www.cnblogs.com/winnerlan/archive/2008/06/20.html

http://www.sujun.org/feed.asp?cateID=15

以前的舊文章,歸下類而已.....目前也在用red5做服務器開發一款比較小的flash網絡遊戲,呵呵...期待...

玩家移動信息同步例子,我已經寫過了兩個版本的啦,一個java的,一個是fms的,現在又寫了個red5的,其實這些的原理都是差不多的。以前好象沒怎 麼講解原理,這些就寫得詳細點吧。用red5做服務器,其實客戶端基本是fms一樣的。(事實上就是一樣,哈哈)。無非就是客戶端呼叫服務端的方法,服務 端呼叫客戶端的方法。(這是同步是沒有采用ShareObject的,過陣可能會寫吧)
先看下圖片吧


首先先來看看最基本的(具體的內容我就不寫了,代碼裏有詳細的解釋,在這裏我只說說思路和邏輯)
程序代碼 程序代碼

var mync:NetConnection = new NetConnection();   mync.connect("rtmp://localhost/game");連接到服務器
mync.onStatus = function(info)
{
if (info.code == "NetConnection.Connect.Success")
{
  trace("接通");
  //通知服務器,有新用戶登陸了
  this.call("userLogin",null,userName);
}
};


看這段代碼。當客戶端連接服務器成功後,馬上呼叫服務端的用戶登陸方法,並把新登陸的用戶名傳了過去。那麼我們再看服務端

程序代碼 程序代碼

/**
  * 用戶登陸
  * @param userName:用戶名
  */
public void userLogin(String userName)
{
  System.out.println("新用戶登陸:" + userName);
  //檢查是否已經有用戶登陸,有登陸,則呼叫其他用戶接受新登陸用戶的信息
  if(map.size() > 0)
  {
   for(Iterator<IServiceCapableConnection> it = map.values().iterator(); it.hasNext();)
   {
    IServiceCapableConnection ic = it.next();
    //發送用戶名過去
    ic.invoke("userLogin", new Object[]{userName});
   }
  }
  //放進map對象中保存
  map.put(userName,getCurrentConn());
}


當客戶端call的時候,服務器就執行userLogin裏的代碼。在這裏個方法裏服務器只做了兩件事,一是把map對象裏的所有的客戶端連接對象進行遍 歷,把新登陸用戶的信息發送給所有已經登陸的客戶端。然後再把當前的客戶端連接對象保存在服務端的map對象中。接着來看 ic.invoke ("userLogin", new Object[]{userName})這句代碼,他是呼叫客戶端方法;既然服務端呼叫客戶端 的userLogin方法,那麼我們可以過來客戶端的 userLogin方法。

程序代碼 程序代碼

//當有新用戶登陸時,被服務器呼叫
mync.userLogin = function(name:String)
{
//根據名字複製一個新的mc,最近登陸的用戶
var mc:MovieClip = _root.attachMovie("personMC","personMC" + name,depth++);
userSet.put(name,mc);
mc.setName(name);
//馬上呼叫客戶端,把自己的位置告訴新登陸的客戶端
mync.call("userInfo",null,userName,name,personMC._x,personMC._y);
};


在客戶端裏,該userLogin方法也是做了兩件事,一是馬上根據傳過來用戶名,生成了有一個新的mc(在實際中就是玩家,而且真正應用中,傳的還不止 一個參數),接着又呼叫服務端的一個方法userInfo,因爲它必須把自己的信息通知給剛剛登陸的用戶,包括自己的名字,位置等等。  接下來我們就看 服務端的userInfo方法啦

程序代碼 程序代碼

/**
  * 已經存在的用戶的信息
  * @param userName:用戶名
  * @param x:x座標
  * @param y:y座標
  */
public void userInfo(String selfName, String userName,double x,double y)
{
  Object[] ōbject = new Object[3];
  object[0] = selfName;
  object[1] = new Double(x);
  object[2] = new Double(y);
  map.get(userName).invoke("createUser",object);
}
服務端的這個方法就簡單啦,只做了一件非常簡單的時,就是把當前的傳過來的用戶信息,轉發一個指定的用戶就行了(其實就是最新登陸的那個用 戶) map.get(userName).invoke("createUser",object),呼叫了客戶端的createUser方法。客戶 端:
//更新已經登陸的用戶
mync.createUser = function(userName,x,y)
{
//根據名字複製一個新的mc
var mc:MovieClip = _root.attachMovie("personMC","personMC" + userName,depth++);
userSet.put(userName,mc);
mc.setName(userName);
mc._x = x;
mc._y = y;
}


客戶端的這個方法也很簡單,就是根據服務端轉發過來的信息,生成一個新的mc(玩家),同時還有位置。
好啦,到了,一個用戶登陸到服務器,在所有客戶端同步顯示的步驟就完成啦。
接下來就把最後的功能完成,就是當一個客戶端移動時,在其他客戶端的也做對應的動作。呵~~不用我說什麼也應該想了吧。恩,其實也是很簡單,就是當前用戶 操作時,做自己的操作命令通過服務端轉發其他所有的客戶端,就達到同步的目的了。例如這一句代碼:if( Key.isDown( Key.UP ) )
{
  personMC.up();
  mync.call("userAction",null,userName,"up");
}

當按下鍵盤的UP時,客戶端就呼叫服務起的userAction的方法,同時把自己的名字(才知道哪個用戶的動作)和動作(具體哪個動作)傳給了服務器,下面是服務端的代碼

程序代碼 程序代碼

public void userAction(String userName, String action)
{
  Object[] ōbject = new Object[2];
  object[0] = userName;
  object[1] = action;
  //對所有的用戶進行遍歷
  if(map.size() > 1)
  {
   //取出當前用戶的conn
   IServiceCapableConnection temp = getCurrentConn();
   for(Iterator<IServiceCapableConnection> it = map.values().iterator(); it.hasNext();)
   {
    IServiceCapableConnection iconn = it.next();
    //如果不是當前用戶,則發送信息
    if(temp != iconn)
    {
     iconn.invoke("userAction", object);
    }
   }
  }
}


恩,這個方法也簡單,就是把收到的信息發送給所有的客戶端,注意這句代碼
IServiceCapableConnection iconn = it.next();
    //如果不是當前用戶,則發送信息
    if(temp != iconn)
    {
     iconn.invoke("userAction", object);
    }
這是爲了防止把該信息發送給自己,因爲服務器是做遍歷的,也就是說會對所有登陸該服務器的用戶進行遍歷,所以應該排除發送該命令的用戶。接下來就看客戶端的userAction方法啦
//當其他用戶有動作時,被服務器呼叫
mync.userAction = function(name:String,action:String)
{
var mc:MovieClip = userSet.get(name);
//執行對應的方法
mc[action]();
};
它也只做了一件事,就是找出該用戶對應的mc(玩家),然後執行相應的動作
OK~~~代碼解釋就到這裏啦。光看我寫的這個還是不行,大家還有把源代碼提供去看看,大家有什麼問題,就到red5的專有論壇去討論吧。 www.openred5.com. 這個網站。那裏也有red5的配置教什麼的。這裏所以這裏我就不在講述啦。呵~本來想弄些漂亮的人物進去。不過沒什 麼時間啦,今天是犧牲中午睡眠時間寫的,大概花了一個小時,呵呵。如果很多人喜歡的話,往後會繼續發佈該系列的東西,例如用戶說話,攻擊,自己的名字是紅 色的等等,呵呵。

下載文件Red5源代碼下載

AS3與Red5之間的參數傳遞

2008年2月20日,14:30:47 | [email protected](棄天笑)
差不大一年沒去動red5了,現在因爲項目需要又開始使用red5,呵呵,先寫些基本應用吧
參數傳遞是最基本的,之前是as2,現在用as3與red5 0.63了,幾乎沒什麼變化.不過flash這邊的可以傳遞的參數也就多了一些.就基本的是
String,int,Number,Boolean,Array,對應到red5這邊是String,int,double,boolean,List
下面看基本的代碼吧:
Flash:
程序代碼 程序代碼

/**
 * @(#)ParamRed5.as
 * @author soda.C
 * @version  1.0
 * <br>Copyright (C), 2007 soda.C
 * <br>This program is protected by copyright laws.
 * <br>Program Name:GameHall
 * @data 2008-2-19
 */
package org.sujun.red5.test 
{
    import flash.display.Sprite;
    import flash.net.NetConnection;
    import flash.events.NetStatusEvent;
    import flash.events.SecurityErrorEvent;
    import flash.net.Responder;
    /**
     * 測試flash與red5之間參數的傳遞
     */
    public class ParamRed5 extends Sprite 
    {
        private var netConnection:NetConnection;
        
        public function ParamRed5():void
        {
            netConnection    = new NetConnection();
            
            netConnection.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
            netConnection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
            
           netConnection.connect("rtmp://localhost/paramtest");

        }
        
         private function netStatusHandler(event:NetStatusEvent):void 
         {
             trace("連接狀態:" + event.info["code"]);
             
            switch (event.info["code"]) 
            {
                case "NetConnection.Connect.Success":
                    trace("連接成功.....");
                    //呼叫服務器的baseParam方法,傳遞基本參數,string,int,number,Boolean
                    netConnection.call("baseParam", new Responder(baseParamResult),"soda.C",24,1000.1,false);
                    //封裝數組,int
                    var ary:Array = new Array();
                    ary.push(1);
                    ary.push(2);
                    ary.push(3);
                    //封裝數組,String
                    var ary1:Array = new Array();
                    ary1.push("a");
                    ary1.push("b");
                    ary1.push("c");
                    netConnection.call("receiveArray", new Responder(baseParamResult),ary,ary1);
                    break;
                case "NetStream.Play.StreamNotFound":
                    trace("Stream not found: ");
                    break;
            }
        }
        
        private function baseParamResult(obj:Object):void
        {
            trace(obj);
            trace("響應了.....");
        }
        private function securityErrorHandler(event:SecurityErrorEvent):void 
        {
            trace("securityErrorHandler: " + event);
        }
    }
}

接下來看java代碼
ParamRed5App.java,該類繼承了ApplicationAdapt
程序代碼 程序代碼

package org.sujun.red5.test;

import java.util.List;

import org.red5.server.adapter.ApplicationAdapter;

/**
 * 存放被flash客戶端調用的方法
 */
public class ParamRed5App extends ApplicationAdapter
{
    public ParamRed5App()
    {
        System.out.println("被初始化了......");
    }
    /**
     * 接受服務器傳過來的基本參數
     */
    public void baseParam(String name, int age, double value, boolean flag)
    {
        System.out.println("----name----" + name);
        System.out.println("----age----" + age);
        System.out.println("----value----" + value);
        System.out.println("----flag----" + flag);
    }
    /**
     * 接受客戶端傳遞過來的數組
     */
    public void receiveArray(List<Integer> intArray, List<String> strArray)
    {
        for(int i = 0; i < intArray.size(); i++)
        {
            System.out.println("----intArray----" + intArray.get(i).intValue());
        }
        for(int i = 0; i < intArray.size(); i++)
        {
            System.out.println("----strArray----" + strArray.get(i));
        }
    }
}

代碼很簡單.......直接複製過去,建立一個red5應用就可以使用了
看結果...


不過,還是我是傳上源代碼
下載文件點擊下載此源代碼
posted @ 2008-06-20 21:00 winnerlan 閱讀(390) | 評論 (0)編輯  編輯
http://www.javaeye.com/topic/148292
很多應用都需要將後臺發生的變化,實時傳送到客戶端,而無須客戶端不停地刷新、發送請求。本文首先介紹、比較了常用的“服務器 推”方案,着重介紹了 Comet - 使用 HTTP 長連接、無須瀏覽器安裝插件的兩種“服務器 推”方案:基於 AJAX 的長輪詢方式;基於 iframe 及 htmlfile 的流方式。最後分析了開發 Comet 應用需要注意的一些問題,以及如何藉助開源的 Comet 框架-pushlet 構建自己的“服務器 推”應用。

將“服務器 推”應用在 Web 程序中,首先考慮的是如何在功能有限的瀏覽器端接收、處理信息:

1. 客戶端如何接收、處理信息,是否需要使用套接口或是使用遠程調用。客戶端呈現給用戶的是 HTML 頁面還是 Java applet 或 Flash 窗口。如果使用套接口和遠程調用,怎麼和 JavaScript 結合修改 HTML 的顯示。
2. 客戶與服務器 端通信的信息格式,採取怎樣的出錯處理機制。
3. 客戶端是否需要支持不同類型的瀏覽器如 IE、Firefox,是否需要同時支持 Windows 和 Linux 平臺。

基於客戶端套接口的“服務器 推”技術

Flash XMLSocket

這種方案實現的基礎是:

1. Flash 提供了 XMLSocket 類。
2. JavaScript 和 Flash 的緊密結合:在 JavaScript 可以直接調用 Flash 程序提供的接口。

具體實現方法:在 HTML 頁面中內嵌入一個使用了 XMLSocket 類的 Flash 程序。JavaScript 通過調用此 Flash 程序提供的套接口接口與服務器 端的套接口進行通信。JavaScript 在收到服務器 端以 XML 格式傳送的信息後可以很容易地控制 HTML 頁面的內容顯示。

Javascript 與 Flash 的緊密結合,極大增強了客戶端的處理能力。從 Flash 播放器 V7.0.19 開始,已經取消了 XMLSocket 的端口必須大於 1023 的限制。Linux 平臺也支持 Flash XMLSocket 方案。但此方案的缺點在於:

1. 客戶端必須安裝 Flash 播放器;
2. 因爲 XMLSocket 沒有 HTTP 隧道功能,XMLSocket 類不能自動穿過防火牆;
3. 因爲是使用套接口,需要設置一個通信端口,防火牆、代理服務器 也可能對非 HTTP 通道端口進行限制;

不過這種方案在一些網絡聊天室,網絡互動遊戲中已得到廣泛使用。

Java Applet 套接口


在客戶端使用 Java Applet,通過 java.net.Socket 或 java.net.DatagramSocket 或 java.net.MulticastSocket 建立與服務器 端的套接口連接,從而實現“服務器 推”。

這種方案最大的不足在於 Java applet 在收到服務器 端返回的信息後,無法通過 JavaScript 去更新 HTML 頁面的內容。


基於 HTTP 長連接的“服務器 推”技術

Comet 簡介
下面將介紹兩種 Comet 應用的實現模型。
基於 AJAX 的長輪詢(long-polling)方式
AJAX 的出現使得 JavaScript 可以調用 XMLHttpRequest 對象發出 HTTP 請求,JavaScript 響應處理函數根據服務器 返回的信息對 HTML 頁面的顯示進行更新。使用 AJAX 實現“服務器 推”與傳統的 AJAX 應用不同之處在於:

1. 服務器 端會阻塞請求直到有數據傳遞或超時才返回。
2. 客戶端 JavaScript 響應處理函數會在處理完服務器 返回的信息後,再次發出請求,重新建立連接。
3. 當客戶端處理接收的數據、重新建立連接時,服務器 端可能有新的數據到達;這些信息會被服務器 端保存直到客戶端重新建立連接,客戶端會一次把當前服務器 端所有的信息取回。
因爲這種方案基於 AJAX,具有以下一些優點:請求異步發出;無須安裝插件;IE、Mozilla FireFox 都支持 AJAX。
Mozilla Firefox 提供了對 Streaming AJAX 的支持, 即 readystate 爲 3 時(數據仍在傳輸中),客戶端可以讀取數據,從而無須關閉連接,就能讀取處理服務器 端返回的信息。IE 在 readystate 爲 3 時,不能讀取服務器 返回的數據,目前 IE 不支持基於 Streaming AJAX。


基於 Iframe 及 htmlfile 的流(streaming)方式
通過在 HTML 頁面裏嵌入一個隱蔵幀,然後將這個隱蔵幀的 SRC 屬性設爲對一個長連接的請求,服務器 端就能源源不斷地往客戶端輸入數據。
每次數據傳送不會關閉連接,連接只會在通信出現錯誤時,或是連接重建時關閉(一些防火牆常被設置爲丟棄過長的連接, 服務器 端可以設置一個超時時間, 超時後通知 客戶端重新建立連接,並關閉原來的連接)。
用 iframe 請求一個長連接有一個很明顯的不足之處:IE、Morzilla Firefox 下端的進度欄都會顯示加載沒有完成,而且 IE 上方的圖標會不停的轉動,表示加載正在進行。Google 的天才們使用一個稱爲“htmlfile”的 ActiveX 解決了在 IE 中的加載顯示問題,並將這種方法用到了 gmail+gtalk 產品中。


使用 Comet 模型開發自己的應用

上面介紹了兩種基於 HTTP 長連接的“服務器 推”架構,更多描述了客戶端處理長連接的技術。對於一個實際的應用而言,系統的穩定性和性能是非常重要的。將 HTTP 長連接用於實際應用,很多細節需要考慮。

不要在同一客戶端同時使用超過兩個的 HTTP 長連接
HTTP 1.1 規範中規定,客戶端不應該與服務器 端建立超過兩個的 HTTP 連接, 新的連接會被阻塞。

服務器 端的性能和可擴展性
但是 AJAX 的應用使請求的出現變得頻繁,而 Comet 則會長時間佔用一個連接,上述的服務器 模型會變得非常低效,甚至可能會阻塞新的連接。

控制信息與數據信息使用不同的 HTTP 連接
使用長連接時,存在一個很常見的場景:客戶端網頁需要關閉,而服務器 端還處在讀取數據的堵塞狀態,客戶端需要及時通知 服務器 端關閉數據連接。服務器 在收到關閉請求後首先要從讀取數據的阻塞狀態喚醒,然後釋放爲這個客戶端分配的資源,再關閉連接。
所以在設計上,我們需要使客戶端的控制請求和數據請求使用不同的 HTTP 連接,才能使控制請求不會被阻塞。
在客戶和服務器 之間保持“心跳”信息
在瀏覽器與服務器 之間維持一個長連接會爲通信帶來一些不確定性:因爲數據傳輸是隨機的,客戶端不知道何時服務器 纔有數據傳送。服務器 端需要確保當客戶端不再工作時,釋放爲這個客戶端分配的資源,防止內存泄漏。因此需要一種機制使雙方知道大家都在正常運行。在實現上:
1. 服務器 端在阻塞讀時會設置一個時限,超時後調用會返回,同時發給客戶端沒有新數據到達的心跳信息。
2. 經過某個時限沒有收到客戶端的再次請求,會認爲客戶端不能正常工作,會釋放資源。
3. 當服務器 出現異常,需要通知 客戶端,同時釋放資源。

Pushlet - 開源 Comet 框架
Pushlet 是一個開源的 Comet 框架,在設計上有很多值得借鑑的地方,對於開發輕量級的 Comet 應用很有參考價值。

觀察者模型
Pushlet 使用了觀察者模型:客戶端發送請求,訂閱感興趣的事件;服務器 端爲每個客戶端分配一個會話 ID 作爲標記,事件源會把新產生的事件以多播的方式發送到訂閱者的事件隊列裏。

客戶端 JavaScript 庫

pushlet 提供了基於 AJAX 的 JavaScript 庫文件用於實現長輪詢方式的“服務器 推”;還提供了基於 iframe 的 JavaScript 庫文件用於實現流方式的“服務器 推”。
posted @ 2008-06-20 20:58 winnerlan 閱讀(708) | 評論 (2)編輯  編輯
類似於QQ遊戲百萬人同時在線的服務器架構實現
QQ遊戲於前幾日終於突破了百萬人同時在線的關口,向着更爲遠大的目標邁進,這讓其它衆多傳統的棋牌休閒遊戲平臺黯然失色,相比之下,聯衆似乎已經根本不 是QQ的對手,因爲QQ除了這100萬的遊戲在線人數外,它還擁有3億多的註冊量(當然很多是重複註冊的)以及QQ聊天軟件900萬的同時在線率,我們已 經可以預見未來由QQ構建起來的強大棋牌休閒遊戲帝國。
  那麼,在技術上,QQ遊戲到底是如何實現百萬人同時在線並保持遊戲高效率的呢?
  事實上,針對於任何單一的網絡服務器程序,其可承受的同時連接數目是有理論峯值的,通過C++中對TSocket的定義類型:word,我們可以判定 這個連接理論峯值是65535,也就是說,你的單個服務器程序,最多可以承受6萬多的用戶同時連接。但是,在實際應用中,能達到一萬人的同時連接並能保證 正常的數據交換已經是很不容易了,通常這個值都在2000到5000之間,據說QQ的單臺服務器同時連接數目也就是在這個值這間。
  如果要實現2000到5000用戶的單服務器同時在線,是不難的。在windows下,比較成熟的技術是採用IOCP--完成端口。與完成端口相關的 資料在網上和CSDN論壇裏有很多,感興趣的朋友可以自己搜索一下。只要運用得當,一個完成端口服務器是完全可以達到2K到5K的同時在線量的。但,5K 這樣的數值離百萬這樣的數值實在相差太大了,所以,百萬人的同時在線是單臺服務器肯定無法實現的。
  要實現百萬人同時在線,首先要實現一個比較完善的完成端口服務器模型,這個模型要求至少可以承載2K到5K的同時在線率(當然,如果你MONEY多, 你也可以只開發出最多允許100人在線的服務器)。在構建好了基本的完成端口服務器之後,就是有關服務器組的架構設計了。之所以說這是一個服務器組,是因 爲它絕不僅僅只是一臺服務器,也絕不僅僅是隻有一種類型的服務器。
  簡單地說,實現百萬人同時在線的服務器模型應該是:登陸服務器+大廳服務器+房間服務器。當然,也可以是其它的模型,但其基本的思想是一樣的。下面,我將逐一介紹這三類服務器的各自作用。
  登陸服務器:一般情況下,我們會向玩家開放若干個公開的登陸服務器,就如QQ登陸時讓你選擇的從哪個QQ遊戲服務器登陸一樣,QQ登陸時讓玩家選擇的 六個服務器入口實際上就是登陸服務器。登陸服務器主要完成負載平衡的作用。詳細點說就是,在登陸服務器的背後,有N個大廳服務器,登陸服務器只是用於爲當 前的客戶端連接選擇其下一步應該連接到哪個大廳服務器,當登陸服務器爲當前的客戶端連接選擇了一個合適的大廳服務器後,客戶端開始根據登陸服務器提供的信 息連接到相應的大廳上去,同時客戶端斷開與登陸服務器的連接,爲其他玩家客戶端連接登陸服務器騰出套接字資源。在設計登陸服務器時,至少應該有以下功 能:N個大廳服務器的每一個大廳服務器都要與所有的登陸服務器保持連接,並實時地把本大廳服務器當前的同時在線人數通知給各個登陸服務器,這其中包括:用 戶進入時的同時在線人數增加信息以及用戶退出時的同時在線人數減少信息。這裏的各個大廳服務器同時在線人數信息就是登陸服務器爲客戶端選擇某個大廳讓其登 陸的依據。舉例來說,玩家A通過登陸服務器1連接到登陸服務器,登陸服務器開始爲當前玩家在衆多的大廳服務器中根據哪一個大廳服務器人數比較少來選擇一個 大廳,同時把這個大廳的連接IP和端口發給客戶端,客戶端收到這個IP和端口信息後,根據這個信息連接到此大廳,同時,客戶端斷開與登陸服務器之間的連 接,這便是用戶登陸過程中,在登陸服務器這一塊的處理流程。
  大廳服務器:大廳服務器,是普通玩家看不到的服務器,它的連接IP和端口信息是登陸服務器通知給客戶端的。也就是說,在QQ遊戲的本地文件中,具體的 大廳服務器連接IP和端口信息是沒有保存的。大廳服務器的主要作用是向玩家發送遊戲房間列表信息,這些信息包括:每個遊戲房間的類型,名稱,在線人數,連 接地址以及其它如遊戲幫助文件URL的信息。從界面上看的話,大廳服務器就是我們輸入用戶名和密碼並校驗通過後進入的遊戲房間列表界面。大廳服務器,主要 有以下功能:一是向當前玩家廣播各個遊戲房間在線人數信息;二是提供遊戲的版本以及下載地址信息;三是提供各個遊戲房間服務器的連接IP和端口信息;四是 提供遊戲幫助的URL信息;五是提供其它遊戲輔助功能。但在這衆多的功能中,有一點是最爲核心的,即:爲玩家提供進入具體的遊戲房間的通道,讓玩家順利進 入其欲進入的遊戲房間。玩家根據各個遊戲房間在線人數,判定自己進入哪一個房間,然後雙擊服務器列表中的某個遊戲房間後玩家開始進入遊戲房間服務器。
  遊戲房間服務器:遊戲房間服務器,具體地說就是如“鬥地主1”,“鬥地主2”這樣的遊戲房間。遊戲房間服務器纔是具體的負責執行遊戲相關邏輯的服務 器。這樣的遊戲邏輯分爲兩大類:一類是通用的遊戲房間邏輯,如:進入房間,離開房間,進入桌子,離開桌子以及在房間內說話等;第二類是遊戲桌子邏輯,這個 就是各種不同類型遊戲的主要區別之處了,比如鬥地主中的叫地主或不叫地主的邏輯等,當然,遊戲桌子邏輯裏也包括有通用的各個遊戲裏都存在的遊戲邏輯,比如 在桌子內說話等。總之,遊戲房間服務器纔是真正負責執行遊戲具體邏輯的服務器。
  這裏提到的三類服務器,我均採用的是完成端口模型,每個服務器最多連接數目是5000人,但是,我在遊戲房間服務器上作了邏輯層的限定,最多隻允許 300人同時在線。其他兩個服務器仍然允許最多5000人的同時在線。如果按照這樣的結構來設計,那麼要實現百萬人的同時在線就應該是這樣:首先是大 廳,1000000/5000=200。也就是說,至少要200臺大廳服務器,但通常情況下,考慮到實際使用時服務器的處理能力和負載情況,應該至少準備 250臺左右的大廳服務器程序。另外,具體的各種類型的遊戲房間服務器需要多少,就要根據當前玩各種類型遊戲的玩家數目分別計算了,比如鬥地主最多是十萬 人同時在線,每臺服務器最多允許300人同時在線,那麼需要的鬥地主服務器數目就應該不少於:100000/300=333,準備得充分一點,就要準備 350臺鬥地主服務器。
  除正常的玩家連接外,還要考慮到:
  對於登陸服務器,會有250臺大廳服務器連接到每個登陸服務器上,這是始終都要保持的連接;
  而對於大廳服務器而言,如果僅僅有鬥地主這一類的服務器,就要有350多個連接與各個大廳服務器始終保持着。所以從這一點看,我的結構在某些方面還存在着需要改進的地方,但核心思想是:儘快地提供用戶登陸的速度,儘可能方便地讓玩家進入遊戲中。
posted @ 2008-06-20 20:57 winnerlan 閱讀(96) | 評論 (0)編輯  編輯
基於 WEB 的實時事件通知方式大致有五種方案:HTTP拉取方式(pull),HTTP流,Long Polling,FlashXMLSocket方式,Java Applet。


首先說下Comet這個詞,Comet 這個詞是最早由Alex Russell(DojoToolkit 的項目 Lead)提出的,稱基於 HTTP 長連接、無須在瀏覽器端安裝插件的“服務器推(Push)”技術爲“Comet”。


1.HTTP拉取方式(pull)
在這種傳統的方法中,客戶端以用戶可定義的時間間隔去檢查服務器上的最新數據。這種拉取方式的頻率要足夠高才能保證很高的數據精確度,但高頻率可能會導致 多餘的檢查,從而導致較高的網絡流量。而另一方面,低頻率則會導致錯過更新的數據。理想地,拉取的時間間隔應該等於服務器狀態改變的速度。常見的實現如利 用 " <meta http-equiv="refresh" c />" tag,當然利用xmlHttpRequest定時取也是一種方法。


2.HTTP流(Push機制)
HTTP流有兩種形式:
* Page Stream: 頁面上不間斷的HTTP連接響應(HTTP 1.1Keep Alive).
  通過在 HTML 頁面裏嵌入一個隱蔵幀(iframe),然後將這個隱蔵幀的 SRC屬性設爲對一個長連接的請求,服務器端就能源源不斷地往客戶端輸入數據。
* Service Stream:XMLHttpRequest連接中的服務器數據流。
  客戶端是在 XMLHttpRequest 的 readystate 爲4(即數據傳輸結束)時調用回調函數,進行信息處理。當 readystate 爲 4 時,數據傳輸結束,連接已經關閉。Mozilla Firefox 提供了對Streaming AJAX 的支持,即 readystate 爲 3時(數據仍在傳輸中),客戶端可以讀取數據,從而無須關閉連接,就能讀取處理服務器端返回的信息。IE 在 readystate 爲 3時,不能讀取服務器返回的數據,目前 IE 不支持基於 Streaming AJAX。

注:使用 Page Stream(iframe) 請求一個長連接有一個很明顯的不足之處:IE、Morzilla Firefox下端的進度欄都會顯示加載沒有完成,而且 IE 上方的圖標會不停的轉動,表示加載正在進行。Google 的天才們使用一個稱爲“htmlfile”的 ActiveX解決了在 IE 中的加載顯示問題,並將這種方法用到了 gmail+gtalk 產品中。Alex Russell 在 “What else is burrieddown in the depth's of Google's amazing JavaScript?”文章中介紹了這種方法。Zeitoun 網站提供的comet-iframe.tar.gz,封裝了一個基於 iframe 和 htmlfile 的 JavaScript comet 對象,支持IE、Mozilla Firefox 瀏覽器,可以作爲參考。(http://alex.dojotoolkit.org/?p=538)

3.長時間輪詢(Long Polling)
也就是所謂的異步輪詢(AsynchronousPolling),這種方式是純服務器端推送方式和客戶端拉取方式的混合。它是基於BAYEUX協議 (http://svn.xantus.org/shortbus/trunk/bayeux/bayeux.html) 的。這個協議遵循基於主題的發佈——訂閱機制。在訂閱了某個頻道後,客戶端和服務器間的連接會保持打開狀態,並保持一段事先定義好的時間(默認爲45 秒)。如果服務器端沒有事件發生,而發生了超時,服務器端就會請求客戶端進行異步重新連接。如果有事件發生,服務器端會發送數據到客戶端,然後客戶端重新 連接。
  1.  服務器端會阻塞請求直到有數據傳遞或超時才返回。
  2. 客戶端 JavaScript響應處理函數會在處理完服務器返回的信息後,再次發出請求,重新建立連接。
  3.當客戶端處理接收的數據、重新建立連接時,服務器端可能有新的數據到達;這些信息會被服務器端保存直到客戶端重新建立連接,客戶端會一次把當前服務器端所有的信息取回。


4.Flash XMLSocket(push機制)


這種方案實現的基礎是:
  1. 安裝了 Flash 播放器,Flash 提供了 XMLSocket 類(Flash 7.0.14以上版本)。
  2.JavaScript 和 Flash 的緊密結合:在 JavaScript 可以直接調用 Flash 程序提供的接口。

具體實現方法:在 HTML 頁面中內嵌入一個使用了 XMLSocket 類的 Flash 程序。JavaScript 通過調用此 Flash程序提供的套接口接口與服務器端的套接口進行通信。JavaScript 在收到服務器端以 XML 格式傳送的信息後可以很容易地控制 HTML頁面的內容顯示。

關於如何去構建 JavaScript 與 Flash XMLSocket 的 Flash 程序,以及如何在 JavaScript 裏調用Flash 提供的接口,我們可以參考 AFLAX(Asynchronous Flash and XML)項目提供的 Socket Demo 以及SocketJS(請參見 [http://www.aflax.org/ Asynchronous Flash and XML,提供了強大的Flash、Javascript 庫和很多範例。])。

Javascript 與 Flash 的緊密結合,極大增強了客戶端的處理能力。從 Flash 播放器 V7.0.19 開始,已經取消了XMLSocket 的端口必須大於 1023 的限制。Linux 平臺也支持 Flash XMLSocket 方案。但此方案的缺點在於:
  1. 客戶端必須安裝 Flash 播放器;
  2. 因爲 XMLSocket 沒有 HTTP 隧道功能,XMLSocket類不能自動穿過防火牆;
  3. 因爲是使用Socket接口,需要設置一個通信端口,防火牆、代理服務器也可能對非 HTTP通道端口進行限制;
  4. 必須使用XML格式作爲消息格式,數據冗餘增大。

此方案在一些網絡聊天室,網絡互動遊戲中得到廣泛使用。

5. Java Applet(Push機制)
類似於Flash XMLSocket方式。目前已經很少使用,原因極可能是因在手機等移動終端缺少支持。


總結和建議:

如果我們想要高數據一致性和高網絡性能,我們就應該選擇推送方式。但是,推送會帶來一些擴展性問題;服務器應用程序CPU使用率是拉取方式的7倍。根據 TUD(http://swerl.tudelft.nl/twiki/pub/Main/TechnicalReports/TUD-SERG- 2007-016.pdf)的測試結果,服務器性能會在350-500個用戶時趨於飽和。對於更大數量的用戶,服務器端需要維護大量併發的長連接。在這種 應用背景下,服務器端需要考慮負載均衡和集羣技術;或是在服務器端爲長連接作一些改進。

使用拉取方式,要想達到完整的數據一致性以及很高的網絡性能是很困難的。如果拉取的時間間隔大於數據更新的時間間隔,就會發生一些數據的遺失。而如果小於 數據更新的時間間隔,網絡性能就會受到影響。拉取方式只有在拉取時間間隔等同於數據更新時間間隔時,纔會恰到好處。但是,爲了達到那樣的目標,我們就需要 提前知道準確的數據更新時間間隔。然而,數據更新的時間間隔很少是靜態不變並可以預知的。這使得拉取方式只有在數據是根據某種特定模式發佈的情況纔有用。

控制信息與數據信息使用不同的 HTTP 連接

使用長連接時,存在一個很常見的場景:客戶端網頁需要關閉,而服務器端還處在讀取數據的堵塞狀態,客戶端需要及時通知服務器端關閉數據連接。服務 器在收到關閉請求後首先要從讀取數據的阻塞狀態喚醒,然後釋放爲這個客戶端分配的資源,再關閉連接。所以在設計上,我們需要使客戶端的控制請求和數據請求 使用不同的 HTTP 連接,才能使控制請求不會被阻塞。

在實現上,如果是基於 iframe 流方式的長連接,客戶端頁面需要使用兩個iframe,一個是控制幀,用於往服務器端發送控制請求,控制請求能很快收到響應,不會被堵塞;一個是顯示幀, 用於往服務器端發送長連接請求。如果是基於 AJAX的長輪詢方式,客戶端可以異步地發出一個 XMLHttpRequest 請求,通知服務器端關閉數據連接。


在客戶和服務器之間保持“心跳”信息
在瀏覽器與服務器之間維持一個長連接會爲通信帶來一些不確定性:因爲數據傳輸是隨機的,客戶端不知道何時服務器纔有數據傳送。服務器端需要確保當客戶端不再工作時,釋放爲這個客戶端分配的資源,防止內存泄漏。因此需要一種機制使雙方知道大家都在正常運行。在實現上:
  1.服務器端在阻塞讀時會設置一個時限,超時後阻塞讀調用會返回,同時發給客戶端沒有新數據到達的心跳信息。此時如果客戶端已經關閉,服務器往通道寫數據會出現異常,服務器端就會及時釋放爲這個客戶端分配的資源。
  2. 如果客戶端使用的是基於 AJAX的長輪詢方式;服務器端返回數據、關閉連接後,經過某個時限沒有收到客戶端的再次請求,會認爲客戶端不能正常工作,會釋放爲這個客戶端分配、維護的資源。
  3. 當服務器處理信息出現異常情況,需要發送錯誤信息通知客戶端,同時釋放資源、關閉連接。
posted @ 2008-06-20 20:56 winnerlan 閱讀(109) | 評論 (0)編輯  編輯

你需要用Ajax技術,你可以在網上查相關資料

我明白你的意思,你需要服務器端發出通知,而不是用客戶端提出請求。

我告訴你以前我是怎麼做的吧,你到網上查一個jetty的jsp平臺,我以前用的是jetty6.0 ,現在應該有更高版本了,jetty6.0提供了一個叫做continuous connection的東西,這是什麼呢,簡單解釋一下,就是一種對服務器端收到http請求,但是不馬上返回結果,一直掛起,知道服務器發生變化,覺得 應該通知客戶端的時候,對客戶端返回請求。

換句話說,只要你的客戶端一開始向你服務器端請求數據,服務器端掛起請求,在有需要時再返回客戶端,客戶端使用Ajax異步處理,在回調函數裏寫上相關處 理,服務器端沒有返回時回調函數不會被觸發。這樣就能做到即時的消息通知,而且並不像客戶端不斷refresh一樣佔用無效的流量。

jetty6.0裏面提供了一個chat的sample,這個技術就是爲聊天室設計的。

關於連接數的問題,其實65534是一個錯誤的數字,首先,如果你用的是windows平臺的服務器,很遺憾,上限2048,因爲以前我也做過相關東西,所以也找過破解,沒有辦法改變這個連接數。

然後,如果你願意用其他平臺,那麼可以告訴你Solaris10的連接數達到十萬,我有個朋友就是使用jetty6.0和solaris10的組合做的聊天室,通訊效果不錯的。

客戶端執行簡單的js腳本,就是IE,firefox這樣的瀏覽器。
如果客戶端發出請求後,服務器馬上回復,還有後面的事情麼?

資料你上他們官方網站看好了,這只是Ajax技術應用的一個極端而已。

我這裏假設你瞭解Ajax技術吧,不瞭解可以上網查。

客戶端裏的js代碼使用Ajax通過XmlHttpRequest請求服務器端,服務器端不返回,形成長連接,等服務器端有數據更新,服務器返回,Ajax異步處理,這樣子通信就是即時的。

jetty官方網站:
http://www.mortbay.org/
你到上面下一個jetty下來,裏面有chat這個sample的。

solaris10支持10萬連接數,這個我就不解釋了,你到sun公司裏面看他的產品介紹就知道了,可以免費下載的。

我這裏也沒什麼資料,開發文檔涉及商業機密,不可能給你的。只是給你一個提示而已,另外也糾正你一個誤區,就是連接數的問題,你服務器端爲什麼要對每一個請求新開一個端口呢?難道就不能所有連接共用一個端口?(比如80端口)

我現在又不知道你基礎怎樣,所以也只能說到這裏吧。我覺得你只是需要一種即時通信的思路了,什麼服務器作爲後臺不重要。如果你技術過關,自己寫一個web服務器處理這種併發請求也是可以的,不一定要用jetty(使用jetty需要JAVA基礎)

另外,這個思路最關鍵的地方就是要掛起客戶端的請求,延時返回。

只要你不使用activeX,或者java applet的方式,只是輕量級的http瘦客戶端,你就必須明白,你必須遵循request-respond的機制,這個是不可能改變的,不管你是 xmlhttprequest還是不斷refresh,你只能在這個機制上加以修改,讓他達到服務器通知的效果。

延時返回的方法有很多,大部分http服務器都可以提供,比如php裏面可以sleep,我之所以使用jetty是因爲它是很底層的,可以很底層的管理每 一個請求的線程,而且專門設計過一個掛起請求的解決方案和專用的類,這樣的好處就是掛起客戶端請求的開銷可以降到最小,用php或者.net開銷都過大 了,因爲他們沒有爲此專門設計過。

太感謝了,我確實沒這方面的基礎,現在先看看可行性,以後開發裏,還得多多請教。5天后給你加分哈
另外,如你所說,jetty是性能最好的,但我想問下,如果我想做到商用的產品裏去的話,用jetty合適嗎?
或者用什麼方案最適合?
jetty的部署你可以在他的官方網站上看看,可以很好的部署,甚至可以加到對方系統服務裏面。

另外,我的方案只是在輕量級瘦客戶端開發上提出的,這個前提是你提供服務器,對方只提供客戶端。你想做成商品,你的客戶想要看到的展示很可能是眩目的效果,js不是不能寫很漂亮的效果,可是js的表現力再強也強不過flash,哪怕你用過所有的css濾鏡。

而flash裏面有socket編程,帶flash的頁面屬於富客戶端,它有天然的socket長連接,不需要你改裝http的request-response機制。你只需要再寫一個socket服務器段來監聽請求,建立連接,就能向客戶端發出通知了

發佈了20 篇原創文章 · 獲贊 4 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章