Springboot版本的WebSocket向app端推送消息实现

业务背景:app端需要实时接收到服务端的交易价格信息,肯定无法实时主动去轮询拉取pull操作,需要一种push实时数据到app端,因此websocket技术正好应用于此。

技术栈选型:

1 传统型 使用@ServerEndpoint注解实现

2 前沿性 使用springboot 三个核心 一配二拦三监听

一配指的 是注册并配置websocket的handler监听器和拦截器

二拦指的 是拦截客户端请求的websocket的参数和权鉴服务

三监听指的 是监听各种事件(请求连接/接收消息/关闭连接)

为什么要用springboot集成的websocket,主要是因为springboot对websocket做了很好的支持,而且提供了在建立连接前可以权鉴和做一些其他的事情。

代码实现:

maven 主要依赖:

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

一 配置服务

/**
 * 注册websocket服务
 * @author panmeng
 */
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new EpaiWebSocketHandler(),"/websocket")
                .setAllowedOrigins("*")
                .addInterceptors(new HandShakeInterceptor())
        ;
    }
}

二 拦截器 (可以接收url的数据)

@Slf4j
public class HandShakeInterceptor extends HttpSessionHandshakeInterceptor {
    /**
     * 在WebSocket连接建立之前的操作,以鉴权为例
     * @param request
     * @param response
     * @param wsHandler
     * @param attributes
     * @return
     * @throws Exception
     */
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
                                   WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        log.info("Handle before webSocket connected. ");
        // 获取url传递的参数,通过attributes在Interceptor处理结束后传递给WebSocketHandler
        // WebSocketHandler可以通过WebSocketSession的getAttributes()方法获取参数
        ServletServerHttpRequest serverRequest = (ServletServerHttpRequest) request;
        String catId = serverRequest.getServletRequest().getParameter("object_id");
        if (TokenValidation.validateSign()) {
            log.info("Validation passed. WebSocket connecting.... ");
            attributes.put("catId", catId);
            return super.beforeHandshake(request, response, wsHandler, attributes);
        } else {
            log.error("Validation failed. WebSocket will not connect. ");
            return false;
        }
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
                               WebSocketHandler wsHandler, Exception ex) {
        // 省略
    }
}

三 监听 并处理

主要代码实现是通过一个商品的id作为key,然后每一个查看当前商品的用户都会给他建立一个websession会话连接,记录下来,然后当服务端有最新的交易数据过来时,调用当前的sendWebsocketMessage dubbo接口即可实现将最新的交易消息发送到各个连接客户端。

/**
 * @author panmeng
 */
@Slf4j
@Service("websocketService")
public class EpaiWebSocketHandler implements WebSocketHandler, WebsocketService {

    private static ConcurrentMap<String,List<WebSocketSession>> existSocketClientMap = new ConcurrentHashMap<>();
    /**
     * 当前在线数
     */
    private static int onlineCount = 0;
    
    @Override
    public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {
        log.info("success connect the web socket server...the remote sessionId is {}",webSocketSession.getId());
        Map<String, Object> attributes = webSocketSession.getAttributes();
        Object catAucId = attributes.get("catId");
        if(catAucId!=null&& !StringUtils.isEmpty(catAucId.toString())){
            List<WebSocketSession> historySessionList= existSocketClientMap.get(catAucId.toString());
            List<WebSocketSession> sessionList=new ArrayList<>();
            sessionList.add(webSocketSession);
            if(!CollectionUtils.isEmpty(historySessionList)){
                sessionList.addAll(historySessionList);
            }
            existSocketClientMap.put(catAucId.toString(),sessionList);
        }else{
            throw new BusinessException(GlobalConstant.CAT_AUC_ID_PARAM_NOT_NULL);
        }
        addOnlineCount();
        log.info("current connect total count is :{}",onlineCount);
    }

    private static synchronized void addOnlineCount() {
        EpaiWebSocketHandler.onlineCount++;
    }


    @Override
    public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {
        String msg = webSocketMessage.getPayload().toString();
        log.info("the web socket receive client message,info is {} prepare start send...",msg);
        BaseMessageInfo baseMessageInfo = JSONObject.parseObject(msg, BaseMessageInfo.class);
        Integer catId=null;
        if(baseMessageInfo.getData() !=null){
            JSONObject data= (JSONObject) baseMessageInfo.getData();
            if(data !=null &&  data.get("catId") !=null){
                catId= Ints.tryParse(data.get("catId").toString());
            }
        }
        if(catId ==null){
            throw new BusinessException(GlobalConstant.CAT_INFO_NOT_FIND_ERROR);
        }
        log.info("the web socket receive client message,catId is {}",catId);
        List<WebSocketSession> historySessionList= existSocketClientMap.get(catId.toString());
        if(!CollectionUtils.isEmpty(historySessionList)){
            log.info("the web socket should send client count is {}",historySessionList.size());
            historySessionList.stream().forEach(session->{
                try {
                    if(session.isOpen()){
                        session.sendMessage(new TextMessage(msg));
                    }else{
                        log.info("the session {} is already closed,now should remove this session from history memory",session);
                        afterConnectionClosed(session,CloseStatus.NORMAL);
                    }
                } catch (IOException e) {
                    log.error("the sessionId {} an error occurred while sending the message...",session.getId());
                    e.printStackTrace();
                } catch (Exception e) {
                    log.error("the sessionId {} an error occurred while the session closed...",session.getId());
                    e.printStackTrace();
                }
            });
        }
        log.info("the web socket receive clint message,info is {} send to all complete...",msg);
    }

    @Override
    public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {
        log.info("the connection happen unknow error,the remote address is {}",webSocketSession.getRemoteAddress());
        if (webSocketSession.isOpen()) {
            webSocketSession.close();
        }
    }

