Spring+websocket+quartz實現訊息定時推送

簡單的說,websocket是真正實現了全雙工通訊的伺服器向客戶端推的網際網路技術。

全雙工與單工、半雙工的區別?

全雙工:簡單地說,就是可以同時進行訊號的雙向傳輸(A->B且B->A),是瞬時同步的。

單工、半雙工:一個時間段內只有一個動作發生。

推送和拉取的區別?

推:由伺服器主動發訊息給客戶端,就像廣播。優勢在於,資訊的主動性和及時性。

拉:由客戶端主動請求所需要的資料。

實現訊息通訊的幾種方式?

傳統的http協議實現方式:。

傳統的socket技術。

websocket協議實現方式。

接下來我們主要講第三種,使用websocket協議,來實現服務端定時向客戶端推送訊息。

開發環境:jdk1。8、tomcat7

後臺:springmvc、websocket、quartz

前臺:html5中新增的API

開發工具:IDEA、maven

實現步驟

一、環境搭建

(1)匯入相關約束:

在pom檔案中加入需要的約束,spring相關的約束,請各位自己匯入,這裡我就不貼出來了。

<!—— 定時器的包 ——> org。quartz-scheduler quartz 2。3。0 <!—— spring-support。jar 這個jar 檔案包含支援UI模版(Velocity,FreeMarker,JasperReports),郵件服務,指令碼服務(JRuby),快取Cache(EHCache),任務計劃Scheduling(uartz)方面的類。 外部依賴spring-context, (spring-jdbc, Velocity, FreeMarker, JasperReports, BSH, Groovy, JRuby, Quartz, EHCache) ——> org。springframework spring-context-support 5。1。1。RELEASE <!—— websocket的包 ——> javax。websocket javax。websocket-api 1。1 provided <!——ps:如果使用原始的配置方式,需要匯入spring-websocket、spring-messaging的包,我們這裡就透過註解實現——>

(2)配置xml檔案

web。xml中就配置前端控制器,大家自行配置。然後,載入springmvc的配置檔案。

springmvc。xml檔案中

<!—— 自動將控制器載入到bean ——> <!—— 配置檢視解析器 ——> <!—— 自動註冊 DefaultAnnotationHandlerMapping 與 AnnotationMethodHandlerAdapter 兩個 bean, 解決了 @Controller 註解的使用前提配置 ——> <!—— 使用fastjson 解析json 因為本人的專案中用到了fastjson,所以這段配置大家可以忽略。 ——> text/html;charset=UTF-8 application/json WriteMapNullValue QuoteFieldNames

到此,環境就基本搭建完成了。

二、完成後臺的功能

這裡我就直接貼出程式碼了,上面有相關的註釋。

首先,完成websocket的實現類。

package com。socket。web。socket;import org。slf4j。Logger;import org。slf4j。LoggerFactory;import org。springframework。stereotype。Component;import javax。websocket。*;import javax。websocket。server。PathParam;import javax。websocket。server。ServerEndpoint;import java。io。IOException;import java。util。Map;import java。util。Set;import java。util。concurrent。ConcurrentHashMap;/** * @Author: 清風一陣吹我心 * @ProjectName: socket * @Package: com。socket。web。socket * @ClassName: WebSocketServer * @Description: * @Version: 1。0 **///ServerEndpoint它的功能主要是將目前的類定義成一個websocket伺服器端。註解的值將被用於監聽使用者連線的終端訪問URL地址。@ServerEndpoint(value = “/socket/{ip}”)@Componentpublic class WebSocketServer { //使用slf4j打日誌 private static final Logger LOGGER = LoggerFactory。getLogger(WebSocketServer。class); //用來記錄當前線上連線數 private static int onLineCount = 0; //用來存放每個客戶端對應的WebSocketServer物件 private static ConcurrentHashMap webSocketMap = new ConcurrentHashMap(); //某個客戶端的連線會話,需要透過它來給客戶端傳送資料 private Session session; //客戶端的ip地址 private String ip; /** * 連線建立成功,呼叫的方法,與前臺頁面的onOpen相對應 * @param ip ip地址 * @param session 會話 */ @OnOpen public void onOpen(@PathParam(“ip”)String ip,Session session){ //根據業務,自定義邏輯實現 this。session = session; this。ip = ip; webSocketMap。put(ip,this); //將當前物件放入map中 addOnLineCount(); //線上人數加一 LOGGER。info(“有新的連線加入,ip:{}!當前線上人數:{}”,ip,getOnLineCount()); } /** * 連線關閉呼叫的方法,與前臺頁面的onClose相對應 * @param ip */ @OnClose public void onClose(@PathParam(“ip”)String ip){ webSocketMap。remove(ip); //根據ip(key)移除WebSocketServer物件 subOnLineCount(); LOGGER。info(“WebSocket關閉,ip:{},當前線上人數:{}”,ip,getOnLineCount()); } /** * 當伺服器接收到客戶端傳送的訊息時所呼叫的方法,與前臺頁面的onMessage相對應 * @param message * @param session */ @OnMessage public void onMessage(String message,Session session){ //根據業務,自定義邏輯實現 LOGGER。info(“收到客戶端的訊息:{}”,message); } /** * 發生錯誤時呼叫,與前臺頁面的onError相對應 * @param session * @param error */ @OnError public void onError(Session session,Throwable error){ LOGGER。error(“WebSocket發生錯誤”); error。printStackTrace(); } /** * 給當前使用者傳送訊息 * @param message */ public void sendMessage(String message){ try{ //getBasicRemote()是同步傳送訊息,這裡我就用這個了,推薦大家使用getAsyncRemote()非同步 this。session。getBasicRemote()。sendText(message); }catch (IOException e){ e。printStackTrace(); LOGGER。info(“傳送資料錯誤:,ip:{},message:{}”,ip,message); } } /** * 給所有使用者發訊息 * @param message */ public static void sendMessageAll(final String message){ //使用entrySet而不是用keySet的原因是,entrySet體現了map的對映關係,遍歷獲取資料更快。 Set> entries = webSocketMap。entrySet(); for (Map。Entry entry : entries) { final WebSocketServer webSocketServer = entry。getValue(); //這裡使用執行緒來控制訊息的傳送,這樣效率更高。 new Thread(new Runnable() { public void run() { webSocketServer。sendMessage(message); } })。start(); } } /** * 獲取當前的連線數 * @return */ public static synchronized int getOnLineCount(){ return WebSocketServer。onLineCount; } /** * 有新的使用者連線時,連線數自加1 */ public static synchronized void addOnLineCount(){ WebSocketServer。onLineCount++; } /** * 斷開連線時,連線數自減1 */ public static synchronized void subOnLineCount(){ WebSocketServer。onLineCount——; } public Session getSession(){ return session; } public void setSession(Session session){ this。session = session; } public static ConcurrentHashMap getWebSocketMap() { return webSocketMap; } public static void setWebSocketMap(ConcurrentHashMap webSocketMap) { WebSocketServer。webSocketMap = webSocketMap; }}

