Spring Boot+Socket實現與html頁面的長連線,客戶端給伺服器端發訊息,伺服器給客戶端輪詢傳送訊息,附案例原始碼

陳彥斌發表於2020-07-26

功能介紹

  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 

功能演示

 客戶端給所有線上使用者發訊息

客戶端給指定線上使用者傳送訊息

 

伺服器給客戶端傳送訊息(輪詢方式)

 注意需要加上這些註解

 

演示

通過前端控制器給指定使用者傳送訊息

 

演示

相關文章