|
名稱
|
說(shuō)明
|
事件處理方法
|
|
open
|
當(dāng)成功與服務(wù)器建立連接時(shí)產(chǎn)生
|
onopen
|
|
message
|
當(dāng)收到服務(wù)器發(fā)送的事件時(shí)產(chǎn)生
|
onmessage
|
|
error
|
當(dāng)出現(xiàn)錯(cuò)誤時(shí)產(chǎn)生
|
onerror
|
如之前所述,服務(wù)器端可以返回自定義類型的事件。對(duì)于這些事件,可以使用 addEventListener 方法來(lái)添加相應(yīng)的事件處理方法。代碼清單 2 給出了 EventSource 對(duì)象的使用示例。
EventSource 對(duì)象的使用示例
var es = new EventSource('events');
es.onmessage = function(e) {
console.log(e.data);
};
es.addEventListener('myevent', function(e) {
console.log(e.data);
});
如上所示,在指定 URL 創(chuàng)建出 EventSource 對(duì)象之后,可以通過(guò) onmessage 和 addEventListener 方法來(lái)添加事件處理方法。當(dāng)服務(wù)器端有新的事件產(chǎn)生,相應(yīng)的事件處理方法會(huì)被調(diào)用。EventSource 對(duì)象的 onmessage 屬性的作用類似于 addEventListener( ‘ message ’ ),不過(guò) onmessage 屬性只支持一個(gè)事件處理方法。在介紹完服務(wù)器推送事件的規(guī)范內(nèi)容之后,下面介紹服務(wù)器端的實(shí)現(xiàn)。
服務(wù)器端和瀏覽器端實(shí)現(xiàn)
從上一節(jié)中對(duì)通訊協(xié)議的描述可以看出,服務(wù)器端推送事件是一個(gè)比較簡(jiǎn)單的協(xié)議。服務(wù)器端的實(shí)現(xiàn)也相對(duì)比較簡(jiǎn)單,只需要按照協(xié)議規(guī)定的格式,返回響應(yīng)內(nèi)容即可。在開源社區(qū)可以找到各種不同的服務(wù)器端技術(shù)相對(duì)應(yīng)的實(shí)現(xiàn)。自己開發(fā)的難度也不大。本文使用 Java 作為服務(wù)器端的實(shí)現(xiàn)語(yǔ)言。相應(yīng)的實(shí)現(xiàn)基于開源的 jetty-eventsource-servlet 項(xiàng)目,見參考資源。下面通過(guò)一個(gè)具體的示例來(lái)說(shuō)明如何使用 jetty-eventsource-servlet 項(xiàng)目。示例用來(lái)模擬一個(gè)物體在某個(gè)限定空間中的隨機(jī)移動(dòng)。該物體從一個(gè)隨機(jī)位置開始,然后從上、下、左和右四個(gè)方向中隨機(jī)選擇一個(gè)方向,并在該方向上移動(dòng)隨機(jī)的距離。服務(wù)器端不斷改變?cè)撐矬w的位置,并把位置信息推送給瀏覽器,由瀏覽器來(lái)顯示。
服務(wù)器端實(shí)現(xiàn)
服務(wù)器端的實(shí)現(xiàn)由兩部分組成:一部分是用來(lái)產(chǎn)生數(shù)據(jù)的 org.eclipse.jetty.servlets.EventSource 接口的實(shí)現(xiàn),另一部分是作為瀏覽器訪問(wèn)端點(diǎn)的繼承自 org.eclipse.jetty.servlets.EventSourceServlet 類的 servlet 實(shí)現(xiàn)。下面代碼給出了 EventSource 接口的實(shí)現(xiàn)類。
EventSource 接口的實(shí)現(xiàn)類 MovementEventSource
public class MovementEventSource implements EventSource {
private int width = 800;
private int height = 600;
private int stepMax = 5;
private int x = 0;
private int y = 0;
private Random random = new Random();
private Logger logger = Logger.getLogger(getClass().getName());
public MovementEventSource(int width, int height, int stepMax) {
this.width = width;
this.height = height;
this.stepMax = stepMax;
this.x = random.nextInt(width);
this.y = random.nextInt(height);
}
@Override
public void onOpen(Emitter emitter) throws IOException {
query(emitter); //開始生成位置信息
}
@Override
public void onResume(Emitter emitter, String lastEventId)
throws IOException {
updatePosition(lastEventId); //更新起始位置
query(emitter); //開始生成位置信息
}
//根據(jù)Last-Event-Id來(lái)更新起始位置
private void updatePosition(String id) {
if (id != null) {
String[] pos = id.split(",");
if (pos.length > 1) {
int xPos = -1, yPos = -1;
try {
xPos = Integer.parseInt(pos[0], 10);
yPos = Integer.parseInt(pos[1], 10);
} catch (NumberFormatException e) {
}
if (isValidMove(xPos, yPos)) {
x = xPos;
y = yPos;
}
}
}
}
private void query(Emitter emitter) throws IOException {
emitter.comment("Start sending movement information.");
while(true) {
emitter.comment("");
move(); //移動(dòng)位置
String id = String.format("%s,%s", x, y);
emitter.id(id); //根據(jù)位置生成事件標(biāo)識(shí)符
emitter.data(id); //發(fā)送位置信息數(shù)據(jù)
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
logger.log(Level.WARNING, \
"Movement query thread interrupted. Close the connection.", e);
break;
}
}
emitter.close(); //當(dāng)循環(huán)終止時(shí),關(guān)閉連接
}
@Override
public void onClose() {
}
//獲取下一個(gè)合法的移動(dòng)位置
private void move() {
while (true) {
int[] move = getMove();
int xNext = x + move[0];
int yNext = y + move[1];
if (isValidMove(xNext, yNext)) {
x = xNext;
y = yNext;
break;
}
}
}
//判斷當(dāng)前的移動(dòng)位置是否合法
private boolean isValidMove(int x, int y) {
return x >= 0 && x <= width && y >=0 && y <= height;
}
//隨機(jī)生成下一個(gè)移動(dòng)位置
private int[] getMove() {
int[] xDir = new int[] {-1, 0, 1, 0};
int[] yDir = new int[] {0, -1, 0, 1};
int dir = random.nextInt(4);
return new int[] {xDir[dir] * random.nextInt(stepMax), \
yDir[dir] * random.nextInt(stepMax)};
}
}
類 MovementEventSource 需要實(shí)現(xiàn) EventSource 接口的 onOpen、onResume 和 onClose 方法,其中 onOpen 方法在瀏覽器端的連接打開的時(shí)候被調(diào)用,onResume 方法在瀏覽器端重新建立連接時(shí)被調(diào)用,onClose 方法則在瀏覽器關(guān)閉連接的時(shí)候被調(diào)用。onOpen 和 onResume 方法都有一個(gè) EventSource.Emitter 接口類型的參數(shù),可以用來(lái)發(fā)送數(shù)據(jù)。EventSource.Emitter 接口中包含的方法包括 data、event、comment、id 和 close 等,分別對(duì)應(yīng)于通訊協(xié)議中各種不同類型的事件。而 onResume 方法還額外包含一個(gè)參數(shù) lastEventId,表示通過(guò) Last-Event-ID 頭發(fā)送過(guò)來(lái)的最近一次事件的標(biāo)識(shí)符。
MovementEventSource 類中事件生成的主要邏輯在 query 方法中。該方法中包含一個(gè)無(wú)限循環(huán),每隔 2 秒鐘改變一次位置,同時(shí)把更新之后的位置通過(guò) EventSource.Emitter 接口的 data 方法發(fā)送給瀏覽器端。每個(gè)事件都有對(duì)應(yīng)的標(biāo)識(shí)符,而標(biāo)識(shí)符的值就是位置本身。如果連接斷開之后,瀏覽器重新進(jìn)行連接,可以從上一次的位置開始繼續(xù)移動(dòng)該物體。
與 MovementEventSource 類對(duì)應(yīng)的 servlet 實(shí)現(xiàn)比較簡(jiǎn)單,只需要繼承自 EventSourceServlet 類并覆寫 newEventSource 方法即可。在 newEventSource 方法的實(shí)現(xiàn)中,需要返回一個(gè) MovementEventSource 類的對(duì)象,如下所示。每當(dāng)瀏覽器端建立連接時(shí),該 servlet 會(huì)創(chuàng)建一個(gè)新的 MovementEventSource 類的對(duì)象來(lái)處理該請(qǐng)求。
servlet 實(shí)現(xiàn)類 MovementServlet
public class MovementServlet extends EventSourceServlet {
@Override
protected EventSource newEventSource(HttpServletRequest request,
String clientId) {
return new MovementEventSource(800, 600, 20);
}
}
在服務(wù)器端實(shí)現(xiàn)中,需要注意的是要添加相應(yīng)的 servlet 過(guò)濾器支持。這是 jetty-eventsource-servlet 項(xiàng)目所依賴的 Jetty Continuations 框架的要求,否則的話會(huì)出現(xiàn)錯(cuò)誤。添加過(guò)濾器的方式是在 web.xml 文件中添加代碼如下所示的配置內(nèi)容。
Jetty Continuations 所需 servlet 過(guò)濾器的配置
<filter>
<filter-name>continuation</filter-name>
<filter-class>org.eclipse.jetty.continuation.ContinuationFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>continuation</filter-name>
<url-pattern>/sse/*</url-pattern>
</filter-mapping>
瀏覽器端實(shí)現(xiàn)
瀏覽器端的實(shí)現(xiàn)也比較簡(jiǎn)單,只需要?jiǎng)?chuàng)建出 EventSource 對(duì)象,并添加相應(yīng)的事件處理方法即可。下面代碼給出了相應(yīng)的實(shí)現(xiàn)。在頁(yè)面中使用一個(gè)方塊表示物體。當(dāng)接收到新的事件時(shí),根據(jù)事件數(shù)據(jù)中給出的坐標(biāo)信息,更新方塊在頁(yè)面上的位置。
瀏覽器端的實(shí)現(xiàn)代碼
var es = new EventSource('sse/movement');
es.addEventListener('message', function(e) {
var pos = e.data.split(','), x = pos[0], y = pos[1];
$('#box').css({
left : x + 'px',
top : y + 'px'
});
});
在介紹完基本的服務(wù)器端和瀏覽器端實(shí)現(xiàn)之后,下面介紹比較重要的 IE 的支持。
IE 支持
使用瀏覽器原生的 EventSource 對(duì)象的一個(gè)比較大的問(wèn)題是 IE 并不提供支持。為了在 IE 上提供同樣的支持,一般有兩種辦法。第一種辦法是在其他瀏覽器上使用原生 EventSource 對(duì)象,而在 IE 上則使用簡(jiǎn)易輪詢或 COMET 技術(shù)來(lái)實(shí)現(xiàn);另外一種做法是使用 polyfill 技術(shù),即使用第三方提供的 JavaScript 庫(kù)來(lái)屏蔽瀏覽器的不同。本文使用的是 polyfill 技術(shù),只需要在頁(yè)面中加載第三方 JavaScript 庫(kù)即可。應(yīng)用本身的瀏覽器端代碼并不需要進(jìn)行改動(dòng)。一般推薦使用第二種做法,因?yàn)檫@樣的話,在服務(wù)器端只需要使用一種實(shí)現(xiàn)技術(shù)即可。
在 IE 上提供類似原生 EventSource 對(duì)象的實(shí)現(xiàn)并不簡(jiǎn)單。理論上來(lái)說(shuō),只需要通過(guò) XMLHttpRequest 對(duì)象來(lái)獲取服務(wù)器端的響應(yīng)內(nèi)容,并通過(guò)文本解析,就可以提取出相應(yīng)的事件,并觸發(fā)對(duì)應(yīng)的事件處理方法。不過(guò)問(wèn)題在于 IE 上的 XMLHttpRequest 對(duì)象并不支持獲取部分的響應(yīng)內(nèi)容。只有在響應(yīng)完成之后,才能獲取其內(nèi)容。由于服務(wù)器端推送事件使用的是一個(gè)長(zhǎng)連接。當(dāng)連接一直處于打開狀態(tài)時(shí),通過(guò) XMLHttpRequest 對(duì)象并不能獲取響應(yīng)的內(nèi)容,也就無(wú)法觸發(fā)對(duì)應(yīng)的事件。更具體的來(lái)說(shuō),當(dāng) XMLHttpRequest 對(duì)象的 readyState 為 3(READYSTATE_INTERACTIVE)時(shí),其 responseText 屬性是無(wú)法獲取的。
為了解決 IE 上 XMLHttpRequest 對(duì)象的問(wèn)題,就需要使用 IE 8 中引入的 XDomainRequest 對(duì)象。XDomainRequest 對(duì)象的作用是發(fā)出跨域的 AJAX 請(qǐng)求。XDomainRequest 對(duì)象提供了 onprogress 事件。當(dāng) onprogress 事件發(fā)生時(shí),可以通過(guò) responseText 屬性來(lái)獲取到響應(yīng)的部分內(nèi)容。這是 XDomainRequest 對(duì)象和 XMLHttpRequest 對(duì)象的最大不同,也是使用 XDomainRequest 對(duì)象來(lái)實(shí)現(xiàn)類似原生 EventSource 對(duì)象的基礎(chǔ)。在使用 XDomainRequest 對(duì)象打開與服務(wù)器端的連接之后,當(dāng)服務(wù)器端有新的數(shù)據(jù)產(chǎn)生時(shí),可以通過(guò) XDomainRequest 對(duì)象的 onprogress 事件的處理方法來(lái)進(jìn)行處理,對(duì)接收到的數(shù)據(jù)進(jìn)行解析,根據(jù)數(shù)據(jù)的內(nèi)容觸發(fā)相應(yīng)的事件。
不過(guò)由于 XDomainRequest 對(duì)象本來(lái)的目的是發(fā)出跨域 AJAX 請(qǐng)求,考慮到跨域訪問(wèn)的安全性問(wèn)題,XDomainRequest 對(duì)象在使用時(shí)的限制也比較嚴(yán)格。這些限制會(huì)影響到其作為 EventSource 對(duì)象的實(shí)現(xiàn)方式。具體的限制和解決辦法如下所示:
由于 XDomainRequest 對(duì)象的這些限制,服務(wù)器端的實(shí)現(xiàn)也需要作出相應(yīng)的改動(dòng)。這些改動(dòng)包括返回 Access-Control-Allow-Origin 頭;對(duì)于瀏覽器端發(fā)送的“text/plain”類型的參數(shù)進(jìn)行解析;處理請(qǐng)求中包含的用戶認(rèn)證相關(guān)的信息。
本文的示例使用的 polyfill 庫(kù)是 GitHub 上的 Yaffle 開發(fā)的 EventSource 項(xiàng)目,具體的地址見參考資源。在使用該 polyfill 庫(kù),并對(duì)服務(wù)器端的實(shí)現(xiàn)進(jìn)行修改之后,就可以在 IE 8 及以上的瀏覽器中使用服務(wù)器推送事件。如果需要支持 IE 7,則只能使用簡(jiǎn)易輪詢或 COMET 技術(shù)。本文的示例代碼見參考資源。
小結(jié)
如果需要從服務(wù)器端推送數(shù)據(jù)給瀏覽器,可以使用的基于 HTML 5 規(guī)范標(biāo)準(zhǔn)的技術(shù)包括 WebSocket 和服務(wù)器推送事件。開發(fā)人員可以根據(jù)應(yīng)用的具體需求來(lái)選擇合適的技術(shù)。如果只是需要從服務(wù)器端推送數(shù)據(jù),服務(wù)器推送事件的規(guī)范更加簡(jiǎn)單,實(shí)現(xiàn)起來(lái)更容易。本文對(duì)服務(wù)器推送事件的規(guī)范內(nèi)容、服務(wù)器端和瀏覽器端的實(shí)現(xiàn)都進(jìn)行了詳細(xì)的介紹,對(duì)如何支持 IE 瀏覽器也進(jìn)行了具體的分析。
標(biāo)簽:惠州 文山 萍鄉(xiāng) 營(yíng)口 紅河 蘇州 甘南 咸陽(yáng)
巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《html5服務(wù)器推送_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理》,本文關(guān)鍵詞 html5,服務(wù)器,推送,動(dòng)力,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問(wèn)題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無(wú)關(guān)。