0


websocket实现(二):springboot-websocet

上一篇我们介绍了使用javax.websocket来实现websocket,现在我们基本使用的都是SpringBoot,今天,我们来学习一下如何在springboot中实现websocket

一、javax.websocket

javax.websockethttps://blog.csdn.net/u014374743/article/details/135905010二、spring-boot-starter-websocket

优点:

  • 集成性:Spring Boot提供的WebSocket模块简化了WebSocket的集成,提供了更易用的编程模型。
  • 灵活性:基于Spring框架,可以利用Spring的依赖注入、AOP等特性,实现更灵活的定制和管理。

劣势:

  • 依赖复杂:引入Spring Boot框架可能增加项目的复杂性和启动时间。
  • 可定制性差:有时对于特定定制需求,可能需要更多的配置和处理。

实现方式:

1.引入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

2.新增User类用于记录访问客户端身份

import lombok.Data;
import java.security.Principal;

@Data
public class User implements Principal {

    private String name;
    private String id;

    public User() {
    }
    public User(String id, String name) {
        this.id = id;
        this.name = name;
    }

}

3.增加拦截器保存已连接的用户

import com.maple.websocket.demos.web.User;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.stereotype.Component;

@Component
public class WebSocketInterceptor implements ChannelInterceptor {
    private static final String USER_ID = "User-ID";
    private static final String USER_NAME = "User-Name";

    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
        // 如果是连接请求,记录userId
        if (StompCommand.CONNECT.equals(accessor.getCommand())) {
            String userID = accessor.getFirstNativeHeader(USER_ID);
            String username = accessor.getFirstNativeHeader(USER_NAME);
            accessor.setUser(new User(userID, username));
        }
        return message;
    }
}

4.创建websocket配置类

定义代理订阅、发送前缀,注册服务,添加拦截器

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        // 启用简单的消息代理,定义客户端订阅前缀
        config.enableSimpleBroker("/topic");
        // 定义客户端发送消息前缀
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 注册端点,客户端将使用此端点连接WebSocket
        registry.addEndpoint("/websocket").withSockJS();
    }

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        // 拦截器配置
        registration.interceptors(new WebSocketInterceptor());
    }

}

5.后端Controller控制器

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.user.SimpUser;
import org.springframework.messaging.simp.user.SimpUserRegistry;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;
import java.util.Set;

@Controller
public class WebSocketController {
    @Autowired
    private SimpMessagingTemplate messagingTemplate;
    @Autowired
    private SimpUserRegistry userRegistry;

    // 接收来自客户端的消息,并发送回复消息给特定的客户端频道或房间
    @MessageMapping("/sendMessage/{clientId}")
    @SendTo("/topic/reply/{clientId}")
    public String sendMessageToClient(@DestinationVariable String clientId, String message) {
        String msg = "收到客户端:" + clientId + "发送的消息:" + message;
        System.out.println(msg);
        return msg;
    }

    // 获取已连接用户的信息
    @GetMapping("/connectedUsers")
    @ResponseBody
    public HashMap<String, User> getConnectedUsers() {
        HashMap<String, User> result = new HashMap<>();
        Set<SimpUser> users = userRegistry.getUsers();
        for (SimpUser user : users) {
            result.put(user.getName(), (User) user.getPrincipal());
        }
        return result;
    }

    // 后端主动向特定客户端推送消息
    @GetMapping("/sendToClient")
    @ResponseBody
    public void sendToClient(@RequestParam String clientId) {
        messagingTemplate.convertAndSendToUser(clientId, "/topic/push", "Hello, Client " + clientId + "! 这是只给你的信息.");
        messagingTemplate.convertAndSend("/topic/pushAll", "Hello! 这是群发消息.");
    }
}

6.前端测试代码

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>WebSocket Example</title>
    <script src="/js/sockjs.min.js"></script>
    <script src="/js/stomp.min.js"></script>
</head>
<body>
<div>
    <input type="text" id="messageInput" placeholder="输入消息">
    <button onclick="sendMessage()">发送消息</button>
</div>

<script>
    //这里是随机生成的clientId
    const clientId = generateRandomId(5);
    function generateRandomId(length) {
        let id = '';

        for (let i = 0; i < length; i++) {
            const randomCharCode = Math.floor((Math.random() * 26)) + 97; // ASCII码范围为a-z
            id += String.fromCharCode(randomCharCode);
        }

        return id;
    }
    const socket = new SockJS('http://localhost:8080/websocket');
    const stompClient = Stomp.over(socket);

    stompClient.connect({"User-ID":clientId,"User-Name":clientId}, function (frame) {
        console.log('WebSocket 连接已建立');

         // 用实际的客户端标识符替换

        // 订阅特定客户端的频道或房间
        stompClient.subscribe(`/topic/reply/${clientId}`, function (response) {
            console.log('收到回复:' + response.body);
        });
        //后端通过toUser指定发送信息时,前端订阅的目的地需要加上/user前缀
        stompClient.subscribe(`/user/topic/push`, function (response) {
            console.log('收到后台推送:' + response.body);
        });

        stompClient.subscribe(`/topic/pushAll`, function (response) {
            console.log('收到后台推送:' + response.body);
        });
    });

    // 发送消息给特定客户端的示例
    function sendMessage() {
        const messageInput = document.getElementById('messageInput');
        const message = messageInput.value;
         // 用实际的客户端标识符替换
        stompClient.send(`/app/sendMessage/${clientId}`, {}, message);

        // 清空输入框
        messageInput.value = '';
    }
</script>
</body>
</html>

7.测试结果

这里打开3个客户端,分别发送一段文字,后台也会同时响应返回信息

后台返回的结果:

接下来测试后端接口,我们可以先获取下目前在线的用户:

通过http://localhost:8080/connectedUsers接口

可以看到正是我们刚才连接的三个用户,这时候我们调用接口给前端qzpmp用户发送信息,并且给所有用户通知一条信息

我们可以查看下结果

可以看到只有一个用户收到了我们单独发送的信息,以上就是这次分享了。

如果对你有帮助,点击关注一下,是我分享的动力。

关注我,学习更多简单好理解的编程和架构知识!

下一篇主要讲解通过Netty方式实现websocket
关注我 不迷路

或者微信 添加公众号,发送websocket获取教程源码


本文转载自: https://blog.csdn.net/u014374743/article/details/135920941
版权归原作者 程序员的小黑板 所有, 如有侵权,请联系我们删除。

“websocket实现(二):springboot-websocet”的评论:

还没有评论