WebSocket入门与结合redis

WebSocket是什么

WebSocket 是一种用于在客户端和服务器之间建立双向通信的协议,它能实现实时、持久的连接。与传统的 HTTP 请求响应模式不同,WebSocket 在建立连接后允许客户端和服务器之间相互发送消息,直到连接关闭。由于 WebSocket 具有低延迟、双向通信和高效的特点,因此适用于多种实时应用场景。

源码在下面

相关依赖

XML 复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

websocket必须配置

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * WebSocket 配置
 */
@Configuration
public class WebSocketConfig {

    /**
     * ServerEndpointExporter 作用
     *
     * 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
     *
     * @return
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

一、入门例子

代码demo

java 复制代码
import jakarta.websocket.*;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * @Description:
 * @Author: sh
 * @Date: 2024/12/15 00:06
 */
@Component
@ServerEndpoint("/websocket/WEBSOCKET_MSG_TOPIC")
public class WebSocketServer {

    @OnOpen
    public void onOpen(Session session) {
        System.out.println("客户端已连接: " + session.getId());
    }

    @OnMessage
    public void onMessage(Session session, String message) {
        System.out.println("收到消息: " + message);
        try {
            session.getBasicRemote().sendText("消息已收到: " + message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @OnClose
    public void onClose(Session session) {
        System.out.println("客户端已关闭: " + session.getId());
    }

    @OnError
    public void onError(Session session, Throwable throwable) {
        System.out.println("发生错误: " + throwable.getMessage());
    }
}

测试demo

java 复制代码
import com.alipay.api.java_websocket.client.WebSocketClient;
import com.alipay.api.java_websocket.handshake.ServerHandshake;

import java.net.URI;
import java.net.URISyntaxException;

/**
 * @Description:
 * @Author: sh
 * @Date: 2024/12/15 00:04
 */
public class WebSocketClientExample {
    public static void main(String[] args) throws URISyntaxException {
        WebSocketClient client = new WebSocketClient(new URI("ws://localhost:8080/websocket/WEBSOCKET_MSG_TOPIC")) {

            @Override
            public void onOpen(ServerHandshake handshakedata) {
                System.out.println("连接已打开");
                send("Hello, Server!"); // 发送消息到 WebSocket 服务器
            }

            @Override
            public void onMessage(String message) {
                System.out.println("收到消息: " + message);
            }

            @Override
            public void onClose(int code, String reason, boolean remote) {
                System.out.println("连接关闭: " + reason);
            }

            @Override
            public void onError(Exception ex) {
                System.out.println("发生错误: " + ex.getMessage());
            }
        };

        client.connect();
    }
}

二、进阶与redis结合

进阶demo

直接从redis中获取数据通过订阅从redis中获取数据

java 复制代码
import com.macro.mall.websocket.listener.WebSocketSubscribeListener;
import jakarta.annotation.Resource;
import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * @Description:
 * @Author: sh
 * @Date: 2024/12/13 15:38
 */
@Component
//@ServerEndpoint(value="/websocket/WEBSOCKET_MSG_TOPIC", decoders = MyMessageDecoder.class)
@ServerEndpoint(value="/websocket/WEBSOCKET_MSG_TOPIC")
public class JavaDemo {

    /**
     * 日志对象
     */
    private static Logger logger = LoggerFactory.getLogger(JavaDemo.class);

    /**
     * redis消息监听者容器,此处不好直接注入
     */
    private static RedisMessageListenerContainer redisMessageListenerContainer;


    private static RedisTemplate redisTemplate;

    @Resource
    public void setRedisMessageListenerContainer(RedisMessageListenerContainer redisMessageListenerContainer) {
        JavaDemo.redisMessageListenerContainer = redisMessageListenerContainer;
    }

    @Resource
    public void setRedisTemplate(RedisTemplate redisTemplate) {
        JavaDemo.redisTemplate = redisTemplate;
    }

    /**
     * concurrent包的线程安全Set,用来存放每个客户端对应的webSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
     */
    private static CopyOnWriteArraySet<JavaDemo> webSocketSet = new CopyOnWriteArraySet<>();

    /**
     * websocket订阅监听器
     */
    private WebSocketSubscribeListener subscribeListener;

    @OnOpen
    public void onOpen(Session session, EndpointConfig config) {
        webSocketSet.add(this);

        subscribeListener = new WebSocketSubscribeListener();
        subscribeListener.setSession(session);

        // 设置订阅topic
        redisMessageListenerContainer.addMessageListener(subscribeListener, new ChannelTopic("WEBSOCKET_MSG_TOPIC"));
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        logger.debug("get msg from websocket client: {}", message);

        //1获取redis数据
        String result = (String) redisTemplate.opsForValue().get(message);
        
        //2.订阅获取redis数据
//        Object result = null;
//        // 处理消息并准备发送给前端
//        if ("WEBSOCKET_MSG_TOPIC".equals(new String(message.getChannel()))) {
//            String responseMessage = "服务器收到的消息: " + new String(message.getBody());
//
//            result = redisTemplate.opsForValue().get(new String(message.getBody()));
//        }

        // 使用 Session 发送消息回客户端
        try {
            session.getBasicRemote().sendText(result.toString());
        } catch (IOException e) {
            logger.error("发送消息失败: {}", e.getMessage());
        }
    }

    @OnClose
    public void onClose(Session session) {
        // 移除session对象
        webSocketSet.remove(this);
        // 移除订阅对象
        redisMessageListenerContainer.removeMessageListener(subscribeListener);
    }

    @OnError
    public void onError(Session session, Throwable error) {

    }
}

redis配置

java 复制代码
@Configuration
public class RedisConfig extends BaseRedisConfig {
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory();
    }

    // 配置 Redis 消息监听容器
    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory redisConnectionFactory,
                                                                       MessageListener subscribeListener,  // 注意这里是 inject 消息监听器
                                                                       ChannelTopic channelTopic) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(redisConnectionFactory);
        container.addMessageListener(subscribeListener, channelTopic);  // 订阅监听器
        return container;
    }