    @Override
    public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
        log.info("the connection will close,sessionId is:{}",webSocketSession.getId());
        if (webSocketSession.isOpen()) {
            webSocketSession.close();
        }
        Iterator<Map.Entry<String, List<WebSocketSession>>> iterator = existSocketClientMap.entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry<String, List<WebSocketSession>> entry = iterator.next();
            List<WebSocketSession> sessionList = entry.getValue();
            if(sessionList.contains(webSocketSession)){
                sessionList.remove(webSocketSession);
            }
        }
        log.info("the connection already closed,sessionId is:{}",webSocketSession.getId());
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }

    @Override
    public ReturnJson sendWebsocketMessage(String msg) {
        log.info("the web socket receive clint message,info is {}",msg);
        BaseMessageInfo baseMessageInfo = JSONObject.parseObject(msg, BaseMessageInfo.class);
        Integer catId=null;
        if(baseMessageInfo.getData() !=null){
            JSONObject data= (JSONObject) baseMessageInfo.getData();
            if(data !=null &&  data.get("catId") !=null){
                catId= Ints.tryParse(data.get("catId").toString());
            }
        }
        if(catId ==null){
            throw new BusinessException(GlobalConstant.CAT_INFO_NOT_FIND_ERROR);
        }
        log.info("the web socket receive clint message,catId is {}",catId);
        List<WebSocketSession> historySessionList= existSocketClientMap.get(catId.toString());
        if(!CollectionUtils.isEmpty(historySessionList)){
            historySessionList.stream().forEach(session->{
                try {
                    if(session.isOpen()){
                        session.sendMessage(new TextMessage(msg));
                    }else{
                        log.info("the session {} is already closed,now should remove this session from history memory",session);
                        afterConnectionClosed(session,CloseStatus.NORMAL);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        log.info("the web socket receive clint message,info is {} send to all complete...",msg);
        return ReturnJson.success();
    }
}

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
Spring Boot提供了一个非常方便的方式来整合WebSocket和STOMP协议,可以非常容易地在应用程序中添加实时消息推送功能。下面是实现的步骤: 1.添加依赖 在pom.xml中添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> ``` 2.创建WebSocket配置类 创建一个类来配置WebSocket支持: ```java @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/topic"); registry.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS(); } } ``` 该类通过@EnableWebSocketMessageBroker注解启用了WebSocket消息代理功能,并实现WebSocketMessageBrokerConfigurer接口来配置消息代理。 configureMessageBroker()方法配置了一个简单的消息代理,它将以“/topic”为前缀的消息发送到代理。应用程序的目标前缀将是“/app”。 registerStompEndpoints()方法将“/ws”路径注册为STOMP点,并启用SockJS支持。 3.编写控制器 创建一个控制器来处理WebSocket请求: ```java @Controller public class WebSocketController { @MessageMapping("/hello") @SendTo("/topic/greetings") public Greeting greeting(HelloMessage message) throws Exception { Thread.sleep(1000); // simulated delay return new Greeting("Hello, " + message.getName() + "!"); } } ``` @MessageMapping注解表示该方法可以处理来自“/app/hello”的消息。@SendTo注解表示当处理完成后,将结果发送到“/topic/greetings”主题。 4.创建实体类 创建HelloMessage和Greeting实体类: ```java public class HelloMessage { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } public class Greeting { private String content; public Greeting(String content) { this.content = content; } public String getContent() { return content; } } ``` 5.创建前页面 在前页面中使用STOMP.js和SockJS来连接WebSocket,发送和接收消息: ```html <!DOCTYPE html> <html> <head> <title>WebSocket Example</title> <script src="https://cdn.jsdelivr.net/sockjs/1.1.4/sockjs.min.js"></script> <script src="https://cdn.jsdelivr.net/stomp.js/2.3.3/stomp.min.js"></script> <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> </head> <body> <div> <label for="name">What is your name?</label> <input type="text" id="name" name="name"> <button id="connect">Connect</button> </div> <br/> <div> <label for="message">Message:</label> <input type="text" id="message" name="message"> <button id="send">Send</button> </div> <br/> <div id="greetings"></div> <script> var stompClient = null; function connect() { var socket = new SockJS('/ws'); stompClient = Stomp.over(socket); stompClient.connect({}, function(frame) { console.log('Connected: ' + frame); stompClient.subscribe('/topic/greetings', function(greeting){ showGreeting(JSON.parse(greeting.body).content); }); }); } function disconnect() { if (stompClient !== null) { stompClient.disconnect(); } console.log("Disconnected"); } function sendName() { stompClient.send("/app/hello", {}, JSON.stringify({'name': $("#name").val()})); } function showGreeting(message) { $("#greetings").append("<tr><td>" + message + "</td></tr>"); } $(function () { $("form").on('submit', function (e) { e.preventDefault(); }); $("#connect").click(function() { connect(); }); $("#disconnect").click(function() { disconnect(); }); $("#send").click(function() { sendName(); }); }); </script> </body> </html> ``` 在页面中,我们使用了SockJS和STOMP.js,创建一个WebSocket连接。我们可以使用connect()函数来建立连接,使用sendName()函数来发送消息,并使用showGreeting()函数来显示接收到的消息。 最后,我们需要在应用程序的主类上添加@SpringBootApplication注解,并运行应用程序。 这样,当用户在页面上输入一个名字并点击“Connect”按钮时,将建立一个WebSocket连接,并向服务器发送一个消息。服务器将在1秒钟后返回一个问候语,并将其发送到“/topic/greetings”主题。浏览器将接收到这个消息,并通过showGreeting()函数显示它。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力终会有回报

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值