日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

服務(wù)器推送技術(shù) java

系統(tǒng) 1960 0

服務(wù)器推送技術(shù) java

文章分類(lèi): Java編程

下面介紹在ARP之上的一個(gè)非常熱門(mén)的技術(shù)實(shí)現(xiàn):服務(wù)器推送技術(shù)。

服務(wù)器推送技術(shù)(Server Push)是最近Web技術(shù)中最熱門(mén)的一個(gè)流行術(shù)語(yǔ),它的別名叫Comet(彗星)。它是繼AJAX之后又一個(gè)倍受追捧的Web技術(shù)。服務(wù)器推送技術(shù)最近的流行與AJAX有著密切的關(guān)系。

隨著Web技術(shù)的流行,越來(lái)越多的應(yīng)用從原有的C/S模式轉(zhuǎn)變?yōu)锽/S模式,享受著Web技術(shù)所帶來(lái)的各種優(yōu)勢(shì)(例如跨平臺(tái)、免客戶(hù)端維護(hù)、跨越防火墻、擴(kuò)展性好等)。但是基于瀏覽器的應(yīng)用,也有它不足的地方。主要在于界面的友好性和交互性。由于瀏覽器中的頁(yè)面每次需要全部刷新才能從服務(wù)器端獲得最新的數(shù)據(jù)或向服務(wù)器傳送數(shù)據(jù),這樣產(chǎn)生的延遲所帶來(lái)的視覺(jué)感受非常糟糕。因此很多的桌面應(yīng)用為了獲得更友好的界面放棄了Web技術(shù),或者采用瀏覽器的插件技術(shù)(ActiveX、Applet、Flash等)。但是瀏覽器插件技術(shù)本身又有許多問(wèn)題,例如跨平臺(tái)問(wèn)題和插件版本兼容性問(wèn)題。

隨著AJAX技術(shù)的興起,讓廣大開(kāi)發(fā)人員又一次看到了使用瀏覽器來(lái)替代桌面應(yīng)用的機(jī)會(huì),并且這次機(jī)會(huì)非常大。AJAX將整個(gè)頁(yè)面的刷新變成頁(yè)面局部的刷新,并且數(shù)據(jù)的傳送是以異步方式進(jìn)行,這使得網(wǎng)絡(luò)延遲帶來(lái)的視覺(jué)差異將會(huì)消失。AJAX還利用DHTML和豐富的JavasSript語(yǔ)言來(lái)模擬桌面系統(tǒng)的各種事件和響應(yīng)過(guò)程,以及平滑滾動(dòng)和拖拽的效果。還不止這些,更有一些IT巨頭(Google、Sun、Oracle等)提供了非常豐富的AJAX開(kāi)發(fā)工具,使得開(kāi)發(fā)和調(diào)試AJAX應(yīng)用變得簡(jiǎn)單高效,并且開(kāi)發(fā)的AJAX應(yīng)用還可以跨越各種瀏覽器和操作系統(tǒng)。在這種情況下基于AJAX的Web應(yīng)用迅速涌起,吞噬著原有桌面系統(tǒng)的份額。聊天工具、郵件閱讀器、博客編輯器,甚至是Office辦公軟件和文字處理軟件在瀏覽器中都有著美麗的外觀(guān)和幾乎可以與桌面系統(tǒng)媲美的交互界面。Google更是提出“有了瀏覽器和Google,就不需要微軟”的口號(hào)和策略。在AJAX的世界中,除了傳統(tǒng)的CAD設(shè)計(jì)軟件和大型游戲軟件等因?yàn)閷?duì)系統(tǒng)硬件的苛刻需求,還離不開(kāi)桌面系統(tǒng)以外,似乎其他所有的應(yīng)用都可以變成Web應(yīng)用了。

