Spring Boot+Socket實現與html頁面的長連接,客戶端給伺服器端發消息,伺服器給客戶端輪詢發送消息,附案例源碼

来源:https://www.cnblogs.com/chenyanbin/archive/2020/07/26/13380769.html
-Advertisement-
Play Games

功能介紹 客戶端給所有線上用戶發送消息 客戶端給指定線上用戶發送消息 伺服器給客戶端發送消息(輪詢方式) 註意:socket只是實現一些簡單的功能,具體的還需根據自身情況,代碼稍微改造下 項目搭建 項目結構圖 pom.xml <?xml version="1.0" encoding="UTF-8"? ...


功能介紹

  1. 客戶端給所有線上用戶發送消息
  2. 客戶端給指定線上用戶發送消息
  3. 伺服器給客戶端發送消息(輪詢方式)

註意:socket只是實現一些簡單的功能,具體的還需根據自身情況,代碼稍微改造下

項目搭建

項目結構圖

 

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 https://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.3.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.cyb</groupId>
    <artifactId>socket_test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>socket_test</name>
    <description>Demo project for Spring Boot</description>

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

    <dependencies>
        <!-- springboot websocket -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <!--guava依賴-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>18.0</version>
        </dependency>
        <!--fastjson依賴-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.46</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

appliccation.properties

 

SocketTestApplication.java(Spring Boot啟動類)

 

WebSocketStompConfig.java

 

package com.cyb.socket.websocket;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketStompConfig {
    //這個bean的註冊,用於掃描帶有@ServerEndpoint的註解成為websocket  ,如果你使用外置的tomcat就不需要該配置文件
    @Bean
    public ServerEndpointExporter serverEndpointExporter()
    {
        return new ServerEndpointExporter();
    }
}

WebSocket.java(Socket核心類)

 

package com.cyb.socket.websocket;

import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * @Author:陳彥斌
 * @Description:Socket核心類
 * @Date: 2020-07-26
 */

@Component
@ServerEndpoint(value = "/connectWebSocket/{userId}")
public class WebSocket {

    private Logger logger = LoggerFactory.getLogger(this.getClass());
    /**
     * 線上人數
     */
    public static int onlineNumber = 0;
    /**
     * 以用戶的姓名為key,WebSocket為對象保存起來
     */
    private static Map<String, WebSocket> clients = new ConcurrentHashMap<String, WebSocket>();
    /**
     * 會話
     */
    private Session session;
    /**
     * 用戶名稱
     */
    private String userId;

    /**
     * 建立連接
     *
     * @param session
     */
    @OnOpen
    public void onOpen(@PathParam("userId") String userId, Session session) {
        onlineNumber++;
        System.out.println("現在來連接的客戶id:" + session.getId() + "用戶名:" + userId);
        //logger.info("現在來連接的客戶id:"+session.getId()+"用戶名:"+userId);
        this.userId = userId;
        this.session = session;
        System.out.println("有新連接加入! 當前線上人數" + onlineNumber);
        //  logger.info("有新連接加入! 當前線上人數" + onlineNumber);
        try {
            //messageType 1代表上線 2代表下線 3代表線上名單 4代表普通消息
            //先給所有人發送通知,說我上線了
            Map<String, Object> map1 = Maps.newHashMap();
            map1.put("messageType", 1);
            map1.put("userId", userId);
            sendMessageAll(JSON.toJSONString(map1), userId);

            //把自己的信息加入到map當中去
            clients.put(userId, this);
            System.out.println("有連接關閉! 當前線上人數" + onlineNumber);
            //logger.info("有連接關閉! 當前線上人數" + clients.size());
            //給自己發一條消息:告訴自己現在都有誰線上
            Map<String, Object> map2 = Maps.newHashMap();
            map2.put("messageType", 3);
            //移除掉自己
            Set<String> set = clients.keySet();
            map2.put("onlineUsers", set);
            sendMessageTo(JSON.toJSONString(map2), userId);
        } catch (IOException e) {
            System.out.println(userId + "上線的時候通知所有人發生了錯誤");
            //logger.info(userId+"上線的時候通知所有人發生了錯誤");
        }
    }

