Springboot 集成websocket 并支持服务集群

1、新增配置类声明


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

@Configuration
public class WebsocketConfig {
    /**
     * 如果单元测试报错,请在类上加上以下注解内容
     * @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
     * @return
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }

}

2、新建websocket连接类


import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;

/**
 * html页面与之关联的接口
 * var reqUrl = "http://localhost:8080/ws/device/{deviceType}/{deviceAddress}";
 * socket = new WebSocket(reqUrl.replace("http", "ws"));
 */
@Slf4j
@Component
@ServerEndpoint("ws/device/{deviceType}/{deviceAddress}")
public class OwonWebSocketServerEndpoint {

    private String KEY;
    private String DEVICE_TYPE;

    @OnOpen
    public void onOpen(Session session, @PathParam("deviceType") String deviceType, @PathParam("deviceAddress") String deviceAddress) {
        log.info("发现设备连接,deviceType:" + deviceType + ",deviceAddress:" + deviceAddress);
        DeviceDO userDevice = SpringContextUtils.getBean(rDeviceMapper.class).findByDeviceTypeAndDeviceAddress(deviceType, deviceAddress);
        if (userDevice == null) {
            try {
                session.close();
            } catch (IOException e) {
                // ignore
            }
            return;
        }
        this.KEY = deviceType + WsSessionManager.SPLIT + deviceAddress;
        this.DEVICE_TYPE = deviceType;
        SpringContextUtils.getBean(WsSessionManager.class).add(KEY, session);
        log.info(String.format("成功建立连接, key:" + this.KEY));
    }

    @OnClose
    public void onClose() {
        SpringContextUtils.getBean(WsSessionManager.class).remove(this.KEY);
        log.info("成功关闭连接, key:" + KEY);
    }

    @OnMessage
    public void onMessage(Session session, String message) {
        log.info("收到消息, message:" + message);
    }

    @OnError
    public void onError(Session session, Throwable error) {
        log.info("发生错误:" + this.KEY);
        error.printStackTrace();
    }

    /**
     * 指定发消息
     *
     * @param message
     */
    public void sendMessage(String deviceType, String deviceAddress, String message) {
        SpringContextUtils.getBean(MessageSendManager.class).sendMessage(deviceType, deviceAddress, message);
    }

3、新建session管理类


import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.websocket.Session;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;


@Slf4j
@Service
public class WsSessionManager {

    @Value("${eureka.instance.instance-id}")
    private String instanceId;
    @Autowired
    private JedisUtil jedisUtil;

    public static final String SPLIT = "@#@";

    /**
     * 保存连接 session 的地方
     */
    private static ConcurrentHashMap<String, Session> SESSION_POOL = new ConcurrentHashMap<>();

    /**
     * 添加 session
     *
     * @param key
     * @param session
     */
    public void add(String key, Session session) {
        // 添加 session
        SESSION_POOL.put(key, session);
        // 记录session所在的机器
        String url = getLocalServerUrl();
        jedisUtil.set(key, url, 24 * 3600);
    }

    /**
     * 设置链接机器地址
     *
     * @param key
     */
    public void setServerUrl(String key) {
        // 记录session所在的机器
        String url = getLocalServerUrl();
        jedisUtil.set(key, url, 24 * 3600);
    }

    /**
     * 删除 session,会返回删除的 session
     *
     * @param key
     * @return
     */
    public Session remove(String key) {

        // 删除 session
        Session session = SESSION_POOL.remove(key);
        // 删除记录的机器地址
        jedisUtil.del(key);
        return session;
    }

    /**
     * 删除并同步关闭连接
     *
     * @param key
     */
    public void removeAndClose(String key) {

        Session session = remove(key);
        if (session != null) {
            try {
                // 关闭连接
                session.close();
            } catch (IOException e) {
                // todo: 关闭出现异常处理
                e.printStackTrace();
            }
        }
    }

