SpringBoot整合WebScoket(指定用戶接收消息)


什么是WebSocket?

筆者是需要實現指定用戶獲得實時數據,類似好友邀請助力當前組隊情況

spring就有很好的封裝,上代碼;

引入pom.xml

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
            <version>2.1.3.RELEASE</version>
        </dependency>

注入bean,WebSocketConfig.java

package com.myelephant.projects.websocket;

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

/**
 * @Author: StephenZhang
 * @date: 2021-01-19 17:54
 */
@Configuration
public class WebSocketConfig {
    /**
     * 給spring容器注入這個ServerEndpointExporter對象
     * 相當於xml:
     * <beans>
     * <bean id="serverEndpointExporter" class="org.springframework.web.socket.server.standard.ServerEndpointExporter"/>
     * </beans>
     * <p>
     * 檢測所有帶有@serverEndpoint注解的bean並注冊他們。
     *
     * @return
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        System.out.println("我被注入了");
        return new ServerEndpointExporter();
    }
}

自定義一個session綁定對象,相當於指定多個用戶接收消息

Clent.java

bagId 組隊Id

session 用戶會話 唯一

package com.myelephant.projects.websocket;

import com.dandandog.framework.mapstruct.model.MapperVo;
import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.websocket.Session;
import java.io.Serializable;

/**
 * @Author: StephenZhang
 * @date: 2021-01-21 16:01
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class ClientVo extends MapperVo implements Serializable {
    private String bagId;
    private Session session;
}

邏輯實現

目的客服端攜帶bagId 則生成對象同時拿到對應session

add,如有兩個用戶同一個bagId則指定用戶獲取數據即可

通過bagId相等的去發送消息,調用

sendMessage()
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @Author: StephenZhang
 * @date: 2021-01-19 17:55
 */

@ServerEndpoint(value = "/ws/test/{bagId}")
@Component
public class WebSocketServer {

    private static final Logger logger = LoggerFactory.getLogger(WebSocketServer.class);

    private static final AtomicInteger OnlineCount = new AtomicInteger(0);

    /**
     * 用線程安全的CopyOnWriteArraySet來存放客戶端連接的信息
     */
    private static CopyOnWriteArraySet<ClientVo> socketServers = new CopyOnWriteArraySet<>();

    /**
     * websocket封裝的session,信息推送,就是通過它來信息推送
     */
    private Session session;

    /**
     * 服務端的userName,因為用的是set,每個客戶端的username必須不一樣,否則會被覆蓋。
     * 要想完成ui界面聊天的功能,服務端也需要作為客戶端來接收后台推送用戶發送的信息
     */
    private final static String SYS_USERNAME = "coding";


    /**
     * 用戶連接時觸發,我們將其添加到
     * 保存客戶端連接信息的socketServers中
     *
     * @param session
     * @param bagId
     */
    @OnOpen
    public void open(Session session, @PathParam(value = "bagId") String bagId) {

        this.session = session;
        ClientVo clientVo = new ClientVo();
        clientVo.setBagId(bagId);
        clientVo.setSession(session);
        socketServers.add(clientVo);
        logger.info("客戶端:【{}】連接成功", bagId);
        logger.info("有連接成功,當前連接數為:{}", socketServers.size());

    }

    /**
     * 收到客戶端發送信息時觸發
     * 我們將其推送給客戶端(coding)
     * 其實也就是服務端本身,為了達到前端聊天效果才這么做的
     *
     * @param message
     */
    @OnMessage
    public void onMessage(String message) {

        ClientVo client = socketServers.stream().filter(cli -> cli.getSession() == session)
                .collect(Collectors.toList()).get(0);
        sendMessage(client.getBagId() + "<--" + message, SYS_USERNAME);

        logger.info("客戶端:【{}】發送信息:{}", client.getBagId(), message);
    }

    /**
     * 連接關閉觸發,通過sessionId來移除
     * socketServers中客戶端連接信息
     */
    @OnClose
    public void onClose() {
        socketServers.forEach(client -> {
            if (client.getSession().getId().equals(session.getId())) {
                logger.info("客戶端:【{}】斷開連接", client.getBagId());
                socketServers.remove(client);
                logger.info("有連接關閉,當前連接數為:{}", socketServers.size());
            }
        });
    }

