溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

websocket怎么在springboot中使用

發(fā)布時(shí)間:2021-01-25 15:57:31 來(lái)源:億速云 閱讀:209 作者:Leah 欄目:開(kāi)發(fā)技術(shù)

本篇文章為大家展示了websocket怎么在springboot中使用,內(nèi)容簡(jiǎn)明扼要并且容易理解,絕對(duì)能使你眼前一亮,通過(guò)這篇文章的詳細(xì)介紹希望你能有所收獲。

一、websocket與http 

http協(xié)議是用在應(yīng)用層的協(xié)議,他是基于tcp協(xié)議的,http協(xié)議建立鏈接也必須要有三次握手才能發(fā)送信息。http鏈接分為短鏈接,長(zhǎng)鏈接,短鏈接是每次請(qǐng)求都要三次握手才能發(fā)送自己的信息。即每一個(gè)request對(duì)應(yīng)一個(gè)response。長(zhǎng)鏈接是在一定的期限內(nèi)保持鏈接。保持TCP連接不斷開(kāi)。客戶(hù)端與服務(wù)器通信,必須要有客戶(hù)端發(fā)起然后服務(wù)器返回結(jié)果??蛻?hù)端是主動(dòng)的,服務(wù)器是被動(dòng)的。 
WebSocket是HTML5中的協(xié)議, 他是為了解決客戶(hù)端發(fā)起多個(gè)http請(qǐng)求到服務(wù)器資源瀏覽器必須要經(jīng)過(guò)長(zhǎng)時(shí)間的輪訓(xùn)問(wèn)題而生的,他實(shí)現(xiàn)了多路復(fù)用,他是全雙工通信。在webSocket協(xié)議下客服端和瀏覽器可以同時(shí)發(fā)送信息。

二、HTTP的長(zhǎng)連接與websocket的持久連接

HTTP1.1的連接默認(rèn)使用長(zhǎng)連接(persistent connection),

即在一定的期限內(nèi)保持鏈接,客戶(hù)端會(huì)需要在短時(shí)間內(nèi)向服務(wù)端請(qǐng)求大量的資源,保持TCP連接不斷開(kāi)。客戶(hù)端與服務(wù)器通信,必須要有客戶(hù)端發(fā)起然后服務(wù)器返回結(jié)果??蛻?hù)端是主動(dòng)的,服務(wù)器是被動(dòng)的。

  在一個(gè)TCP連接上可以傳輸多個(gè)Request/Response消息對(duì),所以本質(zhì)上還是Request/Response消息對(duì),仍然會(huì)造成資源的浪費(fèi)、實(shí)時(shí)性不強(qiáng)等問(wèn)題。

如果不是持續(xù)連接,即短連接,那么每個(gè)資源都要建立一個(gè)新的連接,HTTP底層使用的是TCP,那么每次都要使用三次握手建立TCP連接,即每一個(gè)request對(duì)應(yīng)一個(gè)response,將造成極大的資源浪費(fèi)。

  長(zhǎng)輪詢(xún),即客戶(hù)端發(fā)送一個(gè)超時(shí)時(shí)間很長(zhǎng)的Request,服務(wù)器hold住這個(gè)連接,在有新數(shù)據(jù)到達(dá)時(shí)返回Response

websocket的持久連接  只需建立一次Request/Response消息對(duì),之后都是TCP連接,避免了需要多次建立Request/Response消息對(duì)而產(chǎn)生的冗余頭部信息。

Websocket只需要一次HTTP握手,所以說(shuō)整個(gè)通訊過(guò)程是建立在一次連接/狀態(tài)中,而且websocket可以實(shí)現(xiàn)服務(wù)端主動(dòng)聯(lián)系客戶(hù)端,這是http做不到的。

springboot集成websocket的不同實(shí)現(xiàn)方式:

pom添加依賴(lài)

 <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>

 因涉及到j(luò)s連接服務(wù)端,所以也寫(xiě)了對(duì)應(yīng)的html,這里集成下thymeleaf模板,前后分離的項(xiàng)目這一塊全都是前端做的

 <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>

配置文件:

server:
 port: 8885
 
#添加Thymeleaf配置
thymeleaf:
 cache: false
 prefix: classpath:/templates/
 suffix: .html
 mode: HTML5
 encoding: UTF-8
 content-type: text/html

1:自定義WebSocketServer,使用底層的websocket方法,提供對(duì)應(yīng)的onOpen、onClose、onMessage、onError方法

1.1:添加webSocketConfig配置類(lèi)

/**
 * 開(kāi)啟WebSocket支持
 * Created by huiyunfei on 2019/5/31.
 */
@Configuration
public class WebSocketConfig {
  @Bean
  public ServerEndpointExporter serverEndpointExporter() {
    return new ServerEndpointExporter();
  }
}

