SpringBoot整合webSocket

小夥伴們,週一快樂。悄悄告訴你們一個好消息,還有四天就放假了。。驚不驚喜 意不意外javascript

file

今天大Boss找我,小優呀,給你一個需求:用戶在app填寫完信息而後推送給管理員Pc端。 我問Boss就這麼簡單?Boos說就是這麼簡單,你要考慮程序性能,推送的數據準確性。html

忽然腦子復現了兩個思路方式:java

A:app和Pc公用一個數據庫,前段設置一個定時,每一秒輪循查詢。程序員

B:在pc加一個刷新按鈕,一直人工刷新。。洗刷洗刷~web

我就去和大Boss溝通方案,他說兩個都不行。回去從新想方案。spring

file

太難了,想破腦子也想不到啊,算了對於我這種面向百度開發的高級程序員來講。先問問度娘吧。數據庫

file

度娘告訴我好多種方式:apache

1.Ajax輪循瀏覽器

file

優勢:客戶端很容易實現良好的錯誤處理系統和超時管理,實現成本與Ajax輪詢的方式相似。安全

缺點:須要服務器端有特殊的功能來臨時掛起鏈接。當客戶端發起的鏈接較多時,服務器端會長期保持多個鏈接,具備必定的風險。

  1. 基於 Iframe 及 htmlfile 的流(streaming)方式 俗稱長鏈接。

file

優勢: 實時性好(消息延時小);性能好(能支持大量用戶)

缺點: 長期佔用鏈接,喪失了無狀態高併發的特色。

  1. websocket方式

file

優勢:

一、  較少的控制開銷。在鏈接建立後,服務器和客戶端之間交換數據時,用於協議控制的數據包頭部相對較小。在不包含擴展的狀況下,對於服務器到客戶端的內容,此頭部大小隻有2至10字節(和數據包長度有關);對於客戶端到服務器的內容,此頭部還須要加上額外的4字節的掩碼。相對於HTTP請求每次都要攜帶完整的頭部,此項開銷顯著減小了。
     二、更強的實時性。因爲協議是全雙工的,因此服務器能夠隨時主動給客戶端下發數據。相對於HTTP請求須要等待客戶端發起請求服務端才能響應,延遲明顯更少;即便是和Comet等相似的長輪詢比較,其也能在短期內更屢次地傳遞數據。
     三、保持鏈接狀態。與HTTP不一樣的是,Websocket須要先建立鏈接,這就使得其成爲一種有狀態的協議,以後通訊時能夠省略部分狀態信息。而HTTP請求可能須要在每一個請求都攜帶狀態信息(如身份認證等)。
     四、更好的二進制支持。Websocket定義了二進制幀,相對HTTP,能夠更輕鬆地處理二進制內容。
     五、能夠支持擴展。Websocket定義了擴展,用戶能夠擴展協議、實現部分自定義的子協議。如部分瀏覽器支持壓縮等。
     六、更好的壓縮效果。相對於HTTP壓縮,Websocket在適當的擴展支持下,能夠沿用以前內容的上下文,在傳遞相似的數據時,能夠顯著地提升壓縮率。

缺點:不支持低版本的IE瀏覽器

經老夫掐指一算,小優確定會選擇webSocket

file

今天就和你們一塊兒學習SpringBoot整合webSocket 一對一發送消息,一對多發送消息,服務器主動推送消息。

什麼是webSocket?

file

對於上面的小優的業務,我給你們畫一個牛成圖。hiahia~~ 能夠參考看看哈。

file

不知道你們能不能看懂。再給你們寫一個word版本的流程圖:

file

好了,廢話少說如今SpringBoot和WebSocket集成 上代碼:

①、工程目錄:

file

②、pom文件:

pom.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.cnbuilder</groupId>
    <artifactId>websocket</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>websocket</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!--SpringBootWeb包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

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

        <!--訪問接口跳轉templates目錄下的html 必須加-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
    </dependencies>
    <!--打包-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

③、webSocket配置文件:

WebSocketConfig:
package cn.cnbuilder.websocket.config;

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

@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

④、websocket鏈接信息配置:

ProductWebSocket:

package cn.cnbuilder.websocket;

import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @ServerEndpoint 註解是一個類層次的註解,它的功能主要是將目前的類定義成一個websocket服務器端,
 * 註解的值將被用於監聽用戶鏈接的終端訪問URL地址,客戶端能夠經過這個URL來鏈接到WebSocket服務器端
 * @ServerEndpoint 能夠把當前類變成websocket服務類
 */
