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
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
  • 當從不同來源導入Excel數據時,可能存在重覆的記錄。為了確保數據的準確性,通常需要刪除這些重覆的行。手動查找並刪除可能會非常耗費時間,而通過編程腳本則可以實現在短時間內處理大量數據。本文將提供一個使用C# 快速查找並刪除Excel重覆項的免費解決方案。 以下是實現步驟: 1. 首先安裝免費.NET ...
  • C++ 異常處理 C++ 異常處理機制允許程式在運行時處理錯誤或意外情況。它提供了捕獲和處理錯誤的一種結構化方式,使程式更加健壯和可靠。 異常處理的基本概念: 異常: 程式在運行時發生的錯誤或意外情況。 拋出異常: 使用 throw 關鍵字將異常傳遞給調用堆棧。 捕獲異常: 使用 try-catch ...
  • 優秀且經驗豐富的Java開發人員的特征之一是對API的廣泛瞭解,包括JDK和第三方庫。 我花了很多時間來學習API,尤其是在閱讀了Effective Java 3rd Edition之後 ,Joshua Bloch建議在Java 3rd Edition中使用現有的API進行開發,而不是為常見的東西編 ...
  • 框架 · 使用laravel框架,原因:tp的框架路由和orm沒有laravel好用 · 使用強制路由,方便介面多時,分多版本,分文件夾等操作 介面 · 介面開發註意欄位類型,欄位是int,查詢成功失敗都要返回int(對接java等強類型語言方便) · 查詢介面用GET、其他用POST 代碼 · 所 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...