    @OnError
    public void onError(Session session, Throwable error) {
        //logger.info("服務端發生了錯誤"+error.getMessage());
        //error.printStackTrace();
        System.out.println("服務端發生了錯誤:" + error.getMessage());
    }

    /**
     * 連接關閉
     */
    @OnClose
    public void onClose() {
        onlineNumber--;
        //webSockets.remove(this);
        clients.remove(userId);
        try {
            //messageType 1代表上線 2代表下線 3代表線上名單  4代表普通消息
            Map<String, Object> map1 = Maps.newHashMap();
            map1.put("messageType", 2);
            map1.put("onlineUsers", clients.keySet());
            map1.put("userId", userId);
            sendMessageAll(JSON.toJSONString(map1), userId);
        } catch (IOException e) {
            System.out.println(userId + "下線的時候通知所有人發生了錯誤");
            //logger.info(userId+"下線的時候通知所有人發生了錯誤");
        }
        //logger.info("有連接關閉! 當前線上人數" + onlineNumber);
        //logger.info("有連接關閉! 當前線上人數" + clients.size());
        System.out.println("有連接關閉! 當前線上人數" + onlineNumber);
    }

    /**
     * 收到客戶端的消息
     *
     * @param message 消息
     * @param session 會話
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        try {
            //logger.info("來自客戶端消息:" + message+"客戶端的id是:"+session.getId());
            System.out.println("來自客戶端消息:" + message + " | 客戶端的id是:" + session.getId());
            JSONObject jsonObject = JSON.parseObject(message);
            String textMessage = jsonObject.getString("message");
            String fromuserId = jsonObject.getString("userId");
            String touserId = jsonObject.getString("to");
            //如果不是發給所有,那麼就發給某一個人
            //messageType 1代表上線 2代表下線 3代表線上名單  4代表普通消息
            Map<String, Object> map1 = Maps.newHashMap();
            map1.put("messageType", 4);
            map1.put("textMessage", textMessage);
            map1.put("fromuserId", fromuserId);
            if (touserId.equals("All")) {
                map1.put("touserId", "所有人");
                sendMessageAll(JSON.toJSONString(map1), fromuserId);
            } else {
                map1.put("touserId", touserId);
                System.out.println("開始推送消息給" + touserId);
                sendMessageTo(JSON.toJSONString(map1), touserId);
            }
        } catch (Exception e) {
            e.printStackTrace();
            //logger.info("發生了錯誤了");
        }

    }

    /**
     * 給指定的用戶發送消息
     *
     * @param message
     * @param TouserId
     * @throws IOException
     */
    public void sendMessageTo(String message, String TouserId) throws IOException {
        for (WebSocket item : clients.values()) {
            System.out.println("給指定的線上用戶發送消息,線上人員名單:【" + item.userId.toString() + "】發送消息:" + message);
            if (item.userId.equals(TouserId)) {
                item.session.getAsyncRemote().sendText(message);
                break;
            }
        }
    }

    /**
     * 給所有用戶發送消息
     *
     * @param message    數據
     * @param FromuserId
     * @throws IOException
     */
    public void sendMessageAll(String message, String FromuserId) throws IOException {
        for (WebSocket item : clients.values()) {
            System.out.println("給所有線上用戶發送給消息,線上人員名單:【" + item.userId.toString() + "】發送消息:" + message);
            item.session.getAsyncRemote().sendText(message);
        }
    }

    /**
     * 給所有線上用戶發送消息
     *
     * @param message 數據
     * @throws IOException
     */
    public void sendMessageAll(String message) throws IOException {
        for (WebSocket item : clients.values()) {
            System.out.println("伺服器給所有線上用戶發送消息,當前線上人員為【" + item.userId.toString() + "】發送消息:" + message);
            item.session.getAsyncRemote().sendText(message);
        }
    }

    /**
     * 獲取線上用戶數
     *
     * @return
     */
    public static synchronized int getOnlineCount() {
        return onlineNumber;
    }
}

TestController.java(前端控制器)

package com.cyb.socket.websocket;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;

