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;