    // 配置 ChannelTopic
    @Bean
    public ChannelTopic channelTopic() {
        return new ChannelTopic("WEBSOCKET_MSG_TOPIC");  // 你可以更改为你实际需要的频道名
    }

    // 配置消息监听器(假设你的 subscribeListener 是一个 MessageListener)
    @Bean
    public MessageListener subscribeListener() {
        return new WebSocketSubscribeListener();  // 假设你有一个自定义的 MessageListener 类
    }
}

redis的sub监听器,监听websocket收到的消息

java 复制代码
/**
 * @Description:subscribe监听器
 * @Author: sh
 * @Date: 2024/12/13 16:00
 */
public class WebSocketSubscribeListener implements MessageListener {

    /**
     * 日志对象
     */
    private Logger logger = LoggerFactory.getLogger(WebSocketSubscribeListener.class);

    /**
     * websocket连接对象
     * -- GETTER --
     *  获取websocket连接对象
     *
     * @return websocket连接对象

     */
    @Getter
    private Session session;

    /**
     * 设置websocket连接对象
     *
     * @param session websocket连接对象
     */
    public void setSession(Session session) {
        this.session = session;
    }


    @Override
    public void onMessage(Message message, byte[] bytes) {
        // 获取消息
        String msg = new String(message.getBody());

        try {
            session.getBasicRemote().sendText(msg);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

Html页面测试demo

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket Test</title>
</head>
<body>
<h1>WebSocket 测试</h1>
<div id="status">未连接</div>
<textarea id="messages" rows="10" cols="30" readonly></textarea><br>
<input type="text" id="messageInput" placeholder="输入消息" />
<button onclick="sendMessage()">发送</button>
<button onclick="closeConnection()">关闭连接</button>

<script>
    let websocket;

    // 创建 WebSocket 连接
    function connect() {
        websocket = new WebSocket('ws://127.0.0.1:8080/websocket/WEBSOCKET_MSG_TOPIC'); // 连接到后端 WebSocket 服务

        // WebSocket 连接打开时
        websocket.onopen = () => {
            document.getElementById("status").textContent = "连接已建立";
        };

        // 处理接收到的消息
        websocket.onmessage = (event) => {
            const message = event.data;

            // 假设服务器发送的是 JSON 格式的消息
            try {
                const parsedMessage = JSON.parse(message);
                // 假设服务器返回的数据格式是 { "user": "username", "content": "message text" }
                document.getElementById("messages").value += `来自 ${parsedMessage.user}: ${parsedMessage.content}\n`;
            } catch (e) {
                // 如果解析失败,则显示原始消息
                document.getElementById("messages").value += '收到: ' + message + '\n';
            }
        };

        // 连接关闭时
        websocket.onclose = () => {
            document.getElementById("status").textContent = "连接已关闭";
        };

        // 连接错误时
        websocket.onerror = (error) => {
            console.error("WebSocket 错误:", error);
            document.getElementById("status").textContent = "连接错误";
        };
    }

    // 发送消息到服务器
    function sendMessage() {
        const message = document.getElementById('messageInput').value;
        if (websocket && websocket.readyState === WebSocket.OPEN) {
            websocket.send(message);
            document.getElementById('messageInput').value = '';  // 清空输入框
            document.getElementById('messageInput').focus();  // 聚焦到输入框
        } else {
            alert("WebSocket 连接未打开");
        }
    }

    // 关闭 WebSocket 连接
    function closeConnection() {
        if (websocket) {
            websocket.close();
        }
    }

    // 页面加载时自动连接
    window.onload = connect;
</script>
</body>
</html>
相关推荐
两袖清风9986 分钟前
【算法】—— 前缀和
java·数据结构·算法
项目題供诗11 分钟前
JaxaFx学习(一)
java·学习
勇搏风浪12 分钟前
SpringBoot 学习
java·spring boot·学习
funnyZpC12 分钟前
如何将java私有库(jar)提交至公服/公共仓库(central repository)-手动版
java·ci/cd·中间件·开源
十秒耿直拆包选手15 分钟前
IDEA下加载音频文件操作
java·ide·intellij-idea·javafx
shepherdSantiag17 分钟前
第三套题【C语言期末复习】
java·c语言·开发语言
苹果酱056719 分钟前
Springboot中的SPI 机制
java·vue.js·spring boot·mysql·课程设计
weisian15144 分钟前
Redis篇-9--数据结构篇1--五种基本结构(String,List,Set,Sorted Set,Hash,BLPOP阻塞逻辑)
数据结构·redis·list
风掣长空1 小时前
应用层协议HTTP
网络·网络协议·http
hyf_code2 小时前
Midjourney Describe API 的对接和使用
java·服务器·midjourney