反向Ajax,第3部分:Web伺服器和Socket.IO)

來源:互聯網
上載者:User

 前言

時至今日,使用者期待的是可通過web訪問快速、動態應用。這一文章系列展示了如何使用反向Ajax(Reverse Ajax)技術來開發事件驅動的web應用。系列的第1部分介紹了反向Ajax、輪詢(polling)、流(streaming)、Comet和長輪詢(long polling)。你已經瞭解了Comet是如何使用HTTP長輪詢的,這是可靠地實現反向Ajax的最好方式,因為現有的所有瀏覽器都提供支援。系列的第2部分說明了如何使用WebSocket來實現反向Ajax,一些代碼例子被用來協助說明WebSocket、FlashSocket、伺服器端的約束、請求範圍(request-scoped)服務以及暫停長生存期請求等。

在本篇文章中,我們深入細節,討論在web應用中使用不同web容器和API(Servlet 3.0和Jetty Continuations)的Comet和WebSocket,瞭解如何通過使用諸如Socket.IO一類的抽象庫來透明地使用Comet和Websocket。Socket.IO使用功能檢測來確定串連是使用WebSocket、Ajax長輪詢、Flash還是其他方式來建立。

前提條件

理想情況下,要充分體會本文的話,你應該對JavaScrpit和Java有一定的瞭解。本文中建立的例子是使用Google Guice來構建的,這是一個使用Java編寫的依賴注入架構。若要讀懂文中所談內容,你應該要熟悉諸如Guice、Spring或是Pico一類的依賴注入架構的概念。

若要運行本文中的例子,你還需要最新版本的Maven和JDK(參見參考資料)。

  Comet和WebSocket的伺服器端解決方案

你在第1部分內容中已經瞭解到了,Comet(長輪詢或是流)需要伺服器端能夠暫停某個請求,並在一個可能的長延遲之後恢複或是完成該請求。第2部分內容描述了伺服器端如何使用非阻塞式的I/O功能來處理大量的串連,以及它們只能使用線程來服務要求(每個請求一個線程模式)。你也已經瞭解到了WebSocket的使用是伺服器端依賴的,且並非所有的伺服器都支援WebSocket。

本節內容說明了如果適用的話,那麼在Jetty、Tomcat和Grizzly等web伺服器上是如何使用Comet和WebSocket的。本文提供的原始碼包含了Jetty和Tomcat的一個聊天web應用例子。本節內容還討論了下面的這些應用伺服器:Jboss、Glassfish和WebSphere所支援的API。

Jetty

Jetty是一個web伺服器,支援Java Servlet 3.0規範、WebSocket和其他的許多整合規範。Jetty:

1. 功能強大且靈活

2. 易於嵌入

3. 支援虛擬機器主機、會話叢集和許多可很容易地通過用於Google App Engine的託管服務的Java代碼或是XML來配置的功能。

核心的Jetty項目由Eclipse Foundation打理。

從版本6開始,Jetty加入了一個被稱作Jetty Continuation(Jetty延續)的非同步API,該API允許請求被暫停並在之後被恢複。表1給出了所支援的規範和Jetty的主要版本系列的API之間的一個對照關係。

表1. Jetty的版本和支援

Supports Jetty 6 Jetty 7 Jetty 8
Non-blocking I/O X X X
Servlet 2.5 X X X
Servlet 3.0   X X
Jetty Continuations (Comet) X X X
WebSockets   X X

若要實現使用Comet的反向Ajax的話,你可以使用Jetty的Continuation API,如清單1所示:

清單1. 用於Comet的Jetty Continuation API

// 暫停一個來自servlet方法(get、post……)的請求:
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Continuation continuation = ContinuationSupport.getContinuation(req);
// 可選的做法,設定逾時以避免請求掛起過久
continuation.setTimeout(0);
// 掛起請求
continuation.suspend();
// 儲存引用,以備將來另一線程使用
continuations.offer(continuation);
}
// 然後,來自另一個想給客戶發送事件的線程:
while (!continuations.isEmpty()) {
Continuation continuation = continuations.poll();
HttpServletResponse response =
(HttpServletResponse) continuation.getServletResponse();
// 向響應中寫入
continuation.complete();
}

完整的web應用在本文所帶的原始碼中。Jetty Continuation被打包放在一個JAR歸檔檔案中,你需要把這一JAR檔案放在web應用的WEB-INF/lib目錄下才能使用Jetty的Comet功能。Jetty Continuation在Jetty 6、7和8上都是可用的。

從Jetty 7開始,你還可以訪問WebSocket功能,把Jetty的WebSocket JAR檔案放在web應用的WEB-INF/lib目錄下以獲得對Jetty的WebSocket API的訪問,如清單2所示:

清單2. Jetty的Websocket API

// 實現doWebSocketConnect並返回WebSocket的實現

publicfinalclass ReverseAjaxServlet extends WebSocketServlet {
@Override
protected WebSocket doWebSocketConnect(HttpServletRequest request,
String protocol) {
return [...]
}
}

// WebSocket的樣本實現

class Endpoint implements WebSocket {
Outbound outbound;
publicvoid onConnect(Outbound outbound) {
this.outbound = outbound;
}
publicvoid onMessage(byte opcode, String data) {
outbound.sendMessage(“Echo: ”+ data);
if(“close”.equals(data))
outbound.disconnect();
}
publicvoid onFragment(boolean more, byte opcode,
byte[] data, int offset, int length) {
}
publicvoid onMessage(byte opcode, byte[] data,
int offset, int length) {
onMessage(opcode, new String(data, offset, length));
}
publicvoid onDisconnect() {
outbound =null;
}
}

在下載的原始碼的jetty-websocket目錄下有一個聊天應用的例子,該例子說明了如何使用Jetty的WebSocket API。

Tomcat

Tomcat可能是最廣為人知的web伺服器了,其已經使用多年,且被作為web容器整合在Jboss應用伺服器的早期版本中。Tomcat也被用作是servlet規範的參考實現,但到了servlet API 2.5後就不再是了,這時人們開始尋求另一種基於非阻塞式I/O的做法(比如說Jetty)。表2給出了所支援的規範和兩個最新的Tomcat版本系列的API之間的對照。

表2. Tomcat的支援

Supports Tomcat 6 Tomcat 7
Non-blocking I/O X X
Servlet 2.5 X X
Servlet 3.0   X
Advanced I/O (Comet) X X
WebSockets    

正如表2所示的那樣,Tomcat不支援WebSocket;它有一個相當於Jetty的Continuation的API,這一被稱為Advanced I/O的API支援Comet。與其說Advanced I/O是一個很好地方便了Comet使用的API,倒不如說它是一個封裝了NIO的低層麵包裝器。它缺乏文檔資料,幾乎沒有什麼使用API的應用例子。清單3給出了一個servlet例子,該例子掛起並恢複聊天web應用中的請求。你可以在本文的原始碼中找到完整的web應用。

清單3. Tomcat用於Comet的API

publicfinalclass ChatServlet extends HttpServlet
implements CometProcessor {
privatefinal BlockingQueue events =
new LinkedBlockingQueue();

publicvoid event(CometEvent evt)
throws IOException, ServletException {
HttpServletRequest request = evt.getHttpServletRequest();
String user =
(String) request.getSession().getAttribute(“user”);
switch (evt.getEventType()) {
case BEGIN: {
if (“GET”.equals(request.getMethod())) {
evt.setTimeout(Integer.MAX_VALUE);
events.offer(evt);
} else {
String message = request.getParameter(“message”);
if (“/disconnect”.equals(message)) {
broadcast(user +” disconnected”);
request.getSession().removeAttribute(“user”);
events.remove(evt);
} elseif (message !=null) {
broadcast(“["+ user +"]“+ message);
}
evt.close();
}
}
}
}

void broadcast(String message) throws IOException {
Queue q =new LinkedList();
events.drainTo(q);
while (!q.isEmpty()) {
CometEvent event = q.poll();
HttpServletResponse resp = event.getHttpServletResponse();
resp.setStatus(HttpServletResponse.SC_OK);
resp.setContentType(“text/html”);
resp.getWriter().write(message);
event.close();
}
}
}

在Tomcat中,非同步servlet必須要實現CometProcessor。對於非同步servlet來說,Tomcat並不調用標準的HTTP方法(doGet、doPost等),取而代之的是,事件會被發送給event(CometEvent)方法。當請求首次到達時,例子查看是否是一個GET,是的話便掛起它,evt.close()沒有被調用。如果是一個POST的話,這意味著是使用者發送了一個訊息,因此給其他CometEvent廣播訊息,然後調用evt.close()來完成該post請求。在客戶這一端,廣播會讓所有的長輪詢請求完成訊息的發送,另一個長輪詢請求會被立刻發送出去以接收接下來的事件。

Grizzly和Glassfish

Grizzly不是一個web容器,它更像是一個用來協助開發人員構建可伸縮應用的NIO架構。它是被作為Glassfish項目的一部分開發出來的,但也可以獨立或是嵌入使用。Grizzly提供了充當HTTP/HTTPS伺服器的組件,除了其他的一些組件之外,其還提供了Bayeux Protocol、Servlet、HttpService OSGi和Comet組件。Grizzly支援WebSocket,其被用在Glassfish中提供Comet和WebSocket支援。

Oracle的應用伺服器Glassfish是J2EE 6規範的參考實現。Glassfish是一個完整的套件,類似WebSphere和Jboss,使用Grizzly來支援NIO、Websocket和Comet。它的基於OSGI的模組化架構,使得其在更換組件時具有真正的靈活性。表3說明了Glassfish對Comet和WebSocket的支援。

表3. Glashfish的支援

Supports Glassfish 2 Glassfish 3
Non-blocking I/O X X
Servlet 2.5 X X
Servlet 3.0   X
Comet X X
WebSockets   X

Grizzly的用法值得一說,因為它原本打算是以嵌入的方式或是直接通過Java代碼來使用的。它被廣泛用作支援Comet和WebSocket的架構,可被嵌入到一個更大的應用中,比如說Glassfish,該應用提供了web部署功能和Servlet規範API。

請參閱參考資料一節獲得到Grizzly或是Glassfish中的WebSocket和Comet的例子的連結,因為Glassfish使用了Grizzly,所以例子在兩者上面都是應該能夠啟動並執行。WebSocket API與Jetty中的非常類似,但Comet API就更複雜一些。

Jboss

Jboss是一個構建在Tomcat之上的應用伺服器,它從版本5開始支援Comet和NIO。Jboss 7還在開發中,不過也被放在了下面的表4中進行比較。

表4. Jboss的支援


Supports Jboss 5 Jboss 6 Jboss 7
Non-blocking I/O X X X
Servlet 2.5 X X X
Servlet 3.0   X X
Comet X X X
WebSockets      

WebSphere

WebSphere是一個IBM的應用伺服器,版本8的WebSphere支援Servlet 3 API(包含了用於Comet的標準的非同步API)(請參閱參考資料來瞭解發布公告的內容)。

表5. WebSphere的支援

Supports WebSphere 8
Non-blocking I/O X
Servlet 2.5 X
Servlet 3.0 X
Comet X
WebSockets  

通用API方面的情況

每個伺服器都有自己的本地Comet和WebSocket API,正如你或已猜到的那樣,編寫一個可移植的web應用可能會很難。Servlet 3.0包含了掛起和之後再恢複請求的附加方法,允許所有支援Servlet 3.0規範的web容器支援Comet和長輪詢請求。

Jetty提供了一個被稱作Jetty Continuation的庫,該庫獨立於Jetty容器存在。Jetty Continuation庫非常聰明,能夠檢測出可用的容器或是規範。如果是運行在Jetty伺服器上,應用所使用的就是本地的Jetty API;如果是運行在支援Servlet 3.0規範的容器上的話,應用所使用的就是這一通用的API;否則的話,就要使用一個無伸縮性的實現。

關於WebSocket,現在在Java中還沒有一個標準,因此,如果你想要使用WebSocket的話,就需要在web應用中使用容器供應商的API。

表6總結了各種伺服器支援的技術。

表6. 伺服器支援的技術

Container Comet WebSocket
Jetty 6 Jetty Continuations N/A
Jetty 7 Servlet 3.0
Jetty Continuations
Native Jetty API
Jetty 8 Servlet 3.0
Jetty Continuations
Native Jetty API
Tomcat 6 Advanced I/O N/A
Tomcat 7 Servlet 3.0
Advanced I/O
Jetty Continuations
N/A
Glassfish 2 Native Grizzly API N/A
Glassfish 3 Servlet 3.0
Native Grizzly API
Jetty Continuations
Native Grizzly API
Jboss 5 Native Jboss API N/A
Jboss 6 Servlet 3.0
Native Jboss API
Jetty Continuations
N/A
Jboss 7 Servlet 3.0
Native Jboss API
Jetty Continuations
N/A
WebSphere 8 Servlet 3.0
Jetty Continuations
N/A

除了使用容器的API之外,在WebSocket方面顯然還沒有什麼解決方案。至於Comet,每個支援Servlet 3.0規範的容器都支援Comet。Jetty Continuation在這方面的優勢是在所有的這些容器上都提供了對Comet的支援,因此,某些反向Ajax庫(這在下一節內容和本系列的下一篇文章中會談到)使用Jetty Continuation來作為它們的伺服器端API。

Jetty Continuation API在本文的Jetty例子中有說明,Servlet 3.0規範已經在本系列第1部分的兩個Comet例子中用到並做了介紹。

抽象庫

考慮一下所有主要的API(Servlet 3.0和Jetty Continuation),加上伺服器端的所有本地化支援,以及用戶端的兩種主要的實現反向Ajax的方式(Comet和WebSocket),通過編寫自己的JavaScript和Java代碼來捆綁它們實屬不易,你還要考慮逾時、串連失敗、確認、排序、緩衝等等。

本文餘下部分的內容說明了Socket.IO的用途,本系列的第4部分內容則會探討Atmosphere和CometD,這三個庫都是開源的,它們在許多伺服器上都支援Comet和WebSocket。

Socket.IO

Socket.IO是一個JavaScript用戶端庫,類似於WebSocket,其提供了單個的API來串連遠程伺服器,非同步地發送和接收訊息。通過提供一個通用的API,Socket.IO支援如下多種傳輸:WebSocket、Flash Socket、長輪詢、流、永久Iframe和JSONP輪詢。Socket.IO檢測瀏覽器的功能,然後嘗試選擇可用的最好傳輸方式。Socket.IO庫幾乎相容所有的瀏覽器(包括較舊的那些,比如IE 5.5)和行動瀏覽器,它還擁有其他的一些功能,比如說心跳、逾時、中斷連線和錯誤處理等。

Socket.IO網站(參見參考資料)詳細描述了該庫的使用方式,以及其所用到的瀏覽器和反向Ajax技術。基本情況是這樣,Socket.IO用到了一個通訊協定,該協議使得用戶端的庫能夠與伺服器端的端點進行通訊,伺服器端的端點能夠理解Socket.IO協議。Socket.IO一開始是為Node JS這一用來構建更快的伺服器的JavaScript引擎開發的,許多項目帶來了對其他語言的支援,其中就包括了Java語言。

清單4給出了一個在用戶端使用Socket.IO JavaScript庫的例子,Socket.IO網站上有文檔和例子。

清單4. Socket.IO用戶端庫的用法

var socket = new io.Socket(document.domain, {
resource: ’chat’
});
socket.on(‘connect’, function() {
// Socket.IO已串連
});
socket.on(‘disconnect’, function(disconnectReason, errorMessage) {
// Socket.IO已中斷連線
});
socket.on(‘message’, function(mtype, data, error) {
// 伺服器發送一個事件
});

