WebSocket實現數據庫更新前臺實時顯示
通過一個小實例來實現數據庫更新後,推送消息給前臺,讓前臺進行相應操作。
需求
數據庫更新之後服務器推送消息給前臺,讓前臺做操作。(數據庫的數據不是由服務器寫入的)
實現的話說到底都是用輪詢,因為數據庫的數據不是通過後臺插入更新的,所以無論用什麽辦法,都需要循環地去讀取數據庫中的信息或者數據庫的日誌文件。區別就是,到底是前臺輪詢,還是後臺輪詢了。
如果使用前臺輪詢,就是前臺定期給後臺發送請求,來對數據進行更新,用setInterval()就能實現。你F12看Network就能看到一會就有幾十甚至幾百個請求。。因為我也是第一次實現這樣的功能,雖然對性能這方面沒有什麽研究,但是看到短時間內這麽多請求還是覺得心慌慌。
所以想到了使用後臺輪詢,後臺輪詢的好處就是,前臺不用一直發送請求給後臺,而是等到後臺發現數據更新了再提醒前臺重新請求數據。這就需要用到WebSocket。
我們平常使用的http連接,都是只能客戶端向服務器發送請求。
而WebSocket的最大特點就是,服務器可以主動向客戶端推送信息,客戶端也可以主動向服務器發送信息,是真正的雙向平等對話,屬於服務器推送技術的一種。
在查詢資料的時候也查到可以用數據庫的存儲過程來實現,在存儲數據的時候,調用Java的程序來進行通知。(因為還有一些處理方面的問題沒有去實現)
環境
Server version : Apache Tomcat/7.0.69
Java version: 1.7.0_80
需要引入的jar包:tomcat自帶的tomcat7-websocket.jar和websocket-api.jar,這兩個jar包都在tomcat安裝目錄的lib文件夾下。
需要註意的是:tomcat需要7.0.47版本以上才支持JSR-356,具體文檔可以查看
思路
在建立連接的時候開啟一個線程對數據庫中的數據進行輪詢,如果查詢到數據變化了,就發消息給WebSocket實現類,實現類接收到消息後,推送消息給連接著的用戶。
(如果數據是通過後臺添加的,就不用這麽麻煩了,直接在添加數據的操作類中發送消息給WebSocket實現類就好了)
客戶端代碼
這部分比較簡單,就是通過url來建立WebSocket連接,協議名稱ws也就是WebSocket。在websocket.onmessage()方法中對接收到的消息進行處理,你可以做輸出也可以更新頁面等等。
var websocket = null; //判斷當前瀏覽器是否支持WebSocket if (‘WebSocket‘ in window) { //建立連接,這裏的/websocket ,是Servlet中註解中的那個值 websocket = new WebSocket("ws://localhost:8080/項目名/websocket"); } else { alert(‘當前瀏覽器 Not support websocket‘); } //連接發生錯誤的回調方法 websocket.onerror = function () { console.log("WebSocket連接發生錯誤"); }; //連接成功建立的回調方法 websocket.onopen = function () { console.log("WebSocket連接成功"); } //接收到消息的回調方法 websocket.onmessage = function (event) { console.log(event.data); if(event.data=="1"){ console.log("數據更新啦"); } } //連接關閉的回調方法 websocket.onclose = function () { console.log("WebSocket連接關閉"); } //監聽窗口關閉事件,當窗口關閉時,主動去關閉WebSocket連接,防止連接還沒斷開就關閉窗口,server端會拋異常。 window.onbeforeunload = function () { closeWebSocket(); } //關閉WebSocket連接 function closeWebSocket() { websocket.close(); }
服務器端代碼
在開啟連接的時候啟動了一個線程,關閉連接的時候調用線程的stopMe()方法,終止線程。當接收到消息的時候,調用sendMessage()方法給所有連接著的用戶發送消息。
需要註意的是,一旦建立了連接,就會創建一個session,這個session和request中的session不一樣,但是可以用類似的想法來理解。所以在發送消息的時候也需要調用session的方法來給連接著的用戶發送消息。
WebSocket session發送文本消息有兩個方法:getAsyncRemote()和getBasicRemote(),這兩個方法我只是簡單了解了一下,前者是異步發送消息,後者是同步發送消息。也就是說getBasicRemote()要等上一條消息發送完才能發送下一條消息。如果有錯誤的話希望大家指出!
在文檔中我們看到也可以在服務器端接收消息的時候也可以直接在onMessage()方法中return txt.toUpperCase()來發送消息給消息發送方,但是在這個例子中,我們的消息是線程發送給WebSocket實現類的,所以不用這個方法。
//在相對路徑中發布端點websocket @ServerEndpoint("/websocket") public class WebSocketServlet { MyThread thread1=new MyThread(); Thread thread=new Thread(thread1); //用來存放每個客戶端對應的MyWebSocket對象。 private static CopyOnWriteArraySet<WebSocketServlet> webSocketSet = new CopyOnWriteArraySet<WebSocketServlet>(); private javax.websocket.Session session=null; /** * @ClassName: onOpen * @Description: 開啟連接的操作 */ @OnOpen public void onOpen(Session session) throws IOException{ this.session=session; webSocketSet.add(this); System.out.println(webSocketSet); //開啟一個線程對數據庫中的數據進行輪詢 thread.start(); } /** * @ClassName: onClose * @Description: 連接關閉的操作 */ @OnClose public void onClose(){ thread1.stopMe(); webSocketSet.remove(this); } /** * @ClassName: onMessage * @Description: 給服務器發送消息告知數據庫發生變化 */ @OnMessage public void onMessage(int count) { System.out.println("發生變化"+count); try { sendMessage(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * @ClassName: OnError * @Description: 出錯的操作 */ @OnError public void onError(Throwable error){ System.out.println(error); error.printStackTrace(); } /** * 這個方法與上面幾個方法不一樣。沒有用註解,是根據自己需要添加的方法。 * @throws IOException * 發送自定義信號,“1”表示告訴前臺,數據庫發生改變了,需要刷新 */ public void sendMessage() throws IOException{ //群發消息 for(WebSocketServlet item: webSocketSet){ item.session.getBasicRemote().sendText("1"); } } }
線程的定義
線程先對數據庫中的數據查詢一次,存在sum變量中,然後再一直對數據庫中的數據進行輪詢,new_sum與sum不同的話就發送消息給WebSocket實現類。
public class MyThread implements Runnable{ private int sum; private int new_sum; private boolean stopMe = true; public void stopMe() { stopMe = false; } /* (non-Javadoc) * @see java.lang.Runnable#run() */ public void run() { UrlDao urlDao=new UrlDao(); sum=urlDao.selectCount(); WebSocketServlet wbs=new WebSocketServlet(); while(stopMe){ new_sum=urlDao.selectCount(); if(sum!=new_sum){ System.out.println("change"); sum=new_sum; wbs.onMessage(sum); } try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
測試
至此我的需求是大概完成了,因為是第一次寫WebSocket和線程相關的實例,如果有問題希望大家可以指出!
WebSocket實現數據庫更新前臺實時顯示