Server-Sent Events(SSE)用於網頁自動獲取服務器上更新的數據,它是一個實時性的機制。
對於某些需要實時更新的數據(例如Facebook/Twitter 更新、估價更新、新的博文、賽事結果等)來說,有這麼幾種解決方案:
在客戶端重復的向服務端發送新請求。如果服務器沒有新的數據更動,關閉本次連接。然後客戶端在稍等一段時間之後,再次發起新請求,一直重復這樣的步驟。
在長輪詢中,客戶端發送一個請求到服務端。如果服務端沒有新的數據更動,那麼本次連接將會被保持,直到等待到更新後的數據,返回給客戶端並關閉這個連接。
SSE類似於長輪詢的機制,但是它在每一次的連接中,不只等待一次數據的更動。客戶端發送一個請求到服務端 ,服務端保持這個請求直到一個新的消息准備好,將消息返回至客戶端,此時不關閉連接,仍然保持它,供其它消息使用。SSE的一大特色就是重復利用一個連接來處理每一個消息(又稱event)。
WebSocket不同於以上的這些技術,因為它提供了一個真正意義上的雙向連接。WebSocket是HTML5中非常強大的新特性,已經得到廣泛應用。(這裡暫時不進行展開)
我們希望在html頁面中顯示一個動態變化的時間,這裡使用Server-Sent Events來實現,在後台獲取時間,不斷發送給前台。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script>
function start() {
var eventSource = new EventSource("HelloServlet");
eventSource.onmessage = function(event) {
document.getElementById("foo").innerHTML = event.data;
};
}
</script>
</head>
<body>
Time: <span id="foo"></span>
<br><br>
<button onclick="start()">Start</button>
</body>
</html>
這是一個很簡單的HTML頁面,結合JS代碼,我們現在是希望改變<span id="foo"></span>標簽中的值,這是最純粹的目的。下面分析代碼:
1.new出一個EventSource對象,這個對象就是用來請求服務斷的,它的構造方法中需要一個請求的URL,來請求哪一個Servlet/Action等。。。
2.利用EventSource對象的onmessage函數,得到服務端傳遞的數據。
再來分析後端代碼:
public class HelloServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// ContentType 必須指定為 text/event-stream
resp.setContentType("text/event-stream");
// CharacterEncoding 必須指定為 UTF-8
resp.setCharacterEncoding("UTF-8");
PrintWriter pw = resp.getWriter();
for(int i=0; i<10; i++) {
// 每次發送的消息必須以\n\n結束
pw.write("data: " + System.currentTimeMillis() + "\n\n");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
pw.close();
}
}
這裡可以觀察到,後端代碼其實就是一個普通的Servlet(Web.xml省略),重寫了Get方法,利用response對象得到的PrintWriter來寫消息,這是一個大致的思路。
1.設置ContentType為text/event-stream
2.設置CharacterEncoding為UTF-8
3.得到PrintWriter對象,寫數據,格式為: data: xxxx \n\n 注意一定要有兩個\n\n作為結尾
這樣就告訴了客戶端:我發送了event-stream格式且編碼為UTF-8的數據,數據長這樣:
data: xxxx \n\n
然後客戶端的JS代碼利用EventSource對象的onmessage函數,監聽到了服務端的數據更動,解析內容xxxx,即event.data = xxxx;