    /**
     * 發生錯誤時觸發
     *
     * @param error
     */
    @OnError
    public void onError(Throwable error) {
        socketServers.forEach(client -> {
            if (client.getSession().getId().equals(session.getId())) {
                socketServers.remove(client);
                logger.error("客戶端:【{}】發生異常", client.getBagId());
                error.printStackTrace();
            }
        });
    }

    /**
     * 信息發送的方法,通過客戶端的bagId
     * 拿到其對應的session,調用信息推送的方法
     *
     * @param message
     * @param bagId
     */
    public synchronized static void sendMessage(String message, String bagId) {
        socketServers.forEach(client -> {
            if (bagId.equals(client.getBagId())) {
                try {
                    client.getSession().getBasicRemote().sendText(message);
                    logger.info("服務端推送給客戶端 :【{}】", client.getBagId(), message);

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * 獲取服務端當前客戶端的連接數量,
     * 因為服務端本身也作為客戶端接受信息,
     * 所以連接總數還要減去服務端
     * 本身的一個連接數
     * <p>
     * 這里運用三元運算符是因為客戶端第一次在加載的時候
     * 客戶端本身也沒有進行連接,-1 就會出現總數為-1的情況,
     * 這里主要就是為了避免出現連接數為-1的情況
     *
     * @return
     */
    public synchronized static int getOnlineNum() {
        return socketServers.stream().filter(client -> !client.getBagId().equals(SYS_USERNAME))
                .collect(Collectors.toList()).size();
    }

    /**
     * 獲取在線用戶名,前端界面需要用到
     *
     * @return
     */
    public synchronized static List<String> getOnlineUsers() {

        List<String> onlineUsers = socketServers.stream()
                .filter(client -> !client.getBagId().equals(SYS_USERNAME))
                .map(client -> client.getBagId())
                .collect(Collectors.toList());

        return onlineUsers;
    }

    /**
     * 信息群發,我們要排除服務端自己不接收到推送信息
     * 所以我們在發送的時候將服務端排除掉
     *
     * @param message
     */
    public synchronized static void sendAll(String message) {
        //群發,不能發送給服務端自己
        socketServers.stream().filter(cli -> cli.getBagId() != SYS_USERNAME)
                .forEach(client -> {
                    try {
                        client.getSession().getBasicRemote().sendText(message);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                });

        logger.info("服務端推送給所有客戶端 :【{}】", message);
    }

    /**
     * 多個人發送給指定的幾個用戶
     *
     * @param message
     * @param persons
     */
    public synchronized static void SendMany(String message, String[] persons) {
        for (String userName : persons) {
            sendMessage(message, userName);
        }
    }
}

貼上前端代碼

<html>
<head>
    <meta charset="UTF-8">
    <title>websocket測試</title>
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
    <style type="text/css">
        h3,h4{
            text-align:center;
        }
    </style>
</head>
<body>

<h3>WebSocket測試,客戶端接收到的消息如下:</h3>

<textarea id = "messageId" readonly="readonly" cols="150" rows="30" >

</textarea>


<script type="text/javascript">
    var socket;
    if (typeof (WebSocket) == "undefined") {
        console.log("遺憾:您的瀏覽器不支持WebSocket");
    } else {
        console.log("恭喜:您的瀏覽器支持WebSocket");
        //實現化WebSocket對象
        //指定要連接的服務器地址與端口建立連接
        //注意ws、wss使用不同的端口。我使用自簽名的證書測試,
        //無法使用wss,瀏覽器打開WebSocket時報錯
        //ws對應http、wss對應https。
        if(!socket){
            socket = new WebSocket("ws://localhost:8080/api/ws/test/96325");
        }
        //連接打開事件
        socket.onopen = function() {
            console.log("Socket 已打開");
            socket.send("消息發送測試(From Client)");
        };
        //收到消息事件
        socket.onmessage = function(msg) {
            $("#messageId").append(msg.data+ "\n");
            console.log(msg.data  );
            //msg = success則更新 分頁獲取禮包人數信息and分頁獲取當前團隊人數信息
        };
        //連接關閉事件
        socket.onclose = function() {
            console.log("Socket已關閉");
        };
        //發生了錯誤事件
        socket.onerror = function() {
            alert("Socket發生了錯誤");
        }
        //窗口關閉時,關閉連接
        window.unload=function() {
            socket.close();
        };
    }
</script>

</body>
</html>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM