日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

Spring+websocket+quartz實(shí)現(xiàn)消息定時(shí)推送

 昵稱10087950 2022-06-16 發(fā)布于江蘇


大家好,我是寶哥!

websocket

簡(jiǎn)單的說,websocket是真正實(shí)現(xiàn)了全雙工通信的服務(wù)器向客戶端推的互聯(lián)網(wǎng)技術(shù)。

全雙工與單工、半雙工的區(qū)別?

  • 全雙工:簡(jiǎn)單地說,就是可以同時(shí)進(jìn)行信號(hào)的雙向傳輸(A->B且B->A),是瞬時(shí)同步的。
  • 單工、半雙工:一個(gè)時(shí)間段內(nèi)只有一個(gè)動(dòng)作發(fā)生。

推送和拉取的區(qū)別?

  • 推:由服務(wù)器主動(dòng)發(fā)消息給客戶端,就像廣播。優(yōu)勢(shì)在于,信息的主動(dòng)性和及時(shí)性。
  • 拉:由客戶端主動(dòng)請(qǐng)求所需要的數(shù)據(jù)。

實(shí)現(xiàn)消息通信的幾種方式?

  • 傳統(tǒng)的http協(xié)議實(shí)現(xiàn)方式:。
  • 傳統(tǒng)的socket技術(shù)。
  • websocket協(xié)議實(shí)現(xiàn)方式。

接下來我們主要講第三種,使用websocket協(xié)議,來實(shí)現(xiàn)服務(wù)端定時(shí)向客戶端推送消息。

  • 開發(fā)環(huán)境:jdk1.8、tomcat7
  • 后臺(tái):springmvc、websocket、quartz
  • 前臺(tái):html5中新增的API
  • 開發(fā)工具:IDEA、maven

實(shí)現(xiàn)步驟

一、環(huán)境搭建

(1)導(dǎo)入相關(guān)約束:

在pom文件中加入需要的約束,spring相關(guān)的約束,請(qǐng)各位自己導(dǎo)入,這里我就不貼出來了。

<!-- 定時(shí)器的包 -->
    <dependency>
      <groupId>org.quartz-scheduler</groupId>
      <artifactId>quartz</artifactId>
      <version>2.3.0</version>
    </dependency>
<!-- 
 spring-support.jar 這個(gè)jar 文件包含支持UI模版(Velocity,F(xiàn)reeMarker,JasperReports),郵件服務(wù),腳本服務(wù)(JRuby),緩存Cache(EHCache),任務(wù)計(jì)劃Scheduling(uartz)方面的類。 
 外部依賴spring-context, (spring-jdbc, Velocity, FreeMarker, JasperReports, BSH, Groovy, JRuby, Quartz, EHCache) 
 -->

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
      <version>5.1.1.RELEASE</version>
    </dependency>
<!-- websocket的包 -->
    <dependency>
      <groupId>javax.websocket</groupId>
      <artifactId>javax.websocket-api</artifactId>
      <version>1.1</version>
      <scope>provided</scope>
    </dependency>
    
<!--
ps:如果使用原始的配置方式,需要導(dǎo)入spring-websocket、spring-messaging的包,我們這里就通過注解實(shí)現(xiàn)
-->

(2)配置xml文件

web.xml中就配置前端控制器,大家自行配置。然后,加載springmvc的配置文件。

springmvc.xml文件中

    <!-- 自動(dòng)將控制器加載到bean -->
    <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>
    <!-- 自動(dòng)注冊(cè) DefaultAnnotationHandlerMapping 與 AnnotationMethodHandlerAdapter 兩個(gè) bean, 解決了 @Controller 注解的使用前提配置 -->
    <mvc:annotation-driven/>
    
    <!-- 使用fastjson 解析json   因?yàn)楸救说捻?xiàng)目中用到了fastjson,所以這段配置大家可以忽略。 -->
    <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-8</value>
                        <value>application/json</value>
                    </list>
                </property>
                <property name="features">
                    <list>
                        <value>WriteMapNullValue</value>
                        <value>QuoteFieldNames</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

到此,環(huán)境就基本搭建完成了。

二、完成后臺(tái)的功能

這里我就直接貼出代碼了,上面有相關(guān)的注釋。

首先,完成websocket的實(shí)現(xiàn)類。

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<String,WebSocketServer> webSocketMap = new ConcurrentHashMap<String, WebSocketServer>();

    //某個(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<Map.Entry<String, WebSocketServer>> entries = webSocketMap.entrySet();
        for (Map.Entry<String, WebSocketServer> 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<String, WebSocketServer> getWebSocketMap() {
        return webSocketMap;
    }

    public static void setWebSocketMap(ConcurrentHashMap<String, WebSocketServer> webSocketMap) {
        WebSocketServer.webSocketMap = webSocketMap;
    }
}

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

這里我使用的是xml注解的方式,創(chuàng)建一個(gè)job類,此類不需要繼承任何類和實(shí)現(xiàn)任何接口。

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<String, WebSocketServer> map = WebSocketServer.getWebSocketMap();
        if (map.size() != 0){
            for (Map.Entry<String, WebSocketServer> entry : map.entrySet()) {
                WebSocketServer webSocketServer = entry.getValue();
                try {
                    //向客戶端推送消息
                    webSocketServer.getSession().getBasicRemote().sendText("每隔兩秒,向客戶端推送一次數(shù)據(jù)");
                }catch (IOException e){
                    e.printStackTrace();
                }
            }
        }else {
            System.out.println("WebSocket未連接");
        }
    }
}

定時(shí)器的實(shí)現(xiàn)類就完成了,我們還需要在springmvc.xml中進(jìn)行配置

springmvc.xml配置:

<!-- 要執(zhí)行的任務(wù)類 -->
    <bean id="testJob" class="com.socket.web.quartz.TestJob"></bean>

    <!-- 將需要執(zhí)行的定時(shí)任務(wù)注入job中 -->
    <bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <property name="targetObject" ref="testJob"/>
        <!-- 任務(wù)類中需要執(zhí)行的方法 -->
        <property name="targetMethod" value="task"></property>
        <!-- 上一次未執(zhí)行完成的,要等待有再執(zhí)行。 -->
        <property name="concurrent" value="false" />
    </bean>

    <!-- 基本的定時(shí)器,會(huì)綁定具體的任務(wù)。 -->
    <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>

接下來是controller層的代碼,就一個(gè)登錄的功能。

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";
    }

}

以上就是登錄的代碼了,基本上就是偽代碼,只要輸入用戶名就可以了,后面的邏輯,大家可以根據(jù)自己的業(yè)務(wù)來實(shí)現(xiàn)。

最后就是前臺(tái)頁面的設(shè)計(jì)了,登錄,login.jsp

<%@ 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>

消息接收頁面,home.jsp

<%@ 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>

基本上,數(shù)據(jù)推送的功能就完成了,下面附上效果圖。

啟動(dòng)tomcat。后臺(tái)定時(shí)器兩秒刷新一次,判斷是否有websocket連接。

圖片

登錄頁面:

圖片

數(shù)據(jù)推送頁面:

圖片

服務(wù)器定時(shí)向客戶端推送數(shù)據(jù)的功能就完成了,有不明白的可以給博主留言,如果有什么錯(cuò)誤,也希望各位朋友指出,謝謝大家。

本文源碼:

https://github.com/Qingfengchuiwoxin/websocket

來源:blog.csdn.net/qq_32101993/

article/details/83994524

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多