@Controller
@RequestMapping("testMethod")
public class TestController {
    @Autowired
    private WebSocket webSocket;

    /**
     * 給指定的線上用戶發送消息
     * @param userId
     * @param msg
     * @return
     * @throws IOException
     */
    @ResponseBody
    @GetMapping("/sendTo")
    public String sendTo(@RequestParam("userId") String userId,@RequestParam("msg") String msg) throws IOException {
        webSocket.sendMessageTo(msg,userId);
        return "推送成功";
    }

    /**
     * 給所有線上用戶發送消息
     * @param msg
     * @return
     * @throws IOException
     * @throws IOException
     */
    @ResponseBody
    @PostMapping("/sendAll")
    public String sendAll(@RequestBody String msg) throws IOException, IOException {
        webSocket.sendMessageAll(msg);
        return "推送成功";
    }
}

SocketTask.java(輪詢調度往客戶端推送消息)

 

package com.cyb.socket.schedule;

import com.cyb.socket.websocket.WebSocket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

@Component
public class SocketTask {
    @Autowired
    private WebSocket webSocket;
    private SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS" );
    //5秒輪詢一次
    @Scheduled(fixedRate = 5000)
    public void sendClientData() throws IOException {
        String msg="{\"message\":\"你好\",\"userId\":\"002\",\"to\":\"All\"}";
        webSocket.sendMessageAll(msg);
        System.out.println("消息推送時間:"+ sdf.format(new Date()));
    }
}

測試網頁

index.html

<!DOCTYPE HTML>
<html>
<head>
    <title>Test My WebSocket</title>
</head>
 
 
<body>
TestWebSocket
<input  id="text" type="text" style="width:500px"/>
<button onclick="send()">SEND MESSAGE</button>
<button onclick="closeWebSocket()">CLOSE</button>
<div id="message"></div>
</body>
 
<script type="text/javascript">
    var websocket = null;
 
 
    //判斷當前瀏覽器是否支持WebSocket
    if('WebSocket' in window){
        //連接WebSocket節點
        websocket = new WebSocket("ws://localhost:8083/connectWebSocket/001");
    }
    else{
        alert('Not support websocket')
    }
 
 
    //連接發生錯誤的回調方法
    websocket.onerror = function(){
        setMessageInnerHTML("error");
    };
 
 
    //連接成功建立的回調方法
    websocket.onopen = function(event){
        setMessageInnerHTML("open");
    }
 
 
    //接收到消息的回調方法
    websocket.onmessage = function(event){
        setMessageInnerHTML(event.data);
    }
 
 
    //連接關閉的回調方法
    websocket.onclose = function(){
        setMessageInnerHTML("close");
    }
 
 
    //監聽視窗關閉事件,當視窗關閉時,主動去關閉websocket連接,防止連接還沒斷開就關閉視窗,server端會拋異常。
    window.onbeforeunload = function(){
        websocket.close();
    }
 
 
    //將消息顯示在網頁上
    function setMessageInnerHTML(innerHTML){
        document.getElementById('message').innerHTML += innerHTML + '<br/>';
    }
 
 
    //關閉連接
    function closeWebSocket(){
        websocket.close();
    }
 
 
    //發送消息
    function send(){
        var message = document.getElementById('text').value;
        websocket.send(message);
    }
</script>
</html>

index2.html

<!DOCTYPE HTML>
<html>
<head>
    <title>Test My WebSocket</title>
</head>
 
 
<body>
TestWebSocket
<input  id="text" type="text" style="width:500px" />
<button onclick="send()">SEND MESSAGE</button>
<button onclick="closeWebSocket()">CLOSE</button>
<div id="message"></div>
</body>
 
