<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 8239字,需瀏覽 17分鐘

           ·

          2021-11-06 14:50

          websocket

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

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

          • 全雙工:簡(jiǎn)單地說(shuō),就是可以同時(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)方式。

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

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

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

          一、環(huán)境搭建

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

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


          ????<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文件

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

          springmvc.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>

          到此,環(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)聽(tīng)用戶連接的終端訪問(wèn)URL地址。
          @ServerEndpoint(value?=?"/socket/{ip}")
          @Component
          public?class?WebSocketServer?{

          ????//使用slf4j打日志
          ????private?static?final?Logger?LOGGER?=?LoggerFactory.getLogger(WebSocketServer.class);

          ????//用來(lái)記錄當(dāng)前在線連接數(shù)
          ????private?static?int?onLineCount?=?0;

          ????//用來(lái)存放每個(gè)客戶端對(duì)應(yīng)的WebSocketServer對(duì)象
          ????private?static?ConcurrentHashMap?webSocketMap?=?new?ConcurrentHashMap();

          ????//某個(gè)客戶端的連接會(huì)話,需要通過(guò)它來(lái)給客戶端發(fā)送數(shù)據(jù)
          ????private?Session?session;

          ????//客戶端的ip地址
          ????private?String?ip;

          ????/**
          ?????*?連接建立成功,調(diào)用的方法,與前臺(tái)頁(yè)面的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)頁(yè)面的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)頁(yè)面的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)頁(yè)面的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();
          ????????????//這里使用線程來(lái)控制消息的發(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++;
          ????}

          ????/**
          ?????*?斷開(kāi)連接時(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;
          ????}
          }

          然后寫(xiě)我們的定時(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?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未連接");
          ????????}
          ????}
          }

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

          springmvc.xml配置:


          ????<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>

          接下來(lái)是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)到登錄頁(yè)面
          ?????*?@return
          ?????*/

          ????@RequestMapping(value?=?"/login",method?=?RequestMethod.GET)
          ????public?String?goLogin(){
          ????????return?"login";
          ????}

          ????/**
          ?????*?跳轉(zhuǎn)到聊天頁(yè)面
          ?????*?@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ù)來(lái)實(shí)現(xiàn)。

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

          <%@?page?contentType="text/html;charset=UTF-8"?language="java"?%>
          var="path"?value="${pageContext.request.contextPath}"/>


          ????登錄


          "${path}/socket/login"?method="post">
          ????登錄名:"text"?name="username"/>
          ????"submit"?value="登錄"/>



          消息接收頁(yè)面,home.jsp

          <%@?page?contentType="text/html;charset=UTF-8"?language="java"?%>


          ????聊天
          ????
          ????





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

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

          登錄頁(yè)面:

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

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

          本文源碼:

          https://github.com/Qingfengchuiwoxin/websocket

          來(lái)源:blog.csdn.net/qq_32101993/

          article/details/83994524


          瀏覽 52
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  五月天性爱网 | 日日日日人人人夜夜2022 | 豆花成人www、C0m | 亚洲三级片在线观看视频 | 天天撸在线播放 |