但是,在瀏覽器中的AJAX應(yīng)用中存在一個(gè)致命的缺陷無(wú)法滿(mǎn)足傳統(tǒng)桌面系統(tǒng)的需求。那就是“服務(wù)器發(fā)起的消息傳遞(Server-Initiated Message Delivery)”。在很多的應(yīng)用當(dāng)中,服務(wù)器軟件需要向客戶(hù)端主動(dòng)發(fā)送消息或信息。因?yàn)榉?wù)器掌握著系統(tǒng)的主要資源,能夠最先獲得系統(tǒng)的狀態(tài)變化和事件的發(fā)生。當(dāng)這些變化發(fā)生的時(shí)候,服務(wù)器需要主動(dòng)地向客戶(hù)端實(shí)時(shí)地發(fā)送消息。例如股票的變化。在傳統(tǒng)的桌面系統(tǒng)中,這種需求沒(méi)有任何問(wèn)題,因?yàn)榭蛻?hù)端和服務(wù)器之間通常存在著持久的連接,這個(gè)連接可以雙向傳遞各種數(shù)據(jù)。而基于HTTP協(xié)議的Web應(yīng)用卻不行。上節(jié)中也提到過(guò),在Web世界中,服務(wù)器永遠(yuǎn)是被動(dòng)地發(fā)送數(shù)據(jù),前提是客戶(hù)端必須先發(fā)送請(qǐng)求。瀏覽器其實(shí)并不知道服務(wù)器的信息什么時(shí)候會(huì)有改變,為了模擬實(shí)時(shí)的交流,或者不想錯(cuò)過(guò)某些信息,只能通過(guò)輪詢(xún)(Polling)技術(shù)不斷刷新頁(yè)面來(lái)獲得最新的數(shù)據(jù)(見(jiàn)圖18-5)。這種方式不但浪費(fèi)服務(wù)器的資源,最重要的是每次建立(或關(guān)閉)新的HTTP連接都有一定的延遲,這種延遲使得頻繁信息傳遞的應(yīng)用無(wú)法忍受。于是就產(chǎn)生了“服務(wù)器推送技術(shù)”。

圖18-5 Web請(qǐng)求的輪詢(xún)技術(shù)

“服務(wù)器推送技術(shù)”在很久以前就出現(xiàn)過(guò)。例如Netscape曾經(jīng)推出適用于Push技術(shù)的專(zhuān)用瀏覽器和經(jīng)過(guò)修改的HTML語(yǔ)言。但是這僅僅在特定的瀏覽器中才能使用,其他流行的瀏覽器(IE等)就不兼容這種技術(shù)。

現(xiàn)在的“服務(wù)器推送技術(shù)”是保持原有的HTTP協(xié)議不變,在服務(wù)器端改變處理方式,使得服務(wù)器能夠使用瀏覽器已經(jīng)打開(kāi)的HTTP連接,主動(dòng)向?yàn)g覽器發(fā)送消息(見(jiàn)圖18-6)。這里關(guān)鍵的技術(shù)是要保持原有的HTTP連接不斷。一旦擁有持久的連接,服務(wù)器就可以根據(jù)自己的數(shù)據(jù)更新,隨時(shí)地向客戶(hù)端發(fā)送最新的信息。

圖18-6 服務(wù)器數(shù)據(jù)推送技術(shù)

在GlassFish中,Grizzly通過(guò)NIO的技術(shù)實(shí)現(xiàn)了異步請(qǐng)求服務(wù)(ARP),并在ARP之上擴(kuò)展了服務(wù)器推送技術(shù)的實(shí)現(xiàn),將其也命名為“Comet”。因?yàn)槭褂昧薔IO,Grizzly才可以在保持HTTP連接的同時(shí),并不會(huì)綁定固定的線(xiàn)程,使得GlassFish具有很好的擴(kuò)展性,可以很好地同時(shí)支持大量的Comet請(qǐng)求。下面我們來(lái)分析Grizzly中對(duì)Comet的實(shí)現(xiàn)。

18.2.1 Comet實(shí)現(xiàn)的分析

如圖18-7所示,Comet的實(shí)現(xiàn)是基于ARP之上的,因此整個(gè)框架結(jié)構(gòu)仍然符合ARP的模式。讀者可以與“新郵件提醒功能”做一個(gè)比較,大部分的代碼都相類(lèi)似。最大的不同就是“新郵件提醒功能”是Grizzly的一個(gè)擴(kuò)展,而Comet卻已經(jīng)是Grizzly的一部分,它與其他Grizzly的核心Java包位于同樣重要的位置。所有的Comet的實(shí)現(xiàn)都在com.sun. enterprise.web.connector.grizzly.comet包中。

因?yàn)槭茿RP的擴(kuò)展,所以它的入口仍然是AsyncFilter接口的實(shí)現(xiàn)。Comet對(duì)AsyncFilter接口的實(shí)現(xiàn)是CometAsyncFilter類(lèi)。這個(gè)類(lèi)的注冊(cè)比“新郵件提醒功能”要簡(jiǎn)單,只需要在GlassFish的啟動(dòng)配置文件(domain.xml)中加上<property name="cometSupport" value="true"/>就行了,在SelectorThreadConfig類(lèi)中就會(huì)讀取到(見(jiàn)例18.12),并且調(diào)用SelectorThread中的enableCometSupport方法(見(jiàn)例18.13)將CometAsyncFilter類(lèi)注冊(cè)到系統(tǒng)。