<script type="text/javascript">
    var websocket = null;
 
 
    //判斷當前瀏覽器是否支持WebSocket
    if('WebSocket' in window){
        //連接WebSocket節點
        websocket = new WebSocket("ws://localhost:8083/connectWebSocket/002");
    }
    else{
        alert('Not support websocket')
    }
 
 
    //連接發生錯誤的回調方法
    websocket.onerror = function(){
        setMessageInnerHTML("error");
    };
 
 
    //連接成功建立的回調方法
    websocket.onopen = function(event){
        setMessageInnerHTML("open");
    }
 
 
    //接收到消息的回調方法
    websocket.onmessage = function(event){
        setMessageInnerHTML(event.data);
    }
 
 
    //連接關閉的回調方法
    websocket.onclose = function(){
        setMessageInnerHTML("close");
    }
 
 
    //監聽視窗關閉事件,當視窗關閉時,主動去關閉websocket連接,防止連接還沒斷開就關閉視窗,server端會拋異常。
    window.onbeforeunload = function(){
        websocket.close();
    }
 
 
    //將消息顯示在網頁上
    function setMessageInnerHTML(innerHTML){
        document.getElementById('message').innerHTML += innerHTML + '<br/>';
    }
 
 
    //關閉連接
    function closeWebSocket(){
        websocket.close();
    }
 
 
    //發送消息
    function send(){
        var message = document.getElementById('text').value;
        websocket.send(message);
    }
</script>
</html>

項目地址

鏈接:https://pan.baidu.com/s/1yiAXTkCjHac-F3S1HFyNJQ 
提取碼:53tp 

功能演示

 客戶端給所有線上用戶發消息

客戶端給指定線上用戶發送消息

 

伺服器給客戶端發送消息(輪詢方式)

 註意需要加上這些註解

 

演示

通過前端控制器給指定用戶發送消息

 

演示


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 秋招總結 寫在最前 我寫過很多篇秋招總結,這篇文章應該是最後一篇總結,當然也是最完整,最詳細的一篇總結。秋招是我人生中一段寶貴的經歷,不僅是我研究生生涯交出的一份答卷,也是未來職業生涯的開端。僅以此文,獻給自己,以及各位在求職路上的,或者是已經經歷過校招的朋友們。不忘初心,方得始終。 前言 在下本是 ...
  • 一 JDBC簡介 Java DataBase Connectivity Java語言連接資料庫 官方(Sun公司)定義的一套操作所有關係型資料庫的規則(介面) 各個資料庫廠商去實現這套介面 提供資料庫驅動JAR包 可以使用這套介面(JDBC)編程 真正執行的代碼是驅動JAR包中的實現類 二 JDBC ...
  • 題目描述:這裡 思路: 一、部分分演算法 對於的數據,用暴力解決即可,時間複雜度 對於另外的數據(所有木棍長度相等),考慮用組合數學,答案為 二、正解 我們考慮對整個序列進行桶排序。 我們設每個數出現的次數為。 對於所有≥的數,加上比它小的所有數出現的次數,並加上這個數至這個數中所有數出現的個數。 特 ...
  • 區別維度: 1. 可變性 a. String用final修飾,不可變 b. Stringbuilder和StringBuffer均繼承抽象父類AbstractStringBuilder,其中也是用char[]數組儲存字元串,但無final修飾 2. 線程安全性:源碼中StringBuilder和St ...
  • 雖然Python的強項在人工智慧,數據處理方面,但是對於日常簡單的應用,Python也提供了非常友好的支持(如:Tkinter),本文主要一個簡單的畫圖小軟體,簡述Python在GUI(圖形用戶界面)方面的應用,僅供學習分享使用,如有不足之處,還請指正。 ...
  • Debug LinkedList源碼 前置知識 LinkedList基於鏈表,LinkedList的Node節點定義 成員變數 //鏈表中元素的數量 transient int size = 0; /** * 鏈表的頭節點:用於遍歷 */ transient Node<E> first; /** * ...
  • 1 int StringSearch(char str[], char strSearch[]) 2 { 3 int i = -1; 4 while (str[i]) 5 { 6 char c = strSearch[0];//鎖定查找字元的第1位置 7 if (str[i] != c)//判斷查找 ...
  • 學習就是為了不斷的看到自己的知識盲點,然後改正,以前知道如何使用break來跳出迴圈,突然學習到可以用break跳出外部的迴圈(以前只知道怎麼調本次的迴圈)。 上正題代碼如下: break跳出本次迴圈: public static void main(String[] args) { for (in ...
一周排行
    -Advertisement-
    Play Games
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...