1.2:添加webSocketServer服務(wù)端類(lèi)

package com.example.admin.web;
 
/**
 * Created by huiyunfei on 2019/5/31.
 */
 
@ServerEndpoint("/websocket/{sid}")
@Component
@Slf4j
public class WebSocketServer {
  //靜態(tài)變量,用來(lái)記錄當(dāng)前在線連接數(shù)。應(yīng)該把它設(shè)計(jì)成線程安全的。
  private static int onlineCount = 0;
  //concurrent包的線程安全Set,用來(lái)存放每個(gè)客戶(hù)端對(duì)應(yīng)的MyWebSocket對(duì)象。
  private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();
 
  //與某個(gè)客戶(hù)端的連接會(huì)話(huà),需要通過(guò)它來(lái)給客戶(hù)端發(fā)送數(shù)據(jù)
  private Session session;
 
  //接收sid
  private String sid="";
 
 
  */
/**
   * 連接建立成功調(diào)用的方法*//*
  @OnOpen
  public void onOpen(Session session, @PathParam("sid") String sid) {
    this.session = session;
    webSocketSet.add(this);   //加入set中
    addOnlineCount();      //在線數(shù)加1
    log.info("有新窗口開(kāi)始監(jiān)聽(tīng):"+sid+",當(dāng)前在線人數(shù)為" + getOnlineCount());
    this.sid=sid;
    try {
      sendMessage("連接成功");
    } catch (IOException e) {
      log.error("websocket IO異常");
    }
  }
  */
/**
   * 連接關(guān)閉調(diào)用的方法
   *//*
  @OnClose
  public void onClose() {
    webSocketSet.remove(this); //從set中刪除
    subOnlineCount();      //在線數(shù)減1
    log.info("有一連接關(guān)閉!當(dāng)前在線人數(shù)為" + getOnlineCount());
  }
  */
/**
   * 收到客戶(hù)端消息后調(diào)用的方法
   *
   * @param message 客戶(hù)端發(fā)送過(guò)來(lái)的消息*//*
  @OnMessage
  public void onMessage(String message, Session session) {
    log.info("收到來(lái)自窗口"+sid+"的信息:"+message);
    //群發(fā)消息
    for (WebSocketServer item : webSocketSet) {
      try {
        item.sendMessage(message);
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
  */
/**
   *
   * @param session
   * @param error
   *//*
  @OnError
  public void onError(Session session, Throwable error) {
    log.error("發(fā)生錯(cuò)誤");
    error.printStackTrace();
  }
  */
/**
   * 實(shí)現(xiàn)服務(wù)器主動(dòng)推送
   *//*
  public void sendMessage(String message) throws IOException {
    this.session.getBasicRemote().sendText(message);
  }
  */
/**
   * 群發(fā)自定義消息
   * *//*
  public static void sendInfo(String message,@PathParam("sid") String sid) throws IOException {
    log.info("推送消息到窗口"+sid+",推送內(nèi)容:"+message);
    for (WebSocketServer item : webSocketSet) {
      try {
        //這里可以設(shè)定只推送給這個(gè)sid的,為null則全部推送
        if(sid==null) {
          item.sendMessage(message);
        }else if(item.sid.equals(sid)){
          item.sendMessage(message);
        }
      } catch (IOException e) {
        continue;
      }
    }
  }
  public static synchronized int getOnlineCount() {
    return onlineCount;
  }
  public static synchronized void addOnlineCount() {
    WebSocketServer.onlineCount++;
  }
  public static synchronized void subOnlineCount() {
    WebSocketServer.onlineCount--;
  }
  public static CopyOnWriteArraySet<WebSocketServer> getWebSocketSet() {
    return webSocketSet;
  }
}

1.3:添加對(duì)應(yīng)的controller

@Controller
@RequestMapping("/system")
public class SystemController {
  //頁(yè)面請(qǐng)求
  @GetMapping("/index/{userId}")
  public ModelAndView socket(@PathVariable String userId) {
    ModelAndView mav=new ModelAndView("/socket1");
    mav.addObject("userId", userId);
    return mav;
  }
  //推送數(shù)據(jù)接口
  @ResponseBody
  @RequestMapping("/socket/push/{cid}")
  public Map pushToWeb(@PathVariable String cid, String message) {
    Map result = new HashMap();
    try {
      WebSocketServer.sendInfo(message,cid);
      result.put("code", 200);
      result.put("msg", "success");
    } catch (IOException e) {
      e.printStackTrace();
    }
    return result;
  }

 1.4:提供socket1.html頁(yè)面

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8"></meta>
  <title>Title</title>
</head>
<body>
hello world!
 
</body>
<script>
  var socket;
  if(typeof(WebSocket) == "undefined") {
    console.log("您的瀏覽器不支持WebSocket");
  }else{
    console.log("您的瀏覽器支持WebSocket");
    //實(shí)現(xiàn)化WebSocket對(duì)象,指定要連接的服務(wù)器地址與端口 建立連接
    //等同于
    index = new WebSocket("ws://localhost:8885/websocket/2");
    //socket = new WebSocket("${basePath}websocket/${cid}".replace("http","ws"));
    //打開(kāi)事件
    index.onopen = function() {
      console.log("Socket 已打開(kāi)");
      //socket.send("這是來(lái)自客戶(hù)端的消息" + location.href + new Date());
    };
    //獲得消息事件
    index.onmessage = function(msg) {
      console.log(msg.data);
      //發(fā)現(xiàn)消息進(jìn)入  開(kāi)始處理前端觸發(fā)邏輯
    };
    //關(guān)閉事件
    index.onclose = function() {
      console.log("Socket已關(guān)閉");
    };
    //發(fā)生了錯(cuò)誤事件
    index.onerror = function() {
      alert("Socket發(fā)生了錯(cuò)誤");
      //此時(shí)可以嘗試刷新頁(yè)面
    }
    //離開(kāi)頁(yè)面時(shí),關(guān)閉socket
    //jquery1.8中已經(jīng)被廢棄,3.0中已經(jīng)移除
    // $(window).unload(function(){
    //   socket.close();
    //});
  }
</script>
</html>

 總結(jié):

瀏覽器debug訪問(wèn) localhost:8885/system/index/1跳轉(zhuǎn)到socket1.html,js自動(dòng)連接server并傳遞cid到服務(wù)端,服務(wù)端對(duì)應(yīng)的推送消息到客戶(hù)端頁(yè)面(cid區(qū)分不同的請(qǐng)求,server里提供的有群發(fā)消息方法)

2.1:基于STOMP協(xié)議的WebSocket

使用STOMP的好處在于,它完全就是一種消息隊(duì)列模式,你可以使用生產(chǎn)者與消費(fèi)者的思想來(lái)認(rèn)識(shí)它,發(fā)送消息的是生產(chǎn)者,接收消息的是消費(fèi)者。而消費(fèi)者可以通過(guò)訂閱不同的destination,來(lái)獲得不同的推送消息,不需要開(kāi)發(fā)人員去管理這些訂閱與推送目的地之前的關(guān)系,spring官網(wǎng)就有一個(gè)簡(jiǎn)單的spring-boot的stomp-demo,如果是基于springboot,大家可以根據(jù)spring上面的教程試著去寫(xiě)一個(gè)簡(jiǎn)單的demo。

提供websocketConfig配置類(lèi)

/**
 * @Description:
registerStompEndpoints(StompEndpointRegistry registry)
configureMessageBroker(MessageBrokerRegistry config)
這個(gè)方法的作用是定義消息代理,通俗一點(diǎn)講就是設(shè)置消息連接請(qǐng)求的各種規(guī)范信息。
registry.enableSimpleBroker("/topic")表示客戶(hù)端訂閱地址的前綴信息,也就是客戶(hù)端接收服務(wù)端消息的地址的前綴信息(比較繞,看完整個(gè)例子,大概就能明白了)
registry.setApplicationDestinationPrefixes("/app")指服務(wù)端接收地址的前綴,意思就是說(shuō)客戶(hù)端給服務(wù)端發(fā)消息的地址的前綴
 * @Author:hui.yunfei@qq.com
 * @Date: 2019/5/31
 */
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
 
//  這個(gè)方法的作用是添加一個(gè)服務(wù)端點(diǎn),來(lái)接收客戶(hù)端的連接。
//  registry.addEndpoint("/socket")表示添加了一個(gè)/socket端點(diǎn),客戶(hù)端就可以通過(guò)這個(gè)端點(diǎn)來(lái)進(jìn)行連接。
//  withSockJS()的作用是開(kāi)啟SockJS支持,
  @Override
  public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/socket").withSockJS();
  }
  @Override
  public void configureMessageBroker(MessageBrokerRegistry registry) {
    //表示客戶(hù)端訂閱地址的前綴信息,也就是客戶(hù)端接收服務(wù)端消息的地址的前綴信息
    registry.enableSimpleBroker("/topic");
    //指服務(wù)端接收地址的前綴,意思就是說(shuō)客戶(hù)端給服務(wù)端發(fā)消息的地址的前綴
    registry.setApplicationDestinationPrefixes("/app");
  }
}

 2.2:controller提供對(duì)應(yīng)請(qǐng)求的接口

