<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    小菜毛毛技術分享

    與大家共同成長

      BlogJava :: 首頁 :: 聯系 :: 聚合  :: 管理
      164 Posts :: 141 Stories :: 94 Comments :: 0 Trackbacks
    轉載IBM開發社區
    受異步服務器端事件驅動的 Ajax 應用程序實現較為困難,并且難于擴展。Philip McCarthy 在其廣受歡迎的 系列文章 中介紹了一種行之有效的方法:結合使用 Comet 模式(將數據推到客戶機)和 Jetty 6 的 Continuations API(將 Comet 應用程序擴展到大量客戶機中)。您可以方便地在 Direct Web Remoting (DWR) 2 中將 Comet 和 Continuations 與 Reverse Ajax 技術結合使用。

    作為一種廣泛使 用的 Web 應用程序開發技術,Ajax 牢固確立了自己的地位,隨之而來的是一些通用 Ajax 使用模式。例如,Ajax 經常用于對用戶輸入作出響應,然后使用從服務器獲得的新數據修改頁面的部分內容。但是,有時 Web 應用程序的用戶界面需要進行更新以響應服務器端發生的異步事件,而不需要用戶操作 —— 例如,顯示到達 Ajax 聊天應用程序的新消息,或者在文本編輯器中顯示來自另一個用戶的改變。由于只能由瀏覽器建立 Web 瀏覽器和服務器之間的 HTTP 連接,服務器無法在改動發生時將變化 “推送” 給瀏覽器。

    Ajax 應用程序可以使用兩種基本的方法解決這一問題:一種方法是瀏覽器每隔若干秒時間向服務器發出輪詢以進行更新,另一種方法是服務器始終打開與瀏覽器的連接并在數據可用時發送給瀏覽器。長期連接技術被稱為 Comet(請參閱 參考資料)。本文將展示如何結合使用 Jetty servlet 引擎和 DWR 簡捷有效地實現一個 Comet Web 應用程序。

    為什么使用 Comet?

    輪 詢方法的主要缺點是:當擴展到更多客戶機時,將生成大量的通信量。每個客戶機必須定期訪問服務器以檢查更新,這為服務器資源添加了更多負荷。最壞的一種情 況是對不頻繁發生更新的應用程序使用輪詢,例如一種 Ajax 郵件 Inbox。在這種情況下,相當數量的客戶機輪詢是沒有必要的,服務器對這些輪詢的回答只會是 “沒有產生新數據”。雖然可以通過增加輪詢的時間間隔來減輕服務器負荷,但是這種方法會產生不良后果,即延遲客戶機對服務器事件的感知。當然,很多應用程 序可以實現某種權衡,從而獲得可接受的輪詢方法。

    盡管如此,吸引人們使用 Comet 策略的其中一個優點是其顯而易見的高效性??蛻魴C不會像使用輪詢方法那樣生成煩人的通信量,并且事件發生后可立即發布給客戶機。但是保持長期連接處于打開 狀態也會消耗服務器資源。當等待狀態的 servlet 持有一個持久性請求時,該 servlet 會獨占一個線程。這將限制 Comet 對傳統 servlet 引擎的可伸縮性,因為客戶機的數量會很快超過服務器棧能有效處理的線程數量。





    回頁首


    Jetty 6 有何不同

    Jetty 6 的目的是擴展大量同步連接,使用 Java™ 語言的非阻塞 I/O(java.nio)庫并使用一個經過優化的輸出緩沖架構(參閱 參考資料)。Jetty 還為處理長期連接提供了一些技巧:該特性稱為 Continuations。 我將使用一個簡單的 servlet 對 Continuations 進行演示,這個 servlet 將接受請求,等待處理,然后發送響應。接下來,我將展示當客戶機數量超過服務器提供的處理線程后發生的狀況。最后,我將使用 Continuations 重新實現 servlet,您將了解 Continuations 在其中扮演的角色。

    為了便于理解下面的示例,我將把 Jetty servlet 引擎限制在一個單請求處理線程。清單 1 展示了 jetty.xml 中的相關配置。我實際上需要在 ThreadPool 使用三個線程:Jetty 服務器本身使用一個線程,另一線程運行 HTTP 連接器,偵聽到來的請求。第三個線程執行 servlet 代碼。


    清單 1. 單個 servlet 線程的 Jetty 配置
                    
    <?xml version="1.0"?>
    <!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN"
    "http://jetty.mortbay.org/configure.dtd">
    <Configure id="Server" class="org.mortbay.jetty.Server">
    <Set name="ThreadPool">
    <New class="org.mortbay.thread.BoundedThreadPool">
    <Set name="minThreads">3</Set>
    <Set name="lowThreads">0</Set>
    <Set name="maxThreads">3</Set>
    </New>
    </Set>
    </Configure>

    接下來,為了模擬對異步事件的等待,清單 2 展示了 BlockingServletservice() 方法,該方法將使用 Thread.sleep() 調用在線程結束之前暫停 2000 毫秒的時間。它還在執行開始和結束時輸出系統時間。為了區別輸出和不同的請求,還將作為標識符的請求參數記錄在日志中。


    清單 2. BlockingServlet
                    
    public class BlockingServlet extends HttpServlet {

    public void service(HttpServletRequest req, HttpServletResponse res)
    throws java.io.IOException {

    String reqId = req.getParameter("id");

    res.setContentType("text/plain");
    res.getWriter().println("Request: "+reqId+"\tstart:\t" + new Date());
    res.getWriter().flush();

    try {
    Thread.sleep(2000);
    } catch (Exception e) {}

    res.getWriter().println("Request: "+reqId+"\tend:\t" + new Date());
    }
    }

    現在可以觀察到 servlet 響應一些同步請求的行為。清單 3 展示了控制臺輸出,五個使用 lynx 的并行請求。命令行啟動五個 lynx 進程,將標識序號附加在請求 URL 的后面。


    清單 3. 對 BlockingServlet 并發請求的輸出
                    

    $ for i in 'seq 1 5' ; do lynx -dump localhost:8080/blocking?id=$i & done
    Request: 1 start: Sun Jul 01 12:32:29 BST 2007
    Request: 1 end: Sun Jul 01 12:32:31 BST 2007

    Request: 2 start: Sun Jul 01 12:32:31 BST 2007
    Request: 2 end: Sun Jul 01 12:32:33 BST 2007

    Request: 3 start: Sun Jul 01 12:32:33 BST 2007
    Request: 3 end: Sun Jul 01 12:32:35 BST 2007

    Request: 4 start: Sun Jul 01 12:32:35 BST 2007
    Request: 4 end: Sun Jul 01 12:32:37 BST 2007

    Request: 5 start: Sun Jul 01 12:32:37 BST 2007
    Request: 5 end: Sun Jul 01 12:32:39 BST 2007

    清單 3 中的輸出和預期一樣。因為 Jetty 只可以使用一個線程執行 servlet 的 service() 方法。Jetty 對請求進行排列,并按順序提供服務。當針對某請求發出響應后將立即顯示時間戳(一個 end 消息),servlet 接著處理下一個請求(后續的 start 消息)。因此即使同時發出五個請求,其中一個請求必須等待 8 秒鐘的時間才能接受 servlet 處理。

    請注意,當 servlet 被阻塞時,執行任何操作都無濟于事。這段代碼模擬了請求等待來自應用程序不同部分的異步事件。這里使用的服務器既不是 CPU 密集型也不是 I/O 密集型:只有線程池耗盡之后才會對請求進行排隊。

    現在,查看 Jetty 6 的 Continuations 特性如何為這類情形提供幫助。清單 4 展示了 清單 2 中使用 Continuations API 重寫后的 BlockingServlet。我將稍后解釋這些代碼。


    清單 4. ContinuationServlet
                    
    public class ContinuationServlet extends HttpServlet {

    public void service(HttpServletRequest req, HttpServletResponse res)
    throws java.io.IOException {

    String reqId = req.getParameter("id");

    Continuation cc = ContinuationSupport.getContinuation(req,null);

    res.setContentType("text/plain");
    res.getWriter().println("Request: "+reqId+"\tstart:\t"+new Date());
    res.getWriter().flush();

    cc.suspend(2000);

    res.getWriter().println("Request: "+reqId+"\tend:\t"+new Date());
    }
    }

    清單 5 展示了對 ContinuationServlet 的五個同步請求的輸出;請與 清單 3 進行比較。


    清單 5. 對 ContinuationServlet 的五個并發請求的輸出
                    
    $ for i in 'seq 1 5' ; do lynx -dump localhost:8080/continuation?id=$i & done

    Request: 1 start: Sun Jul 01 13:37:37 BST 2007
    Request: 1 start: Sun Jul 01 13:37:39 BST 2007
    Request: 1 end: Sun Jul 01 13:37:39 BST 2007

    Request: 3 start: Sun Jul 01 13:37:37 BST 2007
    Request: 3 start: Sun Jul 01 13:37:39 BST 2007
    Request: 3 end: Sun Jul 01 13:37:39 BST 2007

    Request: 2 start: Sun Jul 01 13:37:37 BST 2007
    Request: 2 start: Sun Jul 01 13:37:39 BST 2007
    Request: 2 end: Sun Jul 01 13:37:39 BST 2007

    Request: 5 start: Sun Jul 01 13:37:37 BST 2007
    Request: 5 start: Sun Jul 01 13:37:39 BST 2007
    Request: 5 end: Sun Jul 01 13:37:39 BST 2007

    Request: 4 start: Sun Jul 01 13:37:37 BST 2007
    Request: 4 start: Sun Jul 01 13:37:39 BST 2007
    Request: 4 end: Sun Jul 01 13:37:39 BST 2007

    清單 5 中有兩處需要重點注意。首先,每個 start 消息出現兩次;先不要著急。其次,更重要的一點,請求現在不需排隊就能夠并發處理,注意所有 startend 消息的時間戳是相同的。因此,每個請求的處理時間不會超過兩秒,即使只運行一個 servlet 線程。





    回頁首


    Jetty Continuations 機制原理

    理解了 Jetty Continuations 機制的實現原理,您就能夠解釋 清單 5 中的現象。要使用 Continuations,必須對 Jetty 進行配置,以使用其 SelectChannelConnector 處理請求。這個連接器構建在 java.nio API 之上,因此使它能夠不用消耗每個連接的線程就可以持有開放的連接。當使用 SelectChannelConnector 時,ContinuationSupport.getContinuation() 將提供一個 SelectChannelConnector.RetryContinuation 實例。(然而,您應該只針對 Continuation 接口進行編碼;請參閱 Portability and the Continuations API。)當對 RetryContinuation 調用 suspend() 時,它將拋出一個特殊的運行時異常 —— RetryRequest —— 該異常將傳播到 servlet 以外并通過過濾器鏈傳回,并由 SelectChannelConnector 捕獲。 但是發生該異常之后并沒有將響應發送給客戶機,請求被放到處于等待狀態的 Continuation 隊列中,而 HTTP 連接仍然保持打開狀態。此時,為該請求提供服務的線程將返回 ThreadPool,用以為其他請求提供服務。

    可移植性和 Continuations API

    我提到過應該使用 Jetty 的 SelectChannelConnector 來啟用 Continuations 功能。然而,Continuations API 仍然可用于傳統的 SocketConnector,這種情況下 Jetty 將回退到不同的 Continuation 實現,該實現使用 wait()/notify() 方法。您的代碼仍然可以編譯和運行,但是卻失去了非阻塞 Continuations 的優點。如果您希望繼續使用非 Jetty 服務器,您應該考慮編寫自己的 Continuation 包裝器,在運行時期使用反射檢查 Jetty Continuations 庫是否可用。DWR 就使用了這種策略。

    暫停的請求將一直保持在等待狀態的 Continuation 隊列,直到超出指定的時限,或者當對 resume() 方法的 Continuation 調用 resume() 時(稍后將詳細介紹)。出現上述任意一種條件時,請求將被重新提交到 servlet(通過過濾器鏈)。事實上,整個請求被重新進行處理,直到首次調用 suspend()。當執行第二次發生 suspend() 調用時,RetryRequest 異常不會被拋出,執行照常進行。

    現在應該可以解釋 清單 5 中的輸出了。每個請求依次進入 servlet 的 service() 方法后,將發送 start 消息進行響應,Continuationsuspend() 方法引發 servlet 異常,將釋放線程使其處理下一個請求。所有五個請求快速通過 service() 方法的第一部分,并進入等待狀態,并且所有 start 消息將在幾毫秒內輸出。兩秒后,當超過 suspend() 的時限后,將從等待隊列中檢索第一個請求,并將其重新提交給 ContinuationServlet。第二次輸出 start 消息,立即返回對 suspend() 的第二次調用,并且發送 end 消息進行響應。然后將在此執行 servlet 代碼來處理隊列中的下一個請求,以此類推。

    因此,在 BlockingServletContinuationServlet 兩種情況中,請求被放入隊列中以訪問單個 servlet 線程。然而,雖然 servlet 線程執行期間 BlockingServlet 發生兩秒暫停,SelectChannelConnector 中的 ContinuationServlet 的暫停發生在 servlet 之外。ContinuationServlet 的總吞吐量更高一些,因為 servlet 線程沒有將大部分時間用在 sleep() 調用中。





    回頁首


    使 Continuations 變得有用

    現在您已經了解到 Continuations 能夠不消耗線程就可以暫停 servlet 請求,我需要進一步解釋 Continuations API 以向您展示如何在實際應用中使用。

    resume() 方法生成一對 suspend()??梢詫⑺鼈円暈闃藴实?Object wait()/notify() 機制的 Continuations 等價體。就是說,suspend() 使 Continuation(因此也包括當前方法的執行)處于暫停狀態,直到超出時限,或者另一個線程調用 resume()。suspend()/resume() 對于實現真正使用 Continuations 的 Comet 風格的服務非常關鍵。其基本模式是:從當前請求獲得 Continuation,調用 suspend(),等待異步事件的到來。然后調用 resume() 并生成一個響應。

    然而,與 Scheme 這種語言中真正的語言級別的 continuations 或者是 Java 語言的 wait()/notify() 范例不同的是,對 Jetty Continuation 調用 resume() 并不意味著代碼會從中斷的地方繼續執行。正如您剛剛看到的,實際上和 Continuation 相關的請求被重新處理。這會產生兩個問題:重新執行 清單 4 中的 ContinuationServlet 代碼,以及丟失狀態:即調用 suspend() 時丟失作用域內所有內容。

    第一個問題的解決方法是使用 isPending() 方法。如果 isPending() 返回值為 true,這意味著之前已經調用過一次 suspend(),而重新執行請求時還沒有發生第二次 suspend() 調用。換言之,根據 isPending() 條件在執行 suspend() 調用之前運行代碼,這樣將確保對每個請求只執行一次。在 suspend() 調用具有等冪性之前,最好先對應用程序進行設計,這樣即使調用兩次也不會出現問題,但是某些情況下無法使用 isPending() 方法。Continuation 也提供了一種簡單的機制來保持狀態:putObject(Object)getObject() 方法。在 Continuation 發生暫停時,使用這兩種方法可以保持上下文對象以及需要保存的狀態。您還可以使用這種機制作為在線程之間傳遞事件數據的方式,稍后將演示這種方法。





    回頁首


    編寫基于 Continuations 的應用程序

    作 為實際示例場景,我將開發一個基本的 GPS 坐標跟蹤 Web 應用程序。它將在不規則的時間間隔內生成隨機的經緯度值對。發揮一下想象力,生成的坐標值可能就是臨近的一個公共車站、隨身攜帶著 GPS 設備的馬拉松選手、汽車拉力賽中的汽車或者運輸中的包裹。令人感興趣的是我將如何告訴瀏覽器這個坐標。圖 1 展示了這個簡單的 GPS 跟蹤器應用程序的類圖:


    圖 1. 顯示 GPS 跟蹤器應用程序主要組件的類圖
    GPS 跟蹤器組件的 UML 類圖

    首先,應用程序需要某種方法來生成坐標。這將由 RandomWalkGenerator 完成。從一對初始坐標對開始,每次調用它的私有 generateNextCoord() 方法時,將從該位置移動隨機指定的距離,并將新的位置作為 GpsCoord 對象返回。初始化完成后,RandomWalkGenerator 將生成一個線程,該線程以隨機的時間間隔調用 generateNextCoord() 方法并將生成的坐標發送給任何注冊了 addListener()CoordListener 實例。清單 6 展示了 RandomWalkGenerator 循環的邏輯:


    清單 6. RandomWalkGenerator's run() 方法
                    
    public void run() {

    try {
    while (true) {
    int sleepMillis = 5000 + (int)(Math.random()*8000d);
    Thread.sleep(sleepMillis);
    dispatchUpdate(generateNextCoord());
    }
    } catch (Exception e) {
    throw new RuntimeException(e);
    }
    }

    CoordListener 是一個回調接口,僅僅定義 onCoord(GpsCoord coord) 方法。在本例中,ContinuationBasedTracker 類實現 CoordListenerContinuationBasedTracker 的另一個公有方法是 getNextPosition(Continuation, int)。清單 7 展示了這些方法的實現:


    清單 7. ContinuationBasedTracker 結構
                    
    public GpsCoord getNextPosition(Continuation continuation, int timeoutSecs) {

    synchronized(this) {
    if (!continuation.isPending()) {
    pendingContinuations.add(continuation);
    }

    // Wait for next update
    continuation.suspend(timeoutSecs*1000);
    }

    return (GpsCoord)continuation.getObject();
    }


    public void onCoord(GpsCoord gpsCoord) {

    synchronized(this) {
    for (Continuation continuation : pendingContinuations) {

    continuation.setObject(gpsCoord);
    continuation.resume();
    }

    pendingContinuations.clear();
    }
    }

    當客戶機使用 Continuation 調用 getNextPosition() 時,isPending 方法將檢查此時的請求是否是第二次執行,然后將它添加到等待坐標的 Continuation 集合中。然后該 Continuation 被暫停。同時,onCoord —— 生成新坐標時將被調用 —— 循環遍歷所有處于等待狀態的 Continuation,對它們設置 GPS 坐標,并重新使用它們。之后,每個再次執行的請求完成 getNextPosition() 執行,從 Continuation 檢索 GpsCoord 并將其返回給調用者。注意此處的同步需求,是為了保護 pendingContinuations 集合中的實例狀態不會改變,并確保新增的 Continuation 在暫停之前沒有被處理過。

    最后一個難點是 servlet 代碼本身,如 清單 8 所示:


    清單 8. GPSTrackerServlet 實現
                    
    public class GpsTrackerServlet extends HttpServlet {

    private static final int TIMEOUT_SECS = 60;
    private ContinuationBasedTracker tracker = new ContinuationBasedTracker();

    public void service(HttpServletRequest req, HttpServletResponse res)
    throws java.io.IOException {

    Continuation c = ContinuationSupport.getContinuation(req,null);
    GpsCoord position = tracker.getNextPosition(c, TIMEOUT_SECS);

    String json = new Jsonifier().toJson(position);
    res.getWriter().print(json);
    }
    }

    如您所見,servlet 只執行了很少的工作。它僅僅獲取了請求的 Continuation,調用 getNextPosition(),將 GPSCoord 轉換成 JavaScript Object Notation (JSON),然后輸出。這里不需要防止重新執行,因此我不必檢查 isPending()清單 9 展示了調用 GpsTrackerServlet 的輸出,同樣,有五個同步請求而服務器只有一個可用線程:


    Listing 9. Output of GPSTrackerServlet
                    
    $ for i in 'seq 1 5' ; do lynx -dump localhost:8080/tracker & done
    { coord : { lat : 51.51122, lng : -0.08103112 } }
    { coord : { lat : 51.51122, lng : -0.08103112 } }
    { coord : { lat : 51.51122, lng : -0.08103112 } }
    { coord : { lat : 51.51122, lng : -0.08103112 } }
    { coord : { lat : 51.51122, lng : -0.08103112 } }

    這個示例并不引人注意,但是提供了概念證明。發出請求后,它們將一直保持打開的連接直至生成坐標,此時將快速生成響應。這是 Comet 模式的基本原理,Jetty 使用這種原理在一個線程內處理 5 個并發請求,這都是 Continuations 的功勞。





    回頁首


    創建一個 Comet 客戶機

    現在您已經了解了如何使用 Continuations 在理論上創建非阻塞 Web 服務,您可能想知道如何創建客戶端代碼來使用這種功能。一個 Comet 客戶機需要完成以下功能:

    1. 保持打開 XMLHttpRequest 連接,直到收到響應。
    2. 將響應發送到合適的 JavaScript 處理程序。
    3. 立即建立新的連接。
    更高級的 Comet 設置將使用一個連接將數據從不同服務推入瀏覽器,并且客戶機和服務器配有相應的路由機制。一種可行的方法是根據一種 JavaScript 庫,例如 Dojo,編寫客戶端代碼,這將提供基于 Comet 的請求機制,其形式為 dojo.io.cometd。

    然而,如果服務器使用 Java 語言,使用 DWR 2 可以同時在客戶機和服務器上獲得 Comet 高級支持,這是一種不錯的方法(參閱 參考資料)。如果您并不了解 DWR 的話,請參閱本系列第 3 部分 “結合 Direct Web Remoting 使用 Ajax”。DWR 透明地提供了一種 HTTP-RPC 傳輸層,將您的 Java 對象公開給網絡中 JavaScript 代碼的調用。DWR 生成客戶端代理,將自動封送和解除封送數據,處理安全問題,提供方便的客戶端實用工具庫,并可以在所有主要瀏覽器上工作。





    回頁首


    DWR 2: Reverse Ajax

    DWR 2 最新引入了 Reverse Ajax 概念。這種機制可以將服務器端事件 “推入” 到客戶機??蛻舳?DWR 代碼透明地處理已建立的連接并解析響應,因此從開發人員的角度來看,事件是從服務器端 Java 代碼輕松地發布到客戶機中。

    DWR 經過配置之后可以使用 Reverse Ajax 的三種不同機制。第一種就是較為熟悉的輪詢方法。第二種稱為 piggyback, 這種機制并不創建任何到服務器的連接,相反,將一直等待直至發生另一個 DWR 服務,piggybacks 使事件等待該請求的響應。這使它具有較高的效率,但也意味著客戶機事件通知被延遲到直到發生另一個不相關的客戶機調用。最后一種機制使用長期的、 Comet 風格的連接。最妙的是,當運行在 Jetty 下時,DWR 能夠自動檢測并切換為使用 Contiuations,實現非阻塞 Comet。

    我將在 GPS 示例中結合使用 Reverse Ajax 和 DWR 2。通過這種演示,您將對 Reverse Ajax 的工作原理有更多的了解。

    此時不再需要使用 servlet。DWR 提供了一個控制器 servlet,它將在 Java 對象之上直接轉交客戶機請求。同樣也不需要顯式地處理 Continuations,因為 DWR 將在內部進行處理。因此我只需要一個新的 CoordListener 實現,將坐標更新發布到到任何客戶機瀏覽器上。

    ServerContext 接口提供了 DWR 的 Reverse Ajax 功能。ServerContext 可以察覺到當前查看給定頁面的所有 Web 客戶機,并提供一個 ScriptSession 進行相互通信。ScriptSession 用于從 Java 代碼將 JavaScript 片段推入到客戶機。清單 10 展示了 ReverseAjaxTracker 響應坐標通知的方式,并使用它們生成對客戶端 updateCoordinate() 函數的調用。注意對 DWR ScriptBuffer 對象調用 appendData() 將自動把 Java 對象封送給 JSON(如果使用合適的轉換器)。


    清單 10. ReverseAjaxTracker 中的通知回調方法
                    
    public void onCoord(GpsCoord gpsCoord) {

    // Generate JavaScript code to call client-side
    // function with coord data
    ScriptBuffer script = new ScriptBuffer();
    script.appendScript("updateCoordinate(")
    .appendData(gpsCoord)
    .appendScript(");");

    // Push script out to clients viewing the page
    Collection<ScriptSession> sessions =
    sctx.getScriptSessionsByPage(pageUrl);

    for (ScriptSession session : sessions) {
    session.addScript(script);
    }
    }

    接下來,必須對 DWR 進行配置以感知 ReverseAjaxTracker 的存在。在大型應用程序中,可以使用 DWR 的 Spring 集成提供 Spring 生成的 bean。但是,在本例中,我僅使用 DWR 創建了一個 ReverseAjaxTracker 新實例并將其放到 application 范圍中。所有后續請求將訪問這個實例。

    我還需告訴 DWR 如何將數據從 GpsCoord beans 封送到 JSON。由于 GpsCoord 是一個簡單對象,DWR 的基于反射的 BeanConverter 就可以完成此功能。清單 11 展示了 ReverseAjaxTracker 的配置:


    清單 11. ReverseAjaxTracker 的 DWR 配置
                    
    <dwr>
    <allow>
    <create creator="new" javascript="Tracker" scope="application">
    <param name="class" value="developerworks.jetty6.gpstracker.ReverseAjaxTracker"/>
    </create>

    <convert converter="bean" match="developerworks.jetty6.gpstracker.GpsCoord"/>
    </allow>
    </dwr>

    create 元素的 javascript 屬性指定了 DWR 用于將跟蹤器公開為 JavaScript 對象的名字,在本例中,我的客戶端代碼沒有使用該屬性,而是將數據從跟蹤器推入到其中。同樣 ,還需對 web.xml 進行額外的配置,以針對 Reverse Ajax 配置 DWR,如 清單 12 所示:


    清單 12. DwrServlet 的 web.xml 配置
                    
    <servlet>
    <servlet-name>dwr-invoker</servlet-name>
    <servlet-class>
    org.directwebremoting.servlet.DwrServlet
    </servlet-class>
    <init-param>
    <param-name>activeReverseAjaxEnabled</param-name>
    <param-value>true</param-value>
    </init-param>
    <init-param>
    <param-name>initApplicationScopeCreatorsAtStartup</param-name>
    <param-value>true</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    </servlet>

    第一個 servlet init-paramactiveReverseAjaxEnabled 將激活輪詢和 Comet 功能。第二個 initApplicationScopeCreatorsAtStartup 通知 DWR 在應用程序啟動時初始化 ReverseAjaxTracker。這將在對 bean 生成第一個請求時改寫延遲初始化(lazy initialization)的常規行為 —— 在本例中這是必須的,因為客戶機不會主動對 ReverseAjaxTracker 調用方法。

    最后,我需要實現調用自 DWR 的客戶端 JavaScript 函數。將向回調函數 —— updateCoordinate() —— 傳遞 GpsCoord Java bean 的 JSON 表示,由 DWR 的 BeanConverter 自動序列化。該函數將從坐標中提取 longitudelatitude 字段,并通過調用 Document Object Model (DOM) 將它們附加到列表中。清單 13 展示了這一過程,以及頁面的 onload 函數。onload 包含對 dwr.engine.setActiveReverseAjax(true) 的調用,將通知 DWR 打開與服務器的持久連接并等待回調。


    清單 13. 簡單 Reverse Ajax GPS 跟蹤器的客戶端實現
                    
    window.onload = function() {
    dwr.engine.setActiveReverseAjax(true);
    }

    function updateCoordinate(coord) {
    if (coord) {
    var li = document.createElement("li");
    li.appendChild(document.createTextNode(
    coord.longitude + ", " + coord.latitude)
    );
    document.getElementById("coords").appendChild(li);
    }
    }

    不使用 JavaScript 更新頁面
    如果希望最小化應用程序中使用的 JavaScript 代碼的數量,可以使用 ScriptSession 編寫 JavaScript 回調:將 ScriptSession 實例封裝在 DWR Util 對象中。該類將提供直接操作瀏覽器 DOM 的簡單 Java 方法,并在后臺自動生成所需的腳本。

    現在我可以將瀏覽器指向跟蹤器頁面,DWR 將在生成坐標數據時把數據推入客戶機。該實現輸出生成坐標的列表,如 圖 2 所示:


    圖 2. ReverseAjaxTracker 的輸出
    列出生成坐標的簡單 Web 頁面

    可以看到,使用 Reverse Ajax 創建事件驅動的 Ajax 應用程序非常簡單。請記住,正是由于 DWR 使用了 Jetty Continuations,當客戶機等待新事件到來時不會占用服務器上面的線程。

    此時,集成來自 Yahoo! 或 Google 的地圖部件非常簡單。通過更改客戶端回調,可輕松地將坐標傳送到地圖 API,而不是直接附加到頁面中。圖 3 展示了 DWR Reverse Ajax GPS 跟蹤器在此類地圖組件上標繪隨機路線:


    Figure 3. 具有地圖 UI 的 ReverseAjaxTracker
    地圖顯示路線跟蹤生成的坐標




    回頁首


    結束語

    通 過本文,您了解了如何結合使用 Jetty Continuations 和 Comet 為事件驅動 Ajax 應用程序提供高效的可擴展解決方案。我沒有給出 Continuations 可擴展性的具體數字,因為實際應用程序的性能取決于多種變化的因素。服務器硬件、所選擇的操作系統、JVM 實現、Jetty 配置以及應用程序的設計和通信量配置文件都會影響 Jetty Continuations 的性能。然而,Webtide 的 Greg Wilkins(主要的 Jetty 開發人員)曾經發布了一份關于 Jetty 6 的白皮書,對使用 Continuations 和沒有使用 Continuations 的 Comet 應用程序的性能進行了比較,該程序同時處理 10000 個并發請求(參閱 參考資料)。在 Greg 的測試中,使用 Continuations 能夠減少線程消耗,并同時減少了超過 10 倍的棧內存消耗。

    您 還看到了使用 DWR 的 Reverse Ajax 技術實現事件驅動 Ajax 應用程序是多么簡單。DWR 不僅省去了大量客戶端和服務器端編碼,而且 Reverse Ajax 還從代碼中將完整的服務器-推送機制抽象出來。通過更改 DWR 的配置,您可以自由地在 Comet、輪詢,甚至是 piggyback 方法之間進行切換。您可以對此進行實驗,并找到適合自己應用程序的最佳性能策略,同時不會影響到自己的代碼。

    如果希望對自己的 Reverse Ajax 應用程序進行實驗,下載并研究 DWR 演示程序的代碼(DWR 源代碼發行版的一部分,參閱 參考資源)將非常有幫助。如果希望親自運行示例,還可獲得本文使用的示例代碼(參見 下載)。






    回頁首


    下載

    描述名字大小下載方法
    示例代碼 jetty-dwr-comet-src.tgz 8KB HTTP
    關于下載方法的信息


    參考資料

    學習
    • 您可以參閱本文在 developerWorks 全球站點上的 英文原文

    • Comet: Low Latency Data for the Browser”:Alex Russell 是 Dojo Toolkit 的項目主管和 Dojo Foundation 的主席,他在這篇博客文章中提出了 Comet 這個術語。

    • Jetty:Jetty 是一種開源的基于標準的 Web 服務器,完全使用 Java 語言實現。

    • Ajax, Comet and Jetty”(Greg Wilkins,Webtide,2006 年 1 月):Wilkins 的這份白皮書討論了擴展 Ajax 連接的 Jetty 架構方法。

    • Continuations:了解更多關于 Jetty 的 Continuations 特性的信息。

    • Direct Web Remoting (DWR): DWR 是用于編寫 Ajax Web 應用程序的開源 Java 庫,使瀏覽器中的 JavaScript 能夠與服務器中的 Java 代碼進行交互。

    • Merlin 給 Java 平臺帶來了非阻塞 I/O”(Aruna Kalagnanam 和 Balu G, developerWorks,2002 年 3 月):閱讀這個 Java 非阻塞 I/O 包,由 JDK 1.4 引入。

    • developerWorks Java 技術專區:提供了關于 Java 編程各個方面的數百篇文章。

    • 瀏覽 技術書店,查閱有關本文所述主題以及其他技術主題的書籍。


    獲得產品和技術
    • Jetty:下載 Jetty。

    • DWR:下載 DWR。

    討論
    查看 developerWorks blogs,加入 developerWorks 社區
    posted on 2009-05-05 14:49 小菜毛毛 閱讀(1130) 評論(0)  編輯  收藏 所屬分類: AJAX
    主站蜘蛛池模板: 在线播放国产不卡免费视频| 亚洲国产精品成人一区| 亚洲精品二三区伊人久久| 日韩免费视频一区| 中文字幕在线成人免费看| 亚洲国产理论片在线播放| 日韩一级视频免费观看| APP在线免费观看视频| 中文文字幕文字幕亚洲色| 国产成人精品曰本亚洲79ren| 免费国产在线视频| WWW国产亚洲精品久久麻豆| 久久亚洲一区二区| 四虎永久免费地址在线网站| 99久久久国产精品免费蜜臀| 香蕉国产在线观看免费| 亚洲一区二区影视| 亚洲国产精品无码久久久不卡| 成人性生活免费视频| 免费av一区二区三区| 一二三四在线观看免费中文在线观看| 麻豆亚洲AV永久无码精品久久| 麻豆亚洲AV永久无码精品久久| 亚洲福利电影一区二区?| 成人永久福利免费观看| 国产精品免费高清在线观看| 在线观看亚洲电影| 亚洲人成www在线播放| 亚洲av午夜福利精品一区| 免费在线观看黄色毛片| 久久久久久国产精品免费免费| 91视频免费观看高清观看完整| 色窝窝亚洲AV网在线观看| 亚洲午夜精品在线| 亚洲av鲁丝一区二区三区| 美腿丝袜亚洲综合| 亚洲国产精品一区二区九九| 日韩人妻无码免费视频一区二区三区| 91高清免费国产自产拍2021| 男女一边桶一边摸一边脱视频免费 | 一边摸一边桶一边脱免费视频 |