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
  • Dapr Outbox 是1.12中的功能。 本文只介紹Dapr Outbox 執行流程,Dapr Outbox基本用法請閱讀官方文檔 。本文中appID=order-processor,topic=orders 本文前提知識:熟悉Dapr狀態管理、Dapr發佈訂閱和Outbox 模式。 Outbo ...
  • 引言 在前幾章我們深度講解了單元測試和集成測試的基礎知識,這一章我們來講解一下代碼覆蓋率,代碼覆蓋率是單元測試運行的度量值,覆蓋率通常以百分比表示,用於衡量代碼被測試覆蓋的程度,幫助開發人員評估測試用例的質量和代碼的健壯性。常見的覆蓋率包括語句覆蓋率(Line Coverage)、分支覆蓋率(Bra ...
  • 前言 本文介紹瞭如何使用S7.NET庫實現對西門子PLC DB塊數據的讀寫,記錄了使用電腦模擬,模擬PLC,自至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1.Windows環境下鏈路層網路訪問的行業標準工具(WinPcap_4_1_3.exe)下載鏈接:http ...
  • 從依賴倒置原則(Dependency Inversion Principle, DIP)到控制反轉(Inversion of Control, IoC)再到依賴註入(Dependency Injection, DI)的演進過程,我們可以理解為一種逐步抽象和解耦的設計思想。這種思想在C#等面向對象的編 ...
  • 關於Python中的私有屬性和私有方法 Python對於類的成員沒有嚴格的訪問控制限制,這與其他面相對對象語言有區別。關於私有屬性和私有方法,有如下要點: 1、通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public); 2、類內部可以訪問私有屬性(方法); 3、類外 ...
  • C++ 訪問說明符 訪問說明符是 C++ 中控制類成員(屬性和方法)可訪問性的關鍵字。它們用於封裝類數據並保護其免受意外修改或濫用。 三種訪問說明符: public:允許從類外部的任何地方訪問成員。 private:僅允許在類內部訪問成員。 protected:允許在類內部及其派生類中訪問成員。 示 ...
  • 寫這個隨筆說一下C++的static_cast和dynamic_cast用在子類與父類的指針轉換時的一些事宜。首先,【static_cast,dynamic_cast】【父類指針,子類指針】,兩兩一組,共有4種組合:用 static_cast 父類轉子類、用 static_cast 子類轉父類、使用 ...
  • /******************************************************************************************************** * * * 設計雙向鏈表的介面 * * * * Copyright (c) 2023-2 ...
  • 相信接觸過spring做開發的小伙伴們一定使用過@ComponentScan註解 @ComponentScan("com.wangm.lifecycle") public class AppConfig { } @ComponentScan指定basePackage,將包下的類按照一定規則註冊成Be ...
  • 操作系統 :CentOS 7.6_x64 opensips版本: 2.4.9 python版本:2.7.5 python作為腳本語言,使用起來很方便,查了下opensips的文檔,支持使用python腳本寫邏輯代碼。今天整理下CentOS7環境下opensips2.4.9的python模塊筆記及使用 ...