然後寫我們的定時器(quartz),這裡我就不詳解定時器了。大家可以自行去了解。

這裡我使用的是xml註解的方式,建立一個job類,此類不需要繼承任何類和實現任何介面。

package com。socket。web。quartz;import com。socket。web。socket。WebSocketServer;import java。io。IOException;import java。util。Map;import java。util。concurrent。ConcurrentHashMap;/** * @Author: 清風一陣吹我心 * @ProjectName: socket * @Package: com。socket。web。quartz * @ClassName: TestJob * @Description: * @Version: 1。0 **/public class TestJob { public void task(){ //獲取WebSocketServer物件的對映。 ConcurrentHashMap map = WebSocketServer。getWebSocketMap(); if (map。size() != 0){ for (Map。Entry entry : map。entrySet()) { WebSocketServer webSocketServer = entry。getValue(); try { //向客戶端推送訊息 webSocketServer。getSession()。getBasicRemote()。sendText(“每隔兩秒,向客戶端推送一次資料”); }catch (IOException e){ e。printStackTrace(); } } }else { System。out。println(“WebSocket未連線”); } }}

定時器的實現類就完成了,我們還需要在springmvc。xml中進行配置

springmvc。xml配置:

<!—— 要執行的任務類 ——> <!—— 將需要執行的定時任務注入job中 ——> <!—— 任務類中需要執行的方法 ——> <!—— 上一次未執行完成的,要等待有再執行。 ——> <!—— 基本的定時器,會繫結具體的任務。 ——>

接下來是controller層的程式碼,就一個登入的功能。

package com。socket。web。controller;import com。socket。domain。User;import com。sun。org。apache。bcel。internal。generic。RETURN;import org。springframework。stereotype。Controller;import org。springframework。web。bind。annotation。RequestMapping;import org。springframework。web。bind。annotation。RequestMethod;import javax。servlet。http。HttpServletRequest;import javax。servlet。http。HttpSession;import java。util。UUID;/** * @Author: 清風一陣吹我心 * @ProjectName: socket * @Package: com。socket。web * @ClassName: ChatController * @Description: * @CreateDate: 2018/11/9 11:04 * @Version: 1。0 **/@RequestMapping(“socket”)@Controllerpublic class ChatController { /** * 跳轉到登入頁面 * @return */ @RequestMapping(value = “/login”,method = RequestMethod。GET) public String goLogin(){ return “login”; } /** * 跳轉到聊天頁面 * @param request * @return */ @RequestMapping(value = “/home”,method = RequestMethod。GET) public String goMain(HttpServletRequest request){ HttpSession session = request。getSession(); if (null == session。getAttribute(“USER_SESSION”)){ return “login”; } return “home”; } @RequestMapping(value = “/login”,method = RequestMethod。POST) public String login(User user, HttpServletRequest request){ HttpSession session = request。getSession(); //將使用者放入session session。setAttribute(“USER_SESSION”,user); return “redirect:home”; }}

以上就是登入的程式碼了,基本上就是虛擬碼,只要輸入使用者名稱就可以了,後面的邏輯,大家可以根據自己的業務來實現。

最後就是前臺頁面的設計了,登入,login。jsp

<%@ page contentType=“text/html;charset=UTF-8” language=“java” %> 登入

登入名:

訊息接收頁面,home。jsp

<%@ page contentType=“text/html;charset=UTF-8” language=“java” %> 聊天

基本上,資料推送的功能就完成了,下面附上效果圖。

啟動tomcat。後臺定時器兩秒重新整理一次,判斷是否有websocket連線。

Spring+websocket+quartz實現訊息定時推送

登入頁面:

Spring+websocket+quartz實現訊息定時推送

資料推送頁面:

Spring+websocket+quartz實現訊息定時推送

伺服器定時向客戶端推送資料的功能就完成了,有不明白的可以給博主留言,如果有什麼錯誤,也希望各位朋友指出,謝謝大家。

本文原始碼:

https://github。com/Qingfengchuiwoxin/websocket