SpringBoot使用WebSocket

什么是WebSocket
1、WebSocket是一种在单个TCP连接上进行全双工通信的协议。
2、WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。
3、在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

1、依赖

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

2、application.properties

# 端口号
server.port=8080
# 项目路径
server.servlet.context-path=/chat

3、配置类

package com.study.config;


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

//开启WebSocket的支持,并把该类注入到spring容器中
@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}

4、后端

猜测:不同的userId会创建不同的实例,WebSocket后端是多例,但是:
相同的userId请求,后端接收到消息时,使用的同一个实例接收。

 

package com.study;

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.concurrent.ConcurrentHashMap;

/**
 * 不同的userId会创建不同的实例
 * 相同的userId在@OnMessage每次请求接收到消息时,使用的同一个实例接收
 */
@ServerEndpoint(value = "/websocket/{userId}")
@Component
public class WebSocket {
    /**
     * 存放所有在线的客户端
     */
    private static ConcurrentHashMap<String, WebSocket> webSocketMap = new ConcurrentHashMap<>();
    /**
     * 连接uid和连接会话
     */
    private String userId;
    private Session session;

    //新增一个方法用于主动向客户端发送消息
    public static void sendMessage(String message, String userId) {
        WebSocket webSocket = webSocketMap.get(userId);
        if (webSocket != null) {
            try {
                webSocket.session.getBasicRemote().sendText(message);
                System.out.println("【websocket消息】发送消息成功,用户id=" + userId + ",消息内容:" + message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    //前端请求时一个websocket时
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        this.userId = userId;
        this.session = session;
        webSocketMap.put(userId, this);
        System.out.println("【websocket消息】有新的连接,连接id=" + userId + ":" + this);
    }

    //前端关闭时一个websocket时
    @OnClose
    public void onClose(@PathParam("userId") String userId) {
        webSocketMap.remove(userId);
        System.out.println("【websocket消息】连接断开:" + userId);
    }

    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("【websocket消息】WebSocket发生错误,错误信息为:" + error.getMessage());
        error.printStackTrace();
    }

    //前端向后端发送消息
    @OnMessage
    public void onMessage(String message) {
        System.out.println("【websocket实例】" + this);
        if ("ping".equals(message)) {
            sendMessage("pong", userId);
        } else {
            System.out.println("【websocket消息】收到客户端发来的消息:" + message);
            sendMessage(this + "=" + message, userId);
        }

    }
}

5、前端页面

3个页面同时打开,验证结果:

猜测:不同的userId会创建不同的实例,WebSocket后端是多例,但是:
相同的userId请求,后端接收到消息时,使用的同一个实例接收。

前端页面1

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket</title>
</head>
<body>
<h1>WebSocket1</h1>
<input type="text" name="message">
<button onclick="sendMessage()">发送</button>
<div>
    <textarea name="reply" rows="20" cols="50"></textarea>
</div>
<script>
    const socket = new WebSocket('ws://127.0.0.1:8080/chat/websocket/1001');
    // 成功连接 WebSocket 服务器的回调函数
    socket.onopen = function () {
        console.log('WebSocket 连接成功');
        // 向服务器发送消息
        socket.send('Hello Server');
        // 心跳
        setInterval(() => {
            if (socket.readyState === socket.OPEN) {
                // socket.send('{"type":"ping"}')
                socket.send('ping')
            }
        }, 5000)
    };

    // 接收到来自 WebSocket 服务器的消息时的回调函数
    socket.onmessage = function (event) {
        console.info(event)
        const message = event.data;
        console.log('接收到服务器的消息:', message);
        document.getElementsByName("reply")[0].value += message + '\n';
    };

    // WebSocket 连接关闭时的回调函数
    socket.onclose = function (event) {
        console.log('WebSocket 连接关闭');
    };

    // WebSocket 连接发生错误时的回调函数
    socket.onerror = function (error) {
        console.error('WebSocket 发生错误:', error);
    };

    function sendMessage() {
        let value = document.getElementsByName("message")[0].value;
        socket.send(value);
    }
</script>
</body>
</html>

前端页面2

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket</title>
</head>
<body>
<h1>WebSocket2</h1>
<input type="text" name="message">
<button onclick="sendMessage()">发送</button>
<div>
    <textarea name="reply" rows="20" cols="50"></textarea>
</div>
<script>
    const socket = new WebSocket('ws://127.0.0.1:8080/chat/websocket/1002');
    // 成功连接 WebSocket 服务器的回调函数
    socket.onopen = function () {
        console.log('WebSocket 连接成功');
        // 向服务器发送消息
        socket.send('Hello Server');
        // 心跳
        setInterval(() => {
            if (socket.readyState === socket.OPEN) {
                // socket.send('{"type":"ping"}')
                socket.send('ping')
            }
        }, 5000)
    };

    // 接收到来自 WebSocket 服务器的消息时的回调函数
    socket.onmessage = function (event) {
        console.info(event)
        const message = event.data;
        console.log('接收到服务器的消息:', message);
        document.getElementsByName("reply")[0].value += message + '\n';
    };

    // WebSocket 连接关闭时的回调函数
    socket.onclose = function (event) {
        console.log('WebSocket 连接关闭');
    };

    // WebSocket 连接发生错误时的回调函数
    socket.onerror = function (error) {
        console.error('WebSocket 发生错误:', error);
    };

    function sendMessage() {
        let value = document.getElementsByName("message")[0].value;
        socket.send(value);
    }
</script>
</body>
</html>

前端页面3

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket3</title>
</head>
<body>
<h1>WebSocket2</h1>
<input type="text" name="message">
<button onclick="sendMessage()">发送</button>
<div>
    <textarea name="reply" rows="20" cols="50"></textarea>
</div>
<script>
    const socket = new WebSocket('ws://127.0.0.1:8080/chat/websocket/1003');
    // 成功连接 WebSocket 服务器的回调函数
    socket.onopen = function () {
        console.log('WebSocket 连接成功');
        // 向服务器发送消息
        socket.send('Hello Server');
        // 心跳
        setInterval(() => {
            if (socket.readyState === socket.OPEN) {
                // socket.send('{"type":"ping"}')
                socket.send('ping')
            }
        }, 5000)
    };

    // 接收到来自 WebSocket 服务器的消息时的回调函数
    socket.onmessage = function (event) {
        console.info(event)
        const message = event.data;
        console.log('接收到服务器的消息:', message);
        document.getElementsByName("reply")[0].value += message + '\n';
    };

    // WebSocket 连接关闭时的回调函数
    socket.onclose = function (event) {
        console.log('WebSocket 连接关闭');
    };

    // WebSocket 连接发生错误时的回调函数
    socket.onerror = function (error) {
        console.error('WebSocket 发生错误:', error);
    };

    function sendMessage() {
        let value = document.getElementsByName("message")[0].value;
        socket.send(value);
    }
</script>
</body>
</html>

6、验证结果

3个页面同时打开,验证结果:

猜测:不同的userId会创建不同的实例,WebSocket后端是多例,但是:
相同的userId请求,后端接收到消息时,使用的同一个实例接收。

同时打开2个前端页面1 :
发现先打开那个页面不能收到websocket消息,但是还能发送消息,
后打开的那个页面,能接收到2个页面发送的消息,也能发送消息。
原因:
代码逻辑问题,因为使用webSocketMap存放所有的客户端信息,
打开两个前端页面1,导致后打开那个webSocketMap的相同key把前面那个覆盖了,
但是,实际上两个页面的websocket都是正常的,只是因为存放客户端webSocketMap相同key导致。

所以,相同的userId请求,后端接收到消息时,使用的不一定是同一个实例接收。

在WebSocket的上下文中,@ServerEndpoint注解的类通常不会为具有相同属性的不同连接共享同一个实例。每个连接都是独立的,只要客户端通过new WebSocket连接服务端,后端就会产生一个实例,并且会创建新的@ServerEndpoint实例来处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值