圖18-7 Comet實(shí)現(xiàn)類(lèi)結(jié)構(gòu)圖

【例18.12】在SelectorThreadConfig類(lèi)中打開(kāi)Comet功能:

if (System.getProperty(ENABLE_COMET_SUPPORT) != null){

?? selectorThread.enableCometSupport(

?? Boolean.valueOf(System.getProperty(ENABLE_COMET_SUPPORT)).booleanValue());

}

【例18.13】SelectorThread中的enableCometSupport方法:

protected void enableCometSupport(boolean enableComet){

??? if ( enableComet ){

??????? asyncExecution = true;

??????? setBufferResponse(false);???

??????? isFileCacheEnabled = false;

??????? isLargeFileCacheEnabled = false;

??????? asyncHandler = new DefaultAsyncHandler();

??????? asyncHandler.addAsyncFilter(new CometAsyncFilter());

??????? SelectorThread.logger()

.log(Level.INFO,"Enabling Grizzly ARP Comet support.");

??? } else {

??????? asyncExecution = false;

??? }

}

在CometAsyncFilter類(lèi)中,最重要的方法就是doFilter,它是Comet與異步請(qǐng)求處理(ARP)框架的接口。

【例18.14】CometAsyncFilter中的doFilter方法:

public boolean doFilter(AsyncExecutor asyncExecutor) {

AsyncProcessorTask apt =

(AsyncProcessorTask) asyncExecutor.getAsyncTask();

??? CometEngine cometEngine = CometEngine.getEngine();???????????????

??? try{

??????? if (!cometEngine.handle(apt)) {

??????????? return true;

??????? }

??? } catch (IOException ex){

??????? logger.log(Level.SEVERE,"CometAsyncFilter",ex);

??? }

?? return false;

}

從例18.14可以看出,在doFilter方法之中,所有的操作都交給CometEngine的handle方法。

CometEngine是Comet應(yīng)用中最先接觸的類(lèi)。如果一個(gè)Servlet或JSP頁(yè)面要想成為Comet請(qǐng)求,那么在編程的時(shí)候需要經(jīng)過(guò)以下幾步。

(1)?? 獲得CometEngine的實(shí)例對(duì)象,并將需要成為Comet請(qǐng)求的路徑注冊(cè):

CometEngine cometEngine = CometEngine.getEngine();
CometContext cometContext = cometEngine.register(contextPath);

(2)?? 注冊(cè)一個(gè)CometHandler:

cometContext.addCometHandler(handler);

(3)?? 最后,如果有消息發(fā)送,可以通過(guò)下面的方法通知所有注冊(cè)的通道:

cometContext.notify(handler);

有關(guān)CometContext和CometHandler類(lèi),在下面的內(nèi)容會(huì)進(jìn)行稍微詳細(xì)的描述。

當(dāng)請(qǐng)求處理交給CometEngine對(duì)象以后,CometEngine以及其他幾個(gè)類(lèi)(CometContext和CometHandler等)就會(huì)對(duì)這個(gè)請(qǐng)求的生命周期負(fù)起全部的責(zé)任。Comet請(qǐng)求和其他的請(qǐng)求不一樣,它需要長(zhǎng)時(shí)間地保持HTTP連接,來(lái)保證服務(wù)器端能夠利用這些連接主動(dòng)發(fā)送消息給瀏覽器客戶(hù)端。因此CometEngine并沒(méi)有使用主線(xiàn)程的Selector(在SelectorThread中運(yùn)行的Selector,而是使用了自己的Selector對(duì)象:CometSelector,而讓主線(xiàn)程的Selector負(fù)責(zé)其他類(lèi)型的請(qǐng)求讀取和處理。CometSelector的主要職責(zé)是負(fù)責(zé)已經(jīng)注冊(cè)的Comet請(qǐng)求的生命周期:哪些Comet請(qǐng)求的連接被用戶(hù)關(guān)閉或異常關(guān)閉,哪些Comet請(qǐng)求根據(jù)配置已經(jīng)超時(shí)。在這些情況下,需要系統(tǒng)釋放相應(yīng)的資源,使得系統(tǒng)更加穩(wěn)定和健壯。

而CometContext的作用則是應(yīng)用程序和Comet實(shí)現(xiàn)之間的橋梁。CometHandler可以利用它來(lái)注冊(cè),因此CometContext掌握了當(dāng)前Comet應(yīng)用中所有注冊(cè)了的頻道。這樣當(dāng)其中有一個(gè)頻道利用CometContext來(lái)發(fā)送消息時(shí),CometContext能夠?qū)⑾⒅鲃?dòng)發(fā)送給所有注冊(cè)的Handler。這些對(duì)象的關(guān)系,可以通過(guò)一個(gè)典型的例子的講解更加清楚的展現(xiàn)出來(lái)。