// 現在處理常式已經定義好了,可以建立串連:
socket.connect();

若要使用Socket.IO JavaScript庫的話,你需要有相應的被稱作Socket.IO Java(參見參考資料)的Java部分。這一項目最初是由Apach Wave團隊啟動的,是為了給Wave提供反向Ajax支援,這是在WebSocket可用之前的事。Socket.IO Java派生出來後由Ovea(一家專門從事事件驅動的web開發的公司)來維護,再後來就被棄之不顧了。由於有多種傳輸的緣故,開發支援Socket.IO用戶端庫的後端是很複雜的過程。本系列的第4部分內容會說明,為了獲得更好的伸縮性和瀏覽器支援,在用戶端庫中支援多種傳輸是沒有必要的,因為有長輪詢和WebSocket就足夠了。在WebSocket不可用時,Socket.IO確實是一個不錯的選擇。

Socket.IO Java使用Jetty Continuation API來掛起和恢複請求,其使用本地的Jetty WebSocket API作為WebSocket支援。你可以確定使用了Socket.IO Java的web應用可在哪個伺服器上正確地運行。

下面清單5中的例子說明了在伺服器端如何使用Socket.IO,你需要定義一個繼承了SocketIOServlet的servlet,並實現一個返回某種端點表示的方法。這一API非常類似於WebSocket API,它的優點是用在伺服器端,獨立於用戶端所選擇的傳輸方式。Socket.IO把所有的傳輸類型都轉換成伺服器端的同一API。

清單5. 聊天例子中的Socket.IO Java的使用——servlet

public final class ChatServlet extends SocketIOServlet {
private final BlockingQueue endpoints =
new LinkedBlockingQueue();

@Override
protected SocketIOInbound doSocketIOConnect
(HttpServletRequest request) {
String user =
(String) request.getSession().getAttribute(“user”);
return user == null?null : new Endpoint(this, user, request);
}

void broadcast(String data) {
for (Endpoint endpoint : endpoints) {
endpoint.send(data);
}
}

void add(Endpoint endpoint) {
endpoints.offer(endpoint);
}

void remove(Endpoint endpoint) {
endpoints.remove(endpoint);
}
}

清單6說明了返回端點的方式。

清單6. 聊天例子中的Socket.IO Java庫的用法——endpoint

class Endpoint implements SocketIOInbound {
[...]
private SocketIOOutbound outbound;
[...]

@Override
publicvoid onConnect(SocketIOOutbound outbound) {
this.outbound = outbound;
servlet.add(this);
servlet.broadcast(user +” connected”);
}

@Override
publicvoid onDisconnect(DisconnectReason reason,
String errorMessage) {
outbound = null;
request.getSession().removeAttribute(“user”);
servlet.remove(this);
servlet.broadcast(user +” disconnected”);
}

@Override
public void onMessage(int messageType, String message) {
if (“/disconnect”.equals(message)) {
outbound.close();
} else {
servlet.broadcast(“["+ user +"] ”+ message);
}
}

void send(String data) {
try {
if (outbound !=null
&& outbound.getConnectionState() == ConnectionState.CONNECTED) {
outbound.sendMessage(data);
}
} catch (IOException e) {
outbound.close();
}
}

}

Socket.IO的完整例子放在原始碼的socketio目錄下。

結束語

所有的web容器都支援Comet,且大部分的容器都支援WebSocket,即使規範帶來了幾種不同的本地化實現,你還是可以使用Comet和通用API(Servlet 3.0和Jetty Continuation)來開發web應用。且更樂觀的情況是,你可以使用諸如Socket.IO一類的庫,以透明的方式來利用Comet和WebSocket的強大功能。還有兩個庫,Atmosphere和CometD,會在本系列的下一篇文章中談及。這三個庫提供了多瀏覽器支援、非常出色的使用者體驗,以及錯誤處理、更簡單的API、逾時和重串連等多方面的好處。

下載

描述        名稱           大小   下載方法

文章的原始碼    reverse_ajaxpt3_source.zip   21KB     HTTP

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.