@ServerEndpoint("/websocket/{userId}")
@Component
public class ProductWebSocket {

    //靜態變量,用來記錄當前在線鏈接數。應該把它設計成線程安全的。
    private static int onlineCount = 0;

    //concurrent包的線程安全Set,用來存放每一個客戶端對應的MyWebSocket對象。若要實現服務端與單一客戶端通訊的話,可使用Map來存放,其中Key能夠爲用戶id
    private static ConcurrentHashMap<String, ProductWebSocket> webSocketSet = new ConcurrentHashMap<String, ProductWebSocket>();

    //與某個客戶端的鏈接會話,須要經過它來給客戶端發送數據
    private Session session;

    //當前發消息的人員編號
    private String userId = "";

    /**
     * 線程安全的統計在線人數
     *
     * @return
     */
    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        ProductWebSocket.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        ProductWebSocket.onlineCount--;
    }

    /**
     * 鏈接創建成功調用的方法
     *
     * @param param   用戶惟一標示
     * @param session 可選的參數。session爲與某個客戶端的鏈接會話,須要經過它來給客戶端發送數據
     */
    @OnOpen
    public void onOpen(@PathParam(value = "userId") String param, Session session) {
        userId = param;//接收到發送消息的人員編號
        this.session = session;
        webSocketSet.put(param, this);//加入線程安全map中
        addOnlineCount();           //在線數加1
        System.out.println("用戶id:" + param + "加入鏈接!當前在線人數爲" + getOnlineCount());
    }

    /**
     * 鏈接關閉調用的方法
     */
    @OnClose
    public void onClose() {
        if (!userId.equals("")) {
            webSocketSet.remove(userId);  //根據用戶id從ma中刪除
            subOnlineCount();           //在線數減1
            System.out.println("用戶id:" + userId + "關閉鏈接!當前在線人數爲" + getOnlineCount());
        }
    }

    /**
     * 收到客戶端消息後調用的方法
     *
     * @param message 客戶端發送過來的消息
     * @param session 可選的參數
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("來自客戶端的消息:" + message);
        //要發送人的用戶uuid
        String sendUserId = message.split(",")[1];
        //發送的信息
        String sendMessage = message.split(",")[0];
        //給指定的人發消息
        sendToUser(sendUserId, sendMessage);

    }

    /**
     * 給指定的人發送消息
     *
     * @param message
     */
    public void sendToUser(String sendUserId, String message) {

        try {
            if (webSocketSet.get(sendUserId) != null) {
                webSocketSet.get(sendUserId).sendMessage(userId + "給我發來消息,消息內容爲--->>" + message);
            } else {

                if (webSocketSet.get(userId) != null) {
                    webSocketSet.get(userId).sendMessage("用戶id:" + sendUserId + "以離線,未收到您的信息!");
                }
                System.out.println("消息接受人:" + sendUserId + "已經離線!");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 管理員發送消息
     *
     * @param message
     */
    public void systemSendToUser(String sendUserId, String message) {

        try {
            if (webSocketSet.get(sendUserId) != null) {
                webSocketSet.get(sendUserId).sendMessage("系統給我發來消息,消息內容爲--->>" + message);
            } else {
                System.out.println("消息接受人:" + sendUserId + "已經離線!");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 給全部人發消息
     *
     * @param message
     */
    private void sendAll(String message) {
        String sendMessage = message.split(",")[0];
        //遍歷HashMap
        for (String key : webSocketSet.keySet()) {
            try {
                //判斷接收用戶是不是當前發消息的用戶
                if (!userId.equals(key)) {
                    webSocketSet.get(key).sendMessage("用戶:" + userId + "發來消息:" + " <br/> " + sendMessage);
                    System.out.println("key = " + key);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 發生錯誤時調用
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("發生錯誤");
        error.printStackTrace();
    }

    /**
     * 發送消息
     *
     * @param message
     * @throws IOException
     */
    public void sendMessage(String message) throws IOException {
        //同步發送
        this.session.getBasicRemote().sendText(message);
        //異步發送
        //this.session.getAsyncRemote().sendText(message);
    }
}

⑤、服務器推送接口:

IndexController:
package cn.cnbuilder.websocket.controller;


import cn.cnbuilder.websocket.ProductWebSocket;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Map;
import java.util.concurrent.TimeUnit;

@Controller
public class IndexController {


    @GetMapping(value = "/")
    @ResponseBody
    public Object index() {

        return "Hello,ALl。This is yuanmayouchuang webSocket demo!";
    }

    @ResponseBody
    @GetMapping("test")
    public String test(String userId, String message) throws Exception {
        if (userId == "" || userId == null) {
            return "發送用戶id不能爲空";
        }
        if (message == "" || message == null) {
            return "發送信息不能爲空";
        }
        new ProductWebSocket().systemSendToUser(userId, message);
        return "發送成功!";
    }

    @RequestMapping(value = "/ws")
    public String ws() {
        return "ws";
    }

    @RequestMapping(value = "/ws1")
    public String ws1() {
        return "ws1";
    }
}

⑥:SpringBoot配置文件

application.yml:
#端口號
server:
  port: 12006

⑦:動態banner:

banner.txt:
 __     ____  ____     _______
 \ \   / /  \/  \ \   / / ____|
  \ \_/ /| \  / |\ \_/ / |     
   \   / | |\/| | \   /| |     
    | |  | |  | |  | | | |____ 
    |_|  |_|  |_|  |_|  \_____|::猿碼優創:websocket SpringBoot Demo 博客地址:www.cnbuilder.cn

⑧:前段代碼:

ws.html:
<!DOCTYPE html>
<html>
<head>
    <title>WebSocket SpringBootDemo</title>
</head>
<body>
<!--userId:發送消息人的編號-->
<div>默認用戶id:xiaoyou001(後期能夠根據業務邏輯替換)</div>

<br/><input id="text" type="text"/>
<input placeholder="請輸入接收人的用戶id" id="sendUserId"></input>
<button onclick="send()">發送消息</button>
<br/>

<button onclick="closeWebSocket()">關閉WebSocket鏈接</button>
<br/>
<div>公衆號:猿碼優創</div>
<br/>
<div id="message"></div>
</body>

<script type="text/javascript">
    var websocket = null;


    var userId = "xiaoyou001"

    //判斷當前瀏覽器是否支持WebSocket
    if ('WebSocket' in window) {
        websocket = new WebSocket("ws://127.0.0.1:12006/websocket/" + userId);
    }
    else {
        alert('當前瀏覽器不支持websocket哦!')
    }

    //鏈接發生錯誤的回調方法
    websocket.onerror = function () {
        setMessageInnerHTML("WebSocket鏈接發生錯誤");
    };

    //鏈接成功創建的回調方法
    websocket.onopen = function () {
        setMessageInnerHTML("WebSocket鏈接成功");
    }

    //接收到消息的回調方法
    websocket.onmessage = function (event) {
        setMessageInnerHTML(event.data);
    }

    //鏈接關閉的回調方法
    websocket.onclose = function () {
        setMessageInnerHTML("WebSocket鏈接關閉");
    }

    //監聽窗口關閉事件,當窗口關閉時,主動去關閉websocket鏈接,防止鏈接還沒斷開就關閉窗口,server端會拋異常。
    window.onbeforeunload = function () {
        closeWebSocket();
    }

    //將消息顯示在網頁上
    function setMessageInnerHTML(sendMessage) {
        document.getElementById('message').innerHTML += sendMessage + '<br/>';
    }

    //關閉WebSocket鏈接
    function closeWebSocket() {
        websocket.close();
    }

    //發送消息
    function send() {
        var message = document.getElementById('text').value;//要發送的消息內容

        if (message == "") {
            alert("發送信息不能爲空!")
            return;
        } //獲取發送人用戶id
        var sendUserId = document.getElementById('sendUserId').value;
        if (sendUserId == "") {
            alert("發送人用戶id不能爲空!")
            return;
        }

        document.getElementById('message').innerHTML += (userId + "給" + sendUserId + "發送消息,消息內容爲---->>" + message + '<br/>');
        message = message + "," + sendUserId//將要發送的信息和內容拼起來,以便於服務端知道消息要發給誰
        websocket.send(message);
    }
</script>
</html>

ws1.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/html">
<head>
    <title>WebSocket SpringBootDemo</title>
</head>
<body>
<!--userId:發送消息人的編號-->
<div>默認用戶id:xiaoyou002(後期能夠根據業務邏輯替換)</div>

<br/><input id="text" placeholder="請輸入要發送的信息" type="text"/>
<input placeholder="請輸入接收人的用戶id" id="sendUserId"></input>
<button onclick="send()">發送消息</button>
<br/>

<button onclick="closeWebSocket()">關閉WebSocket鏈接</button>
<br/>
<div>公衆號:猿碼優創</div>
</br>
<div id="message"></div>
</body>

<script type="text/javascript">
    var websocket = null;


    var userId = "xiaoyou002"

    //判斷當前瀏覽器是否支持WebSocket
    if ('WebSocket' in window) {
        websocket = new WebSocket("ws://127.0.0.1:12006/websocket/" + userId);
    }
    else {
        alert('當前瀏覽器不支持websocket哦!')
    }

    //鏈接發生錯誤的回調方法
    websocket.onerror = function () {
        setMessageInnerHTML("WebSocket鏈接發生錯誤");
    };

    //鏈接成功創建的回調方法
    websocket.onopen = function () {
        setMessageInnerHTML("WebSocket鏈接成功");
    }

    //接收到消息的回調方法
    websocket.onmessage = function (event) {
        setMessageInnerHTML(event.data);
    }

    //鏈接關閉的回調方法
    websocket.onclose = function () {
        setMessageInnerHTML("WebSocket鏈接關閉");
    }

    //監聽窗口關閉事件,當窗口關閉時,主動去關閉websocket鏈接,防止鏈接還沒斷開就關閉窗口,server端會拋異常。
    window.onbeforeunload = function () {
        closeWebSocket();
    }

    //將消息顯示在網頁上
    function setMessageInnerHTML(sendMessage) {
        document.getElementById('message').innerHTML += sendMessage + '<br/>';
    }

    //關閉WebSocket鏈接
    function closeWebSocket() {
        websocket.close();
    }

    //發送消息
    function send() {
        var message = document.getElementById('text').value;//要發送的消息內容

        if (message == "") {
            alert("發送信息不能爲空!")
            return;
        } //獲取發送人用戶id
        var sendUserId = document.getElementById('sendUserId').value;
        if (sendUserId == "") {
            alert("發送人用戶id不能爲空!")
            return;
        }

        document.getElementById('message').innerHTML += ("我給" + sendUserId + "發送消息,消息內容爲---->>" + message + '<br/>');
        message = message + "," + sendUserId//將要發送的信息和內容拼起來,以便於服務端知道消息要發給誰
        websocket.send(message);
    }
</script>
</html>

測試:啓動項目

file

首頁訪問地址:http://127.0.0.1:12006/

file

訪問WebSocket測試頁面1:http://127.0.0.1:12006/ws

file

訪問流程圖:

file

測試一對一發送消息:給另外一個用戶發送信息

換一個瀏覽器,測試兩個 不要用同一瀏覽器,要不會出問題。

訪問WebSocket測試頁面2:http://127.0.0.1:12006/ws1

file

一對一發送消息牛成圖:

file

測試服務器主動向瀏覽器推送消息:http://127.0.0.1:12006/test?userId=xiaoyou002&message=我是小優,聽到請回答。

接口地址:http://127.0.0.1:12006/test

參數:userId:推送人用戶id ws.html :xiaoyou001 ws1.html:xiaoyou002 寫死的,可根據業務動態寫活。

file

一對多推送的話:流程和一對一類似。你們自行研究哈。

/**
     * 給全部人發消息
     *
     * @param message
     */
    private void sendAll(String message) {
        String sendMessage = message.split(",")[0];
        //遍歷HashMap
        for (String key : webSocketSet.keySet()) {
            try {
                //判斷接收用戶是不是當前發消息的用戶
                if (!userId.equals(key)) {
                    webSocketSet.get(key).sendMessage("用戶:" + userId + "發來消息:" + " <br/> " + sendMessage);
                    System.out.println("key = " + key);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

終、、


以上就是websocket一對一發送消息,一對多發送消息,服務器主動推送消息 感受是否是超簡單! 有什麼問題能夠聯繫我哈。


鼓勵做者寫出更好的技術文檔,就請我喝一瓶哇哈哈哈哈哈哈哈。。
微信:

支付寶:


感謝一路支持個人人。。。。。
Love me and hold me
QQ:69673804(16年老號)
EMAIL:69673804@qq.com
友鏈交換
若是有興趣和本博客交換友鏈的話,請按照下面的格式在評論區進行評論,我會盡快添加上你的連接。


網站名稱:KingYiFan’S Blog
網站地址:http://blog.cnbuilder.cn
網站描述:年少是你未醒的夢話,風華是燃燼的彼岸花。
網站Logo/頭像:http://blog.cnbuilder.cn/upload/2018/7/avatar20180720144536200.jpg

相關文章
相關標籤/搜索