spring boot中websocket的使用以及demo

零、前言

http有http的有點,但也有其不足,即只能從客戶端發起請求給服務端,服務端不能推數據給客戶端。而websocket就不一樣了,是雙工的,不僅可以由客戶端向服務器發送數據,服務端也能主動像客戶端推數據。這其中也就是單工跟雙工的概念。


一、websocket簡介

什麼是WebSocket?

WebSocket協議是基於TCP的一種新的網絡協議。它實現了瀏覽器與服務器全雙工(full-duplex)通信——允許服務器主動發送信息給客戶端。

爲什麼需要 WebSocket?
        初次接觸 WebSocket 的人,都會問同樣的問題:我們已經有了 HTTP 協議,爲什麼還需要另一個協議?它能帶來什麼好處?答案很簡單,因爲 HTTP 協議有一個缺陷:通信只能由客戶端發起,HTTP 協議做不到服務器主動向客戶端推送信息。舉例來說,我們想要查詢當前的排隊情況,只能是頁面輪詢向服務器發出請求,服務器返回查詢結果。輪詢的效率低,非常浪費資源(因爲必須不停連接,或者 HTTP 連接始終打開)。因此WebSocket 就是這樣發明的。

與http進行對比:


二、spring boot中引入websocket的步驟

創建spring boot項目,結構如下:

(1)pom.xml引入依賴 

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

 (2)websocket配置類

package com.example.utils;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * @Description
 * @ClassName WebSocketConfig
 * @Author User
 * @date 2020.05.31 18:45
 */
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

(3)允許跨域配置

由於我在springboot中放了一個websocket客戶端html,訪問該html時用的html與spring boot的端口號不一致,造成了跨域,因此進行了配置。

package com.example.utils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class GlobalCorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("*");
        config.setAllowCredentials(true);
        config.addAllowedMethod("*");
        config.addAllowedHeader("*");

        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
        configSource.registerCorsConfiguration("/**", config);

        return new CorsFilter(configSource);
    }
}

 注意,網絡上有些在config的最後還有一句:

config.addExposedHeader("*");

 這句在我這會導致項目啓動失敗,不知道爲什麼我也沒去研究,我刪掉了他就可以了。

(4)websocket類

package com.example.websocket;


import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Description ws的server,相當於controller
 * @ClassName MyWsServer
 * @Author User
 * @date 2020.05.31 18:24
 */
@ServerEndpoint("/api/v1/websocket/{userId}")
@Component
public class MyWsServer {
    // 引入log4j日誌
    static Logger logger = Logger.getLogger(MyWsServer.class);
    // 靜態變量,用來記錄當前在線連接數
    private static int onlineCount = 0;
    // concurrent包的線程安全Set,用來存放客戶端對象
    private static ConcurrentHashMap<String, MyWsServer> clients = new ConcurrentHashMap<>();
    // 與某個客戶端的連接會話,需要通過它來給客戶端發送數據
    private Session session;
    // 用戶唯一標識符
    private String userId = "";

    /**
     * @return
     * @Description websocket的連接函數
     * @Param {Session} session
     * @Author User
     * @Date 2020.05.31 19:13
     **/
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        this.session = session;
        this.userId = userId;
        logger.info("有新的客戶端連接進來了,客戶端id是: " + session.getId());
        if (clients.containsKey(userId)) {
            clients.remove(userId);
            clients.put(userId, this);
        } else {
            clients.put(userId, this);
            MyWsServer.addOnlineCount();
        }

        logger.info("用戶:" + userId + ", 當前在線人數爲:" + getOnlineCount());

        try {
            sendMessage("連接成功");
        } catch (IOException e) {
            logger.error("用戶:" + userId + ",網絡異常!");
        }

    }

    /**
     * @return
     * @Description 關閉事件處理函數
     * @Param
     * @Author User
     * @Date 2020.06.12 22:15
     **/
    @OnClose
    public void onClose() {
        if (clients.containsKey(userId)) {
            clients.remove(userId);
            subOnlineCount();
        }
        logger.info("用戶退出:" + userId + ",當前在線人數爲:" + getOnlineCount());
    }

    /**
     * @return
     * @Description 接收消息
     * @Param
     * @Author User
     * @Date 2020.06.12 22:29
     **/
    @OnMessage
    public void onMessage(String message, Session session) {
        logger.info("用戶" + userId + "發來消息, 報文:" + message);
    }

    /**
     * @return
     * @Description
     * @Param
     * @Author User
     * @Date 2020.06.12 22:31
     **/
    @OnError
    public void onError(Session session, Throwable error) {
        logger.error("用戶錯誤:" + this.userId + ",原因:" + error.getMessage());
        error.printStackTrace();
    }

    /**
     * @return {null}
     * @Description 向客戶端發送消息
     * @Param {String} message 要發送的消息
     * @Author User
     * @Date 2020.05.31 19:14
     **/
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }

    /**
     * @return
     * @Description 發送自定義消息到指定用戶或者羣發消息
     * @Param
     * @Author User
     * @Date 2020.06.12 22:55
     **/
    public static void sendInfo(String message, String fromUserId, String toUserId) {
        logger.info("推送消息給用戶" + toUserId + ",推送內容:" + message);
        for (MyWsServer client : clients.values()) {
            try {
                //這裏可以設定只推送給這個userId的,爲null則全部推送
                if ("every".equals(toUserId) && !client.userId.equals(fromUserId)) {
                    client.sendMessage("來自" + fromUserId + "的羣發消息:" + message);
                } else if (client.userId.equals(toUserId)) {
                    client.sendMessage("來自" + fromUserId + "發給" + toUserId + "的消息:" + message);
                }
            } catch (IOException e) {
                System.out.println(e.toString());
                continue;
            }
        }
    }

    /**
     * @return {null}
     * @Description 在線客戶端數加一
     * @Param {null}
     * @Author User
     * @Date 2020.05.31 19:12
     **/
    public static synchronized void addOnlineCount() {
        MyWsServer.onlineCount++;
    }

    /**
     * @return {null}
     * @Description 在線客戶端數減一
     * @Param {null}
     * @Author User
     * @Date 2020.05.31 19:12
     **/
    public static synchronized void subOnlineCount() {
        MyWsServer.onlineCount--;
    }

    /**
     * @return
     * @Description 獲取在線連接數
     * @Param
     * @Author User
     * @Date 2020.05.31 19:12
     **/
    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

}

 最重要的一個文件,裏面註釋很詳細。

