Spring+websocket+quartz實(shí)現(xiàn)消息定時(shí)推送
閱讀本文大概需要 8.5 分鐘。
來自:blog.csdn.net/qq_32101993/article/details/83994524/
websocket
全雙工:簡(jiǎn)單地說,就是可以同時(shí)進(jìn)行信號(hào)的雙向傳輸(A->B且B->A),是瞬時(shí)同步的。 單工、半雙工:一個(gè)時(shí)間段內(nèi)只有一個(gè)動(dòng)作發(fā)生。
推:由服務(wù)器主動(dòng)發(fā)消息給客戶端,就像廣播。優(yōu)勢(shì)在于,信息的主動(dòng)性和及時(shí)性。 拉:由客戶端主動(dòng)請(qǐng)求所需要的數(shù)據(jù)。
傳統(tǒng)的http協(xié)議實(shí)現(xiàn)方式:。 傳統(tǒng)的socket技術(shù)。 websocket協(xié)議實(shí)現(xiàn)方式。
開發(fā)環(huán)境:jdk1.8、tomcat7 后臺(tái):springmvc、websocket、quartz 前臺(tái):html5中新增的API 開發(fā)工具:IDEA、maven
推薦下自己做的 Spring Boot 的實(shí)戰(zhàn)項(xiàng)目: https://github.com/YunaiV/ruoyi-vue-pro
實(shí)現(xiàn)步驟
一、環(huán)境搭建
(1)導(dǎo)入相關(guān)約束:
????<dependency>
??????<groupId>org.quartz-schedulergroupId>
??????<artifactId>quartzartifactId>
??????<version>2.3.0version>
????dependency>
????<dependency>
??????<groupId>org.springframeworkgroupId>
??????<artifactId>spring-context-supportartifactId>
??????<version>5.1.1.RELEASEversion>
????dependency>
????<dependency>
??????<groupId>javax.websocketgroupId>
??????<artifactId>javax.websocket-apiartifactId>
??????<version>1.1version>
??????<scope>providedscope>
????dependency>
????
(2)配置xml文件
????
????<context:component-scan?base-package="com.socket.web"?/>
?
????<bean?class="org.springframework.web.servlet.view.InternalResourceViewResolver">
????????<property?name="prefix"?value="/WEB-INF/views/"/>
????????<property?name="suffix"?value=".jsp"/>
????????<property?name="contentType"?value="text/html;?charset=utf-8"/>
????bean>
????
????<mvc:annotation-driven/>
????
????
????<mvc:annotation-driven>
????????<mvc:message-converters?register-defaults="true">
????????????<bean?class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
????????????????<property?name="supportedMediaTypes">
????????????????????<list>
????????????????????????<value>text/html;charset=UTF-8value>
????????????????????????<value>application/jsonvalue>
????????????????????list>
????????????????property>
????????????????<property?name="features">
????????????????????<list>
????????????????????????<value>WriteMapNullValuevalue>
????????????????????????<value>QuoteFieldNamesvalue>
????????????????????list>
????????????????property>
????????????bean>
????????mvc:message-converters>
????mvc:annotation-driven>
二、完成后臺(tái)的功能
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:?清風(fēng)一陣吹我心
?*?@ProjectName:?socket
?*?@Package:?com.socket.web.socket
?*?@ClassName:?WebSocketServer
?*?@Description:
?*?@Version:?1.0
?**/
//ServerEndpoint它的功能主要是將目前的類定義成一個(gè)websocket服務(wù)器端。注解的值將被用于監(jiān)聽用戶連接的終端訪問URL地址。
@ServerEndpoint(value?=?"/socket/{ip}")
@Component
public?class?WebSocketServer?{
????//使用slf4j打日志
????private?static?final?Logger?LOGGER?=?LoggerFactory.getLogger(WebSocketServer.class);
????//用來記錄當(dāng)前在線連接數(shù)
????private?static?int?onLineCount?=?0;
????//用來存放每個(gè)客戶端對(duì)應(yīng)的WebSocketServer對(duì)象
????private?static?ConcurrentHashMap?webSocketMap?=?new?ConcurrentHashMap ();
????//某個(gè)客戶端的連接會(huì)話,需要通過它來給客戶端發(fā)送數(shù)據(jù)
????private?Session?session;
????//客戶端的ip地址
????private?String?ip;
????/**
?????*?連接建立成功,調(diào)用的方法,與前臺(tái)頁面的onOpen相對(duì)應(yīng)
?????*?@param?ip?ip地址
?????*?@param?session?會(huì)話
?????*/
????@OnOpen
????public?void?onOpen(@PathParam("ip")String?ip,Session?session){
????????//根據(jù)業(yè)務(wù),自定義邏輯實(shí)現(xiàn)
????????this.session?=?session;
????????this.ip?=?ip;
????????webSocketMap.put(ip,this);??//將當(dāng)前對(duì)象放入map中
????????addOnLineCount();??//在線人數(shù)加一
????????LOGGER.info("有新的連接加入,ip:{}!當(dāng)前在線人數(shù):{}",ip,getOnLineCount());
????}
????/**
?????*?連接關(guān)閉調(diào)用的方法,與前臺(tái)頁面的onClose相對(duì)應(yīng)
?????*?@param?ip
?????*/
????@OnClose
????public?void?onClose(@PathParam("ip")String?ip){
????????webSocketMap.remove(ip);??//根據(jù)ip(key)移除WebSocketServer對(duì)象
????????subOnLineCount();
????????LOGGER.info("WebSocket關(guān)閉,ip:{},當(dāng)前在線人數(shù):{}",ip,getOnLineCount());
????}
????/**
?????*?當(dāng)服務(wù)器接收到客戶端發(fā)送的消息時(shí)所調(diào)用的方法,與前臺(tái)頁面的onMessage相對(duì)應(yīng)
?????*?@param?message
?????*?@param?session
?????*/
????@OnMessage
????public?void?onMessage(String?message,Session?session){
????????//根據(jù)業(yè)務(wù),自定義邏輯實(shí)現(xiàn)
????????LOGGER.info("收到客戶端的消息:{}",message);
????}
????/**
?????*?發(fā)生錯(cuò)誤時(shí)調(diào)用,與前臺(tái)頁面的onError相對(duì)應(yīng)
?????*?@param?session
?????*?@param?error
?????*/
????@OnError
????public?void?onError(Session?session,Throwable?error){
????????LOGGER.error("WebSocket發(fā)生錯(cuò)誤");
????????error.printStackTrace();
????}
????/**
?????*?給當(dāng)前用戶發(fā)送消息
?????*?@param?message
?????*/
????public?void?sendMessage(String?message){
????????try{
????????????//getBasicRemote()是同步發(fā)送消息,這里我就用這個(gè)了,推薦大家使用getAsyncRemote()異步
????????????this.session.getBasicRemote().sendText(message);
????????}catch?(IOException?e){
????????????e.printStackTrace();
????????????LOGGER.info("發(fā)送數(shù)據(jù)錯(cuò)誤:,ip:{},message:{}",ip,message);
????????}
????}
????/**
?????*?給所有用戶發(fā)消息
?????*?@param?message
?????*/
????public?static?void?sendMessageAll(final?String?message){
????????//使用entrySet而不是用keySet的原因是,entrySet體現(xiàn)了map的映射關(guān)系,遍歷獲取數(shù)據(jù)更快。
????????Set>?entries?=?webSocketMap.entrySet();
????????for?(Map.Entry?entry?:?entries)?{
????????????final?WebSocketServer?webSocketServer?=?entry.getValue();
????????????//這里使用線程來控制消息的發(fā)送,這樣效率更高。
????????????new?Thread(new?Runnable()?{
????????????????public?void?run()?{
????????????????????webSocketServer.sendMessage(message);
????????????????}
????????????}).start();
????????}
????}
????/**
?????*?獲取當(dāng)前的連接數(shù)
?????*?@return
?????*/
????public?static?synchronized?int?getOnLineCount(){
????????return?WebSocketServer.onLineCount;
????}
????/**
?????*?有新的用戶連接時(shí),連接數(shù)自加1
?????*/
????public?static?synchronized?void?addOnLineCount(){
????????WebSocketServer.onLineCount++;
????}
????/**
?????*?斷開連接時(shí),連接數(shù)自減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;
????}
}
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:?清風(fēng)一陣吹我心
?*?@ProjectName:?socket
?*?@Package:?com.socket.web.quartz
?*?@ClassName:?TestJob
?*?@Description:
?*?@Version:?1.0
?**/
public?class?TestJob?{
????public?void?task(){
????????//獲取WebSocketServer對(duì)象的映射。
????????ConcurrentHashMap?map?=?WebSocketServer.getWebSocketMap();
????????if?(map.size()?!=?0){
????????????for?(Map.Entry?entry?:?map.entrySet())?{
????????????????WebSocketServer?webSocketServer?=?entry.getValue();
????????????????try?{
????????????????????//向客戶端推送消息
????????????????????webSocketServer.getSession().getBasicRemote().sendText("每隔兩秒,向客戶端推送一次數(shù)據(jù)");
????????????????}catch?(IOException?e){
????????????????????e.printStackTrace();
????????????????}
????????????}
????????}else?{
????????????System.out.println("WebSocket未連接");
????????}
????}
}
????<bean?id="testJob"?class="com.socket.web.quartz.TestJob">bean>
????
????<bean?id="jobDetail"?class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
????????<property?name="targetObject"?ref="testJob"/>
????????
????????<property?name="targetMethod"?value="task">property>
????????
????????<property?name="concurrent"?value="false"?/>
????bean>
????
????<bean?id="trigger"?class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
????????<property?name="jobDetail"?ref="jobDetail"/>
????????<property?name="startDelay"?value="3000"/>
????????<property?name="repeatInterval"?value="2000"/>
????bean>
????<bean?id="scheduler"?class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
????????<property?name="triggers">
????????????<list>
????????????????<ref?bean="trigger"/>
????????????list>
????????property>
????bean>
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:?清風(fēng)一陣吹我心
?*?@ProjectName:?socket
?*?@Package:?com.socket.web
?*?@ClassName:?ChatController
?*?@Description:
?*?@CreateDate:?2018/11/9?11:04
?*?@Version:?1.0
?**/
@RequestMapping("socket")
@Controller
public?class?ChatController?{
????/**
?????*?跳轉(zhuǎn)到登錄頁面
?????*?@return
?????*/
????@RequestMapping(value?=?"/login",method?=?RequestMethod.GET)
????public?String?goLogin(){
????????return?"login";
????}
????/**
?????*?跳轉(zhuǎn)到聊天頁面
?????*?@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";
????}
}
<%@?page?contentType="text/html;charset=UTF-8"?language="java"?%>
<c:set?var="path"?value="${pageContext.request.contextPath}"/>
<html>
<head>
????<title>登錄title>
head>
<body>
<form?action="${path}/socket/login"?method="post">
????登錄名:<input?type="text"?name="username"/>
????<input?type="submit"?value="登錄"/>
form>
body>
html>
<%@?page?contentType="text/html;charset=UTF-8"?language="java"?%>
<html>
<head>
????<title>聊天title>
????
????<script?type="text/javascript">
????????//判斷當(dāng)前瀏覽器是否支持WebSocket
????????var?webSocket?=?null;
????????if?('WebSocket'?in?window)?{
????????????webSocket?=?new?WebSocket("ws://localhost:9001/socket/127.0.0.1");
????????}
????????else?if?('MozWebSocket'?in?window)?{
????????????webSocket?=?new?MozWebSocket("ws://localhost:9001/socket/127.0.0.1");
????????}
????????else?{
????????????alert('Not?support?webSocket');
????????}
????????//打開socket,握手
????????webSocket.onopen?=?function?(event)?{
????????????alert("websocket已經(jīng)連接");
????????}
????????//接收推送的消息
????????webSocket.onmessage?=?function?(event)?{
????????????console.info(event);
????????????alert(event.data);
????????}
????????//錯(cuò)誤時(shí)
????????webSocket.onerror?=?function?(event)?{
????????????console.info("發(fā)生錯(cuò)誤");
????????????alert("websocket發(fā)生錯(cuò)誤"?+?event);
????????}
????????//關(guān)閉連接
????????webSocket.onclose?=?function?()?{
????????????console.info("關(guān)閉連接");
????????}
????????//監(jiān)聽窗口關(guān)閉
????????window.onbeforeunload?=?function?(event)?{
????????????webSocket.close();
????????}
????script>
head>
<body>
body>
html>



推薦閱讀:
公司規(guī)定所有接口都用 POST請(qǐng)求,這是為什么?
最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊(cè)》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。
朕已閱?