18.2.2 Comet實(shí)例講解——“聊天室”應(yīng)用

“聊天室”是一個(gè)非常典型的Comet應(yīng)用。通常的“聊天室”至少需要包含兩個(gè)基本的功能:發(fā)送本人的消息和接受顯示別人的消息。這里的Comet應(yīng)用主要是指接受別人的消息。因?yàn)閯e人什么時(shí)候發(fā)送了消息瀏覽器是不會(huì)知道的,只有聊天服務(wù)器本身知道,如果想要將各種消息實(shí)時(shí)地通知各個(gè)客戶(hù)端,就需要服務(wù)器推送技術(shù)。

現(xiàn)有的很多“聊天室”大多使用輪詢(xún)(Polling)技術(shù),來(lái)使得瀏覽器不斷自動(dòng)刷新以獲得最新的消息。這種實(shí)現(xiàn)方法在并發(fā)用戶(hù)不太多的情況下還能接受。如果并發(fā)用戶(hù)非常多,服務(wù)器的負(fù)擔(dān)就會(huì)大大地增加。另外每次重新建立連接所帶來(lái)的延遲也使得用戶(hù)不能非常及時(shí)地獲得最新的消息。綜合這些因此,對(duì)“聊天室”的最佳實(shí)現(xiàn)應(yīng)該使用Comet技術(shù),也就是“服務(wù)器推送技術(shù)”。

下面來(lái)講解一個(gè)使用GlassFish的Comet來(lái)實(shí)現(xiàn)的“聊天室”。在本書(shū)所附的CD中有詳細(xì)的代碼和步驟來(lái)部署和運(yùn)行“聊天室”應(yīng)用。

在“聊天室”中,只有一個(gè)Servlet和幾個(gè)JSP頁(yè)面文件。JSP頁(yè)面非常簡(jiǎn)單,只是簡(jiǎn)單的HTML。所有的請(qǐng)求處理都在Servlet中。

【例18.15】Servlet中的init方法:

...

public void init(ServletConfig config) throws ServletException {

??? super.init(config);

??? contextPath = config.getServletContext().getContextPath() + "/chat";

??? CometEngine cometEngine = CometEngine.getEngine();???????????? ?? // [1]

??? CometContext context = cometEngine.register(contextPath);???? ?? // [2]

??? context.setExpirationDelay(20*1000);?????? // [3]
}
...

從例18.15的代碼可以看出,Servlet在初始化的時(shí)候做了以下三件事情。

(1)?? 獲得了一個(gè)CometEngine的實(shí)例對(duì)象。上文已經(jīng)解釋過(guò),CometEngine對(duì)象是Comet應(yīng)用的入口。任何Comet應(yīng)用都需要CometEngine對(duì)象來(lái)注冊(cè)Comet請(qǐng)求的路徑。

(2)?? 將當(dāng)前的路徑向CometEngine進(jìn)行注冊(cè)。顯然,當(dāng)Comet功能打開(kāi)的時(shí)候,GlassFish不會(huì)將所有的請(qǐng)求都認(rèn)為是Comet請(qǐng)求,而是僅僅當(dāng)請(qǐng)求的路徑和將注冊(cè)的路徑相匹配的時(shí)候才會(huì)進(jìn)行Comet處理。注冊(cè)成功的結(jié)果是返回一個(gè)CometContext對(duì)象。上文已經(jīng)解釋過(guò),CometContext是每個(gè)用戶(hù)之間交流的橋梁。

(3)?? 設(shè)置當(dāng)前Comet應(yīng)用的超時(shí)的閥值。

【例18.16】Servlet的doPost方法中的部分代碼(一):

...

public void doPost(HttpServletRequest request,

??????????? HttpServletResponse response)

??????????? throws ServletException, IOException

{

??? String action = request.getParameter("action");

??? CometEngine cometEngine = CometEngine.getEngine();

??? CometContext cometContext = cometEngine.getCometContext(contextPath);

...

}

從例18.16的代碼中可以看出,在處理Comet請(qǐng)求,與其他用戶(hù)交互的時(shí)候,是需要先獲得CometContext的。需要指出的是,CometEngine對(duì)象是一個(gè)單例對(duì)象(Singleton),只會(huì)存在一個(gè)實(shí)例,因此任何時(shí)候調(diào)用getEngine的方法都會(huì)獲得同一個(gè)實(shí)例。

【例18.17】Servlet的doPost方法中的部分代碼(二):

...