//頁(yè)面請(qǐng)求
  @GetMapping("/socket2")
  public ModelAndView socket2() {//@PathVariable String userId
    ModelAndView mav=new ModelAndView("html/socket2");
    //mav.addObject("userId", userId);
    return mav;
  }
 
  /**
   * @Description:這個(gè)方法是接收客戶(hù)端發(fā)送功公告的WebSocket請(qǐng)求,使用的是@MessageMapping
   * @Author:hui.yunfei@qq.com
   * @Date: 2019/5/31
   */
  @MessageMapping("/change-notice")//客戶(hù)端訪問(wèn)服務(wù)端的時(shí)候config中配置的服務(wù)端接收前綴也要加上 例:/app/change-notice
  @SendTo("/topic/notice")//config中配置的訂閱前綴記得要加上
  public CustomMessage greeting(CustomMessage message){
    System.out.println("服務(wù)端接收到消息:"+message.toString());
    //我們使用這個(gè)方法進(jìn)行消息的轉(zhuǎn)發(fā)發(fā)送!
    //this.simpMessagingTemplate.convertAndSend("/topic/notice", value);(可以使用定時(shí)器定時(shí)發(fā)送消息到客戶(hù)端)
    //    @Scheduled(fixedDelay = 1000L)
    //    public void time() {
    //      messagingTemplate.convertAndSend("/system/time", new Date().toString());
    //    }
    //也可以使用sendTo發(fā)送
    return message;
  }