(5)controller配置demo

package com.example.controller;

import com.example.websocket.MyWsServer;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;

/**
 * @Description
 * @ClassName MyController
 * @Author User
 * @date 2020.06.12 23:05
 */
@RestController
@RequestMapping(("/api/v1"))
public class MyController {
    @GetMapping("/index")
    public ResponseEntity<String> index() {
        return ResponseEntity.ok("<h1>請求成功</h1>");
    }

    @GetMapping("/pushMessage/{fromUserId}/{toUserId}")
    public ResponseEntity<String> pushToClients(@RequestParam("message") String message, @PathVariable("fromUserId") String fromUserId, @PathVariable("toUserId") String toUserId) throws IOException {
        MyWsServer.sendInfo(message, fromUserId, toUserId);
        return ResponseEntity.ok("MSG SEND SUCCESS");
    }
}
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>websocket測試工具</title>
</head>
<script src="./jquery.js"></script>
<script>
    var socket;

    function openSocket() {
        if (typeof (WebSocket) == "undefined") {
            console.log("您的瀏覽器不支持WebSocket");
        } else {
            if ($('#userId').val() === "") {
                alert("請設置您的個性id");
                return;
            }
            console.log("您的瀏覽器支持WebSocket");
            //實現化WebSocket對象,指定要連接的服務器地址與端口  建立連接
            var socketUrl = "ws://127.0.0.1:8000/api/v1/websocket/" + $("#userId").val();
            console.log(socketUrl);
            if (socket != null) {
                socket.close();
                socket = null;
            }
            socket = new WebSocket(socketUrl);
            //打開事件
            socket.onopen = function () {
                // alert("websocket已打開");
                //socket.send("這是來自客戶端的消息" + location.href + new Date());
            };
            //獲得消息事件
            socket.onmessage = function (msg) {
                alert(msg.data);
                //發現消息進入    開始處理前端觸發邏輯
            };
            //關閉事件
            socket.onclose = function () {
                console.log("websocket已關閉");
            };
            //發生了錯誤事件
            socket.onerror = function () {
                console.log("websocket發生了錯誤");
            }
        }
    }

    function disconnectSocket() {
        if (typeof (WebSocket) == "undefined") {
            alert("您的瀏覽器不支持WebSocket");
        }
        if (socket === undefined) {
            alert("您尚未連接服務器");
        } else {
            socket.close();
        }
    }

    function sendMessage() {
        if (typeof (WebSocket) == "undefined") {
            console.log("您的瀏覽器不支持WebSocket");
        } else {
            if ($('#userId').val() === "") {
                alert("請設置您的個性id");
                return;
            }
            if (socket === undefined) {
                alert("您尚未連接服務器");
            } else {
                if (socket.readyState === socket.CLOSED) {
                    alert("您已斷開與服務器的連接");
                } else {
                    var obj = {
                        toUserId: $("#toUserId").val(),
                        message: $('#message').val()
                    }
                    // 推送數據到指定客戶端,不填toUserId則認爲是羣發消息
                    var toWho = "";
                    $('#toUserId').val() ? toWho = $('#toUserId').val() : toWho = "every"
                    var url = "http://127.0.0.1:8000/api/v1/pushMessage/"+$('#userId').val() + "/" + toWho + '?message=' + $('#message').val();
                    // console.log(url);
                    $.ajax(url);
                    // console.log(JSON.stringify(obj));
                    // socket.send(JSON.stringify(obj));
                }
            }


        }
    }
</script>
<body>
<h1 align="center" style="color: red">websocket測試工具</h1>
<div align="left">
    <div>【我的id】:&nbsp<input id="userId" name="userId" type="text" placeholder="設置一個個性的id">&nbsp
        <button id="open" onclick="openSocket()">連接socket</button>
        &nbsp
        <button onclick="disconnectSocket()">斷開socket</button>
    </div>
    <br>
    <div>【發送至】:&nbsp<input id="toUserId" name="toUserId" type="text" placeholder="發送給某個用戶"></div>
    <br>
    <div>【mesgs】:&nbsp<input id="message" name="message" type="text" placeholder="要發送的消息">&nbsp
        <button onclick="sendMessage()">發送消息</button>
    </div>
</div>

</body>

</html>

          只是一個簡單的頁面,但是功能齊全,包括連接斷開,單發消息以及羣發消息

、演示截圖

(1)單發消息
啓動三個客戶端,

客戶端1向客戶端2發送消息:

只有客戶端2收到了消息: 

(2)user1羣發消息

用戶3收到消息:

用戶2收到消息:

四、總結

文章簡單介紹了,springboot中如何使用websocket,demo完善,方便後續參考,擴展

五、demo下載地址

          由於經常在csdn下載東西,積分不夠用,這個demo就上傳到csdn下載了,多多見諒。

          csdn下載地址:https://download.csdn.net/download/qianlixiaomage/12520760

六、結束

再見!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章