    /**
     * 获得 session
     *
     * @param key
     * @return
     */
    public Session get(String key) {

        // 获得 session
        return SESSION_POOL.get(key);
    }

    /**
     * 获取本机的地址
     *
     * @return
     */
    public String getLocalServerUrl() {
        return "http://" + instanceId;
    }

    /**
     * 组装session key
     *
     * @param deviceType 设备类型
     * @param devId  设备id
     * @return
     */
    public String getSessionKey(String deviceType, String devId) {
        return deviceType + SPLIT + devId;
    }

    /**
     * 获取redis 里面存储的链接地址
     *
     * @param sessionKey
     * @return
     */
    public String getServerUrl(String sessionKey) {
        return jedisUtil.get(sessionKey);
    }

    /**
     * 获取所有sessionKey
     *
     * @return
     */
    public List<String> getAllSessionKeys() {
        Enumeration<String> keys = SESSION_POOL.keys();
        if (keys.hasMoreElements()) {
            return Collections.list(keys);
        }
        return Lists.newArrayList();
    }

}

4、新建消息发送类


import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.websocket.Session;

@Slf4j
@Component
public class MessageSendManager {

    @Autowired
    private WsSessionManager wsSessionManager;

    /**
     * 发送消息,先看本机,本机没有链接就转发
     * @param deviceType
     * @param deviceAddress
     * @param message
     * @return
     */
    public Boolean sendMessage(String deviceType, String deviceAddress, String message) {
        //先尝试找本机
        Session session = wsSessionManager.get(wsSessionManager.getSessionKey(deviceType, deviceAddress));
        if (null != session) {
            synchronized (session) {
                try {
                    session.getAsyncRemote().sendText(message);
                    log.info("MessageSendManager sendMsg 消息发送成功 deviceType={},deviceAddress={},payload={}", deviceType, deviceAddress, JSON.toJSONString(message));
                    return true;
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return false;
            }
        } else {
            // 转发到链接所在的机器
            String url = wsSessionManager.getServerUrl(wsSessionManager.getSessionKey(deviceType, deviceAddress));
            if (StringUtils.isBlank(url)) {
                log.info("MessageSendManager sendMsg 找不到链接地址 deviceType={},deviceAddress={}", deviceType, deviceAddress);
                return false;
            }
            // 本机地址
            String localUrl = wsSessionManager.getLocalServerUrl();
            if (StringUtils.equals(url, localUrl)) {
                log.info("MessageSendManager sendMsg 本机找不到 deviceType={},deviceAddress={}", deviceType, deviceAddress);
                return false;
            }
            // 转发到其他机器
            transferByMsg(url, deviceType, deviceAddress, message);
            return true;
        }
    }

    /**
     * 发送消息,本机
     * @param message
     * @return
     */
    public Boolean sendMessageByKey(String key, String message) {
        //先尝试找本机
        Session session = wsSessionManager.get(key);
        if (null != session) {
            synchronized (session) {
                try {
                    session.getAsyncRemote().sendText(message);
                    log.info("MessageSendManager sendMsg 消息发送成功 key={},payload={}", key, JSON.toJSONString(message));
                    return true;
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return false;
            }
        }
        return false;
    }

    /**
     * 发送消息,先看本机,本机没有链接就转发
     * @param deviceType
     * @param deviceAddress
     * @param message
     * @return
     */
    public Boolean sendMsgToClient(String deviceType, String deviceAddress, String message) {
        //先尝试找本机
        Session session = wsSessionManager.get(wsSessionManager.getSessionKey(deviceType, deviceAddress));
        if (null != session) {
            synchronized (session) {
                try {
                    session.getAsyncRemote().sendText(message);
                    log.info("MessageSendManager sendMsg 消息发送成功 deviceType={},deviceAddress={},payload={}", deviceType, deviceAddress, JSON.toJSONString(message));
                    return true;
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return false;
            }
        }
        return false;
    }

    private void transferByMsg(String url, String deviceType, String deviceAddress, String message) {
        String urlString = url + "/device/msg/dispatch";
        HttpUtil.post(urlString, JSON.toJSONString(new WsMsgDispatchDTO(deviceType, deviceAddress, message)));
    }

}

5、新建转发消息接收类

@RestController
@RequestMapping("/device")
public class DeviceMsgDispatchController {

    @Autowired
    private MessageSendManager manager;

    /**
     * 消息转发处理
     * @param dto
     * @return
     */
    @RequestMapping(value = "/msg/dispatch", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public BaseResult<Boolean> dispatch(@RequestBody WsMsgDispatchDTO dto) {
        return BaseResult.success(manager.sendMsgToClient(dto.getDeviceType(), dto.getDeviceAddress(), dto.getMessage()));
    }

}

<think>好的,用户想了解如何使用Spring Boot创建WebSocket服务,需要示例教程。我需要结合提供的三个引用来组织回答。首先,用户可能已经看过引用里的内容,但需要更具体的步骤指导。 首先,引用[2]提到了添加依赖,我应该从这里开始,确保用户正确配置pom.xml。然后,引用[3]提到了服务端的实现,可能需要配置一个配置类,使用@EnableWebSocket和编写处理器。接着,引用[1]和[3]都提到示例代码,可能需要编写处理文本和二进制消息的处理器,展示发送消息的方法。 用户可能需要详细的代码示例,包括配置类、处理器以及如何发送消息到客户端。同时,要确保代码格式正确,解释关键部分。此外,引用中的仓库地址可以作为参考,可以提到让用户查看完整代码。 另外,用户可能关心如何测试这个服务,是否需要前端示例?虽然引用中没有提到,但可能补充简单的HTML/JS测试代码会有帮助,这样用户能全面测试服务端的功能。 需要注意检查是否所有步骤都覆盖到了:依赖添加、配置类、处理器实现、发送消息的方法、测试方法。还要确保回答结构清晰,分步骤说明,附上代码块。最后生成相关问题,比如性能优化、安全配置或集群处理,这些可能用户后续会感兴趣的问题。</think>### 分步解答:使用Spring Boot创建WebSocket服务 #### 1. 添加依赖 在项目的`pom.xml`中添加WebSocket依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> ``` 该依赖包含WebSocket核心实现与Spring集成支持[^2]。 #### 2. 配置WebSocket端点 创建配置类启用WebSocket注册处理器: ```java @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new MyWebSocketHandler(), "/ws") .setAllowedOrigins("*"); } } ``` 此配置将请求路径`/ws`映射到自定义处理器,允许跨域访问[^3]。 #### 3. 实现消息处理器 ```java public class MyWebSocketHandler extends TextWebSocketHandler { private static final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>(); @Override public void afterConnectionEstablished(WebSocketSession session) { sessions.add(session); System.out.println("新连接: " + session.getId()); } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) { String payload = message.getPayload(); System.out.println("收到消息: " + payload); // 广播消息给所有客户端 sessions.forEach(s -> { try { s.sendMessage(new TextMessage("服务端响应: " + payload)); } catch (IOException e) { e.printStackTrace(); } }); } } ``` 该处理器实现了文本消息的接收和广播功能,使用线程安全的`CopyOnWriteArrayList`管理会话。 #### 4. 发送二进制消息(扩展) ```java @Override protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) { ByteBuffer payload = message.getPayload(); System.out.println("收到二进制数据,长度: " + payload.remaining()); } ``` 支持处理二进制数据如文件传输[^3]。 #### 5. 测试服务 创建测试页面`src/main/resources/static/websocket.html`: ```html <!DOCTYPE html> <script> const ws = new WebSocket('ws://localhost:8080/ws'); ws.onmessage = (event) => console.log('收到:', event.data); ws.onopen = () => ws.send('测试消息'); </script> ``` 启动应用后访问该页面,控制台将显示双向通信结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值