if (action != null) {

if ("login".equals(action)) {

??? String username = request.getParameter("username");

??? request.getSession(true).setAttribute("username", username);

??? if (firstServlet != -1){

??????? cometContext.notify("User " + username

??????????? + " from " + request.getRemoteAddr()

??????????? + " is joining the chat.<br/>",CometEvent.NOTIFY,firstServlet);

??? }

...

從例18.17的代碼可以看出,當(dāng)用戶(hù)登錄成功后,除了將用戶(hù)信息保存到session中之外,還會(huì)通過(guò)cometContext向所有其他用戶(hù)發(fā)出“新用戶(hù)登錄”的信息。

【例18.18】Servlet的doPost方法中的部分代碼(三):

...

else if ("post".equals(action)){

String username = (String) request.getSession(true)

??????????????? .getAttribute("username");

String message = request.getParameter("message");

cometContext.notify("[ " + username + " ] " + message + "<br/>");

response.sendRedirect("post.jsp");

return;

...

例18.18的代碼是在處理用戶(hù)“說(shuō)話(huà)”的情況。如果用戶(hù)在自己的發(fā)送消息框中向其他在線(xiàn)的用戶(hù)發(fā)送了一些消息,Servlet在處理的時(shí)候就是通過(guò)cometContext來(lái)通知所有的在線(xiàn)用戶(hù)。

【例18.19】Servlet的doPost方法中的部分代碼(四):

...
else if ("openchat".equals(action)) {

??? response.setContentType("text/html");

??? String username = (String) request.getSession(true)

??????????? ??????????????????????? .getAttribute("username");

??? response.getWriter().println("<h2>Welcome "+ username + " </h2>");

??? CometRequestHandler handler = new CometRequestHandler();

??? handler.clientIP = request.getRemoteAddr();

??? handler.attach(response.getWriter());

??? cometContext.addCometHandler(handler);??????

??? return;

...

例18.19的代碼演示的是“聊天消息顯示”的功能,這才是真正Comet的請(qǐng)求,這個(gè)請(qǐng)求的連接是一直保持打開(kāi)著的,等待著服務(wù)器主動(dòng)將最新的信息發(fā)送到瀏覽器。這段代碼中最主要的內(nèi)容就是向CometContext注冊(cè)了一個(gè)CometHandler。注冊(cè)之后,這個(gè)Handler就會(huì)等待服務(wù)器端的回調(diào),來(lái)完成向?yàn)g覽器輸出的功能。

【例18.20】CometRequestHandler類(lèi)的onEvent方法:

public class CometRequestHandler implements CometHandler<PrintWriter>{

public void onEvent(CometEvent event) throws IOException{??

??? ???? try{

??????? ???? if (firstServlet != -1 && this.hashCode() != firstServlet){

??????????? ???? event.getCometContext().notify("User " + clientIP

??????????? ???? + " is getting a new message.<br/>",CometEvent.NOTIFY,

??????????? ???? firstServlet);

??????? ???? }

??????? ???? if (event.getType() != CometEvent.READ){

??????????? ???? printWriter.println(event.attachment());

??????????? ???? printWriter.flush();

????? ?????? }

??? ???? } catch (Throwable t){

??????? ???? t.printStackTrace();

??? ???? }

}

...

}?? ???????

例18.19的代碼解釋了CometRequestHandler類(lèi)在收到了系統(tǒng)的函數(shù)回調(diào)之后,進(jìn)入到onEvent方法。在onEvent方法的處理中,僅僅是簡(jiǎn)單地將系統(tǒng)傳遞過(guò)來(lái)的消息通過(guò)一直保持的HTTP連接向客戶(hù)傳過(guò)去。

當(dāng)“聊天室”應(yīng)用運(yùn)行的時(shí)候,用戶(hù)界面如圖18-8所示。其中下半部分是發(fā)送消息的部分,它的處理代碼對(duì)應(yīng)于例18-18。上半部分是對(duì)話(huà)消息顯示的部分,它的處理代碼對(duì)應(yīng)于例18.19。

服務(wù)器推送技術(shù) java


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 宜昌市| 板桥市| 卢氏县| 临汾市| 莒南县| 绍兴市| 祁门县| 呈贡县| 黔江区| 唐海县| 集贤县| 罗城| 沙雅县| 墨脱县| 河池市| 马鞍山市| 通榆县| 绥德县| 东山县| 宜川县| 普陀区| 称多县| 襄垣县| 沂水县| 班戈县| 顺平县| 遂平县| 汉寿县| 个旧市| 太白县| 大渡口区| 雷山县| 基隆市| 玛纳斯县| 安达市| 顺义区| 黄龙县| 中牟县| 南昌市| 灵台县| 西昌市|