標籤:http io ar 使用 sp java 檔案 資料 on
1 背景
“伺服器推送技術”(ServerPushing)是最近Web技術中最熱門的一個流行術語。它是繼“Ajax”之後又一個倍受追捧的Web技術。“伺服器推送技術”最近的流行跟“Ajax ”有著密切的關係。
隨著 Ajax技術的興起,讓廣大開發人員又一次看到了使用瀏覽器來替代案頭應用的機會,並且這次機會非常大。Ajax將整個頁面的重新整理變成頁面局部的重新整理,並且資料的傳送是以非同步方式進行,這使得網路延遲帶來的視覺差異將會消失。
但是,在瀏覽器中的 Ajax應用中存在一個致命的缺陷無法滿足傳統案頭系統的需求。那就是“伺服器發起的訊息傳遞”(Server-Initiated Message Delivery)。在很多的應用當中,伺服器軟體需要向用戶端主動發送訊息或資訊。因為伺服器掌握著系統的主要資源,能夠最先獲得系統的狀態變化和事件的發生。當這些變化發生的時候,伺服器需要主動的向用戶端即時的發送訊息。例如股票的變化。在傳統的案頭系統這種需求沒有任何問題,因為用戶端和伺服器之間通常存在著持久的串連,這個串連可以雙向傳遞各種資料。而基於HTTP協議的 Web應用卻不行。
2 用戶端得到通知的方式
圖1 傳統web 訪問機制
我們知道, Web的訪問機制天生是設計用來 pull資料的, 1,也就是只允許 Browser端主動發起請求,server是被動的響應,不允許Server向 Browser發出一個 connection請求,也就是說沒有為server向 Browser push資料提供設計實現.雖然沒有直接的實現方法,卻可以使用一些變通的方式完成類似的功能。
2.1 傳統輪詢
在 Web早期,這一點常使用meta重新整理實現。這將自動指示瀏覽器在指定秒數之後重新裝載頁面,從而支援簡陋的輪詢(polling)。例如在HTML檔案中加入 <META HTTP-RQUIV="Refresh" CONTENT=12>,實際上就是 HTTP 頭標告知瀏覽器每 12 秒更新一次文檔。
優點 :不需要伺服器端的配置。
缺點 :
a) 糟糕的使用者體驗
b) 對伺服器的壓力很大,並且造成頻寬的極大浪費。
2.2 Ajax 輪詢
Ajax隔一段時間(通常使用JavaScript的setTimeout函數)就去伺服器查詢是否有改變,從而進行增量式的更新。但是間隔多長時間去查詢成了問題,因為效能和即時性造成了嚴重的反比關係。間隔太短,連續不斷的請求會衝垮伺服器,間隔太長,務器上的新資料就需要越多的時間才能到達客戶機。
優點:
a) 不需要太多伺服器端的配置。
b) 降低頻寬的負荷(因為伺服器返回的不是完整頁面)。
缺點:
a) 對伺服器的壓力並不會有明顯的減少。
b) 即時性差,有一定的延遲。
應用: 這是一項非常常見的技術,例如,大多數 webmail應用程式就是通過這種技術在電子郵件到達時顯示電子郵件的。
2.3 Comet
Comet方式通俗的說就是一種長串連機制(long lived http)。同樣是由Browser端主動發起請求,但是Server端以一種似乎非常慢的回應程式式給出回答。這樣在這個期間內,伺服器端可以使用同一個connection把要更新的資料主動發送給Browser。因此請求可能等待較長的時間,期間沒有任何資料返回,但是一旦有了新的資料,它將立即被發送到客戶機。Comet又有很多種實現方式,但是總的來說對Server端的負載都會有增加.雖然對於單位操作來說,每次只需要建議一次connection,但是由於connection是保持較長時間的,對於 server端的資源的佔用要有所增加。
優點: 即時性好(訊息延時小);效能好(能支援大量使用者)
缺點: 長期佔用串連,喪失了無狀態高並發的特點。
應用: 股票系統、即時通訊。
2.4 Flash XML Socket
這種方案實現的基礎是:一、Flash提供了 XMLSocket類。二、 JavaScript 和 Flash的緊密結合:在 JavaScript可以直接調用 Flash程式提供的介面。
缺點:
a) 因為XMLSocket沒有HTTP隧道功能,XMLSocket類不能自動穿過防火牆;
b) 因為是使用套介面,需要設定一個通訊連接埠,防火牆、Proxy 伺服器也可能對非HTTP通道連接埠進行限制;
應用: 網路聊天室,網路互動遊戲。
2.5 Java Applet 套介面
在用戶端使用 Java Applet,通過 java.net.Socket或java.net.DatagramSocket或java.net.MulticastSocket 建立與伺服器端的套介面串連,從而實現“伺服器推送 ”。
缺點: 需要用戶端安裝 JAVA虛擬機器。
3 Comet 介紹
Comet 有時也稱反向 Ajax或伺服器端推技術(server-side push)。其思想很簡單:將資料直接從伺服器推到瀏覽器,而不必等到瀏覽器請求資料。聽起來簡單,但是如果熟悉Web 應用程式,尤其是HTTP協議,那麼您就會知道,這絕不簡單。實現Comet風格的 Web應用程式,同時保證在瀏覽器和伺服器上的延展性,這隻是在最近幾年才成為可能。目前一些主流網站都有類似的原理,例如:webQQ、開心網、校內等等,它們中訊息動態都是採用類似的技術,只是具體實現方式不一樣。
COMET的精髓就在於用伺服器與javascript來維持瀏覽器的長串連,同時完成伺服器端事件的瀏覽器端響應。這樣的事件廣播機制是跨網路的,同時也是即時的。
採用了 Comet技術的伺服器在客戶機做出一個請求後,和客戶機建立一個永久的串連,然後伺服器會根據客戶機的請求不斷把資料包推向客戶,這個推的過程是不間斷的。由伺服器推向客戶機的資料在客戶機的瀏覽器上會不斷產生新的內容,而且不會產生Client pull那樣的HTML文檔頭,從而大大減少了延遲的時間,向(伺服器響應--客戶機請求)同步邁進了一步。
伺服器推送通常效率要比用戶端拖曳效率高,因為它不必為後續資料建立新的串連。由於始終保持串連,即使沒有資料轉送時也是這樣,因此伺服器必須願意分配這些TCP/IP連接埠,對於TCP/IP連接埠數有限的伺服器這將是一個嚴重的問題。
用戶端拖曳效率低,因為這必須每次為傳送資料建立新的串連。但是它不必始終保持串連。在實際情況中,建立HTTP串連通常需要花費相當多的時間,多達一秒甚至更多。因此從效能上考慮,伺服器推送對於終端使用者更有吸引力,特別是對於需要經常更新資訊的情況下。
伺服器推送相對用戶端拖曳的另一點優勢是,伺服器推送相對比較容易控制。例如,伺服器每一次推送時都保持一個串連,但它又隨時可以關閉其中的任何串連,而不需要在伺服器上設定特殊的演算法。而用戶端拖曳在同樣的情況下要麻煩許多,它每次要與伺服器建立串連,伺服器為了處理將用戶端拖曳請求與特定的終端使用者匹配等情況,需要使用相當麻煩的演算法。
如上所述,在伺服器推送中,多個響應中串連始終保持,使伺服器可在任何時間發送更多的資料。一個明顯的好處是伺服器完全能夠控制更新資料的時間和頻率。另外,這種方法效率高,因為始終保持串連。缺點是保持串連狀態會浪費伺服器端的資源。伺服器推送還比較容易中斷。
4 Comet 實現(Java語言)
4.1 死迴圈法
最簡單的自然是死迴圈法,如果使用觀察者模式則可以進一步提高效能。
但是這種做法的缺點在於用戶端請求了這個 servlet後, 網頁伺服器會開啟一個線程執行 servlet 的代碼,而 servlet 由遲遲不肯結束,造成該線程也無法被釋放。於是乎,一個用戶端一個線程,當用戶端數量增加時,伺服器依然會承受很大的負擔。
4.2 改寫web伺服器
目前的趨勢是從 網頁伺服器內部入手,用 nio ( JDK 1.4 提出的 java.nio 包)改寫 request/response 的實現,再利用線程池增強伺服器的資源使用率,從而解決這個問題,目前支援這一非 J2EE 官方技術的伺服器有 Glassfish 和 Jetty 。
JDK 1.4 版本 ( 包括之後的版本 ) 最顯著的新特性就是增加了 NIO(New IO) ,能夠以非阻塞的方式處理網路的請求,這就使得在 Java 中只需要少量的線程就能處理大量的並發請求了。
Jetty 6設計來處理大量並發串連,它使用Java語言的不堵塞 I/O(java.nio)庫並且使用最佳化的輸出緩衝架構。Jetty也有一個處理長串連的殺手鐧:一個稱為 Continuations的特性。
Grizzly 作為 GlassFish 中非常重要的一個項目,就是用 NIO 的技術來實現應用伺服器中的高效能純 Java 的 HTTP 引擎。 Grizzly 還是一個獨立於 GlassFish 的架構結構,可以單獨用來擴充和構建自己的伺服器軟體。
特點: 使用 NIO 不是一件簡單的技術,它的一些特點使得編程的模型比原來阻塞的方式更為複雜。
4.3 使用架構
基於 Java 的成熟的伺服器推送架構有 DWR 。
DWR是一個開放源碼的使用Apache許可協議的解決方案,它包含伺服器端Java庫、一個 DWR servlet以及 JavaScript庫。雖然 DWR不是 Java平台上唯一可用的Ajax-RPC 工具包,但是它是最成熟的,而且提供了許多有用的功能。從最簡單的角度來說,DWR是一個引擎,可以把伺服器端Java對象的方法公開給JavaScript 代碼。使用DWR 可以有效地從應用程式代碼中把Ajax的全部請求 -響應迴圈消除掉。這意味著用戶端代碼再也不需要直接處理XMLHttpRequest對象或者伺服器的響應。不再需要編寫對象的序列化代碼或者使用第三方工具才能把對象變成XML。甚至不再需要編寫servlet代碼把 Ajax請求調整成對 Java域對象的調用。
DWR 從 2.0開始增加了 push 功能 , 也就是在非同步傳輸的情況下可以從 Web-Server 端發送資料到 Browser
一個簡單的dwr推送程式
第一步 將dwr相關的jar包匯入到工程
第二步 配置web.xml檔案
[html] view plaincopy
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>dwr-invoker</servlet-name>
<servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
<!-- 設定是否允許使用dwr推送技術 -->
<init-param>
<param-name>activeReverseAjaxEnabled</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>maxWaitAfterWrite</param-name>
<param-value>-1</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dwr-invoker</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>
</web-app>
第三步編寫com.im.service.SendPushService類
[java] view plaincopy
public class SendPushService {
//發送訊息
public void send(String msg) {
System.out.println("==========調用了send方法==========");
ScriptBuffer scriptBuffer = new ScriptBuffer(); //構造js指令碼
WebContext webContext=WebContextFactory.get();
ScriptSession myScSession = webContext.getScriptSession();
scriptBuffer.appendScript("dwrtest(");
scriptBuffer.appendData(msg);
scriptBuffer.appendScript(")");
Util util = new Util(myScSession);
util.addScript(scriptBuffer); //向用戶端推送訊息
}
}
第四步 在dwr.xml檔案定義向外暴露的介面
[html] view plaincopy
<allow>
<create creator="new" javascript="SendPushService">
<param name="class" value="com.im.service.SendPushService"/>
</create>
</allow>
第五步: 編寫jsp檔案.
[html] view plaincopy
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns=" http://www.w3.org/1999/xhtml">
<head>
<base href="${basePath }" />
<script type=‘text/javascript‘ src=‘${basePath}dwr/engine.js‘></script>
<script type=‘text/javascript‘ src=‘${basePath}dwr/util.js‘></script>
<script type=‘text/javascript‘ src=‘${basePath}dwr/interface/SendPushService.js‘></script>
<script type="text/javascript">
function hello(){
SendPushService.send("第一個dwr推程式");
}
/**由dwr在後台調用這個方法**/
function dwrtest(data){
alert(data);
}
</script>
<title>第一個dwr推程式</title>
</head>
<body onload="dwr.engine.setActiveReverseAjax(true);">
<input type="button" value="點擊我" onclick="hello();" />
</body>
</html>
Web端伺服器推送技術原理分析及dwr架構簡單的使用