2.3:提供socket2.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8" />
  <title>Spring Boot+WebSocket+廣播式</title>
 
</head>
<body onload="disconnect()">
<noscript><h3 >貌似你的瀏覽器不支持websocket</h3></noscript>
<div>
  <div>
    <button id="connect" onclick="connect();">連接</button>
    <button id="disconnect" disabled="disabled" onclick="disconnect();">斷開(kāi)連接</button>
  </div>
  <div id="conversationDiv">
    <label>輸入你的名字</label><input type="text" id="name" />
    <button id="sendName" onclick="sendName();">發(fā)送</button>
    <p id="response"></p>
  </div>
</div>
<script th:src="@{/js/sockjs.min.js}"></script>
<script th:src="@{/js/stomp.min.js}"></script>
<script th:src="@{/js/jquery-3.2.1.min.js}"></script>
<script type="text/javascript">
  var stompClient = null;
 
  function setConnected(connected) {
    document.getElementById('connect').disabled = connected;
    document.getElementById('disconnect').disabled = !connected;
    document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden';
    $('#response').html();
  }
 
  function connect() {
    var socket = new SockJS('/socket'); //1
    stompClient = Stomp.over(socket);//2
    stompClient.connect({}, function(frame) {//3
      setConnected(true);
      console.log('開(kāi)始進(jìn)行連接Connected: ' + frame);
      stompClient.subscribe('/topic/notice', function(respnose){ //4
        showResponse(JSON.parse(respnose.body).responseMessage);
      });
    });
  }
 
 
  function disconnect() {
    if (stompClient != null) {
      stompClient.disconnect();
    }
    setConnected(false);
    console.log("Disconnected");
  }
 
  function sendName() {
    var name = $('#name').val();
    stompClient.send("/app/change-notice", {}, JSON.stringify({ 'name': name }));//5
  }
 
  function showResponse(message) {
    var response = $("#response");
    response.html(message);
  }
</script>
</body>
</html>

2.4:對(duì)應(yīng)的js引用可以去網(wǎng)上下載

2.5:瀏覽器debug訪問(wèn)localhost:8885/system/socket2,點(diǎn)擊連接連接到服務(wù)器,數(shù)據(jù)內(nèi)容可以推送到服務(wù)器以及服務(wù)器消息回推。

2.6:實(shí)現(xiàn)前端和服務(wù)端的輪訓(xùn)可以頁(yè)面Ajax輪訓(xùn)也可以后端添加定時(shí)器

@Component
@EnableScheduling
public class TimeTask {
  private static Logger logger = LoggerFactory.getLogger(TimeTask.class);
 
  @Scheduled(cron = "0/20 * * * * ?")
  public void test(){
    System.err.println("*********  定時(shí)任務(wù)執(zhí)行  **************");
    CopyOnWriteArraySet<WebSocketServer> webSocketSet =
        WebSocketServer.getWebSocketSet();
    int i = 0 ;
    webSocketSet.forEach(c->{
      try {
        c.sendMessage(" 定時(shí)發(fā)送 " + new Date().toLocaleString());
      } catch (IOException e) {
        e.printStackTrace();
      }
    });
 
    System.err.println("/n 定時(shí)任務(wù)完成.......");
  }
}

上述內(nèi)容就是websocket怎么在springboot中使用,你們學(xué)到知識(shí)或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識(shí)儲(chǔ)備,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI