SpringBoot引入Websocket

SpringBoot引入WebSocket

前言

本文主要介绍使用SpringBoot + Vue项目引入WebSocket进行服务器端与客户端进行双向通信的具体流程。


一、背景介绍

平时我们开发项目时,前后端采用HTTP的方式进行数据交互,此方式只能从客户端发起请求给服务器端,服务器端接收到请求之后进行相应的业务逻辑处理,再将结果响应给客户端,此时一次HTTP请求结束。

如果我们想要通过服务器端主动给客户端推送数据,此时我们可以使用WebSocket或者SSE来实现。本文主要来介绍使用WebSocket实现服务器端主动给客户端推送数据的过程。

二、后端集成步骤

1.项目依赖

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

<!--以下依赖可根据自己的需要自行调整-->
<!--spring boot-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.3.12.RELEASE</version>
</dependency>
<!--rocketmq-->
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.3.0</version>
</dependency>
<!--common utils-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.11</version>
</dependency>

本文后端SpringBoot版本采用2.3.12.RELEASE,如使用其他版本也可进行参考。

本文使用RocketMQ消息队列,用于处理websocket的session共享:使用websocket建立连接后,此连接数据无法进行序列化进行共享到其他服务器,当项目采用分布式部署时,只能由最开始建立连接的那台服务器给对应的客户端发送消息,其他服务器是发送不了消息给对应的客户端的。所以本文使用消息队列广播的方式,将要发送的消息广播给所有的服务器,未建立连接的服务器跳过处理,建立连接的服务器发送消息给客户端。

2.WebSocket配置类

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

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

3.WebSocket服务端

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.io.Serializable;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * websocket服务端
 */
@Slf4j
@Component
@ServerEndpoint("/wsServer/{userId}")
@JsonIgnoreProperties(ignoreUnknown = true)
public class WebSocketServer implements Serializable {

    /**
     * 与某个客户端的连接会话,需要通过它来给客户端发送消息
     */
    private Session session;

    /**
     * 接收userId
     */
    private String userId;

    private static final ConcurrentHashMap<String, Session> WS_MAP = new ConcurrentHashMap<>();

    /**
     * 连接建立成功调用的方法
     *
     * @param session 连接会话
     */
    @OnOpen
    public void onOpen(Session session, @PathParam(value = "userId") String userId) {
        this.userId = userId;
        if (StringUtils.isBlank(this.userId)) {
            throw new RuntimeException("用户ID不能为空");
        }
        this.session = session;
        if (WS_MAP.containsKey(this.userId)) {
            WS_MAP.remove(this.userId);
            WS_MAP.put(this.userId, this.session);
        } else {
            WS_MAP.put(this.userId, this.session);
        }
        try {
            sendMessage();
        } catch (IOException e) {
            log.error("用户".concat(this.userId).concat("网络异常"));
        }
        log.info("Websocket用户连接======当前在线人数: " + WS_MAP.size() + "人");
    }

    @OnClose
    public void onClose() {
        WS_MAP.remove(this.userId);
        log.info("Websocket用户退出======当前在线人数: " + WS_MAP.size() + "人");
    }

    /**
     * 收到客户端消息后调用
     *
     * @param message 收到的消息
     * @param session websocket会话
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("用户消息:" + userId + ", 报文:" + message);
    }

    /**
     * 发生异常时调用
     *
     * @param session   websocket会话
     * @param throwable 异常信息
     */
    @OnError
    public void onError(Session session, Throwable throwable) {
        log.error("用户" + userId + "连接异常: ", throwable);
    }

    /**
     * 建立连接成功后向客户端发送消息
     *
     * @throws IOException 发送异常
     */
    private void sendMessage() throws IOException {
        this.session.getBasicRemote().sendText("已连接");
    }

    /**
     * 发送消息
     *
     * @param userId  目标用户ID
     * @param message 消息
     */
    public static void sendInfo(String userId, String message) {
        log.info("发送消息给" + userId + ", 报文: " + message);
        if (StringUtils.isBlank(userId) || !WS_MAP.containsKey(userId)) {
            log.error("当前用户不在线");
            return;
        }
        try {
            WS_MAP.get(userId).getBasicRemote().sendText(message);
        } catch (IOException e) {
            throw new RuntimeException("推送消息失败", e);
        }
    }

    /**
     * 获取当前在线的用户
     *
     * @return 在线的用户
     */
    public static Set<String> getOnlineUser() {
        if (WS_MAP.isEmpty()) {
            return null;
        }
        return WS_MAP.keySet();
    }

}

4.RocketMq队列监听

import lombok.Data;

/**
 * 消息实体类
 */
@Data
public class WebSocketMessageDTO {

    /**
     * 目标用户ID
     */
    private String userId;

    /**
     * 要发送的消息
     */
    private String message;

}
import org.apache.rocketmq.spring.annotation.MessageModel;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

/**
 * RocketMq队列监听
 */
@Component
@RocketMQMessageListener(topic = "${rocketmq.consumer.topic}", consumerGroup = "${rocketmq.consumer.group}", selectorExpression = "websocket", messageModel = MessageModel.BROADCASTING)
public class WebsocketConsumerListener implements RocketMQListener<WebSocketMessageDTO> {

    @Override
    public void onMessage(WebSocketMessageDTO messageDTO) {
        WebSocketServer.sendInfo(messageDTO.getUserId(), messageDTO.getMessage());
    }

}

5.推送消息实现类

/**
 * 给客户端推送消息
 */
public interface WebSocketService {
    /**
     * 给客户端发送消息
     *
     * @param messageDTO 要发送的信息
     */
    void sendMessage(WebSocketMessageDTO messageDTO);
}
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * 给客户端推送消息
 */
@Slf4j
@Service
public class WebSocketServiceImpl implements WebSocketService {

    @Value("${rocketmq.consumer.topic}")
    private String topic;

    @Resource
    private RocketMQTemplate rocketMqTemplate;
    
    /**
     * 给客户端发送消息
     *
     * @param messageDTO 要发送的信息
     */
    @Override
    public void sendMessage(WebSocketMessageDTO messageDTO) {
        // 发送到队列中
        String destination = topic + ":websocket";
        SendResult sendResult = rocketMqTemplate.syncSend(destination, messageDTO);
        log.info("已推送至消息队列:MsgId={}, MessageQueue={}", sendResult.getMsgId(), sendResult.getMessageQueue());
    }
}

三、前端集成步骤

1.项目依赖

npm install reconnecting-websocket

本文前端项目采用NodeJs + Vue2进行构建。

本文使用的reconnecting-websocket组件,在连接建立后,当连接中断时会自动进行尝试重连。因为默认使用的websocket组件并没有自动重连机制,故需自行实现自动重连。当然如果不使用reconnecting-websocket组件也可以,使用window.setInterval()建立定时器轮询检查websocket连接状态的方式也可以。

2.创建websocket

initWebSocket: function () {
    // websocket的服务端地址,userId可根据需要自行替换为当前登录的用户ID等
    let url = "http://127.0.0.1:8080/wsServer/userId";
    // 实例化socket
    this.socket = new ReconnectingWebSocket(url);
    // 如果使用websocket组件, 则为: this.socket = new WebSocket(url);
    // 监听socket连接
    this.socket.onopen = this.openWebSocket;
    // 监听socket错误信息
    this.socket.onerror = this.errorWebSocket;
    // 监听socket消息
    this.socket.onmessage = this.getMessageWebSocket;
    // 监听socket断开连接的消息
    this.socket.close=this.closeWebSocket;
},
openWebSocket: function () {
    console.log("socket连接成功");
},
errorWebSocket: function () {
    console.log("连接错误");
},
getMessageWebSocket: function (message) {
    let data = message.data;
    console.log("接收到的消息: " + data);
},
closeWebSocket: function () {
    console.log("连接关闭");
}

四、其他配置

1.Nginx转发ws请求

如果我们在部署websocket项目时会采用分布式部署,此时会有多个websocket服务节点,所以我们可以采用nginx作为负载均衡,统一提供websocket服务入口,供客户端调用。nginx上的配置如下:

http {
    map $http_upgrade $connection_upgrade {
        default          keep-alive;  # 默认 keep-alive,表示HTTP协议。
        'websocket'      upgrade;     # 若是 websocket 请求,则升级协议 upgrade。
    }
    upstream websocket_servers {
        server websocket服务ip1:port
        server websocket服务ip2:port
    }
    server {
        listen       8080;

        location /system/wsServer {
            proxy_pass http://websocket_servers; # 转发到websocket后端接口
            proxy_read_timeout 1800s; # 设置超时时间,默认是60s
            proxy_http_version 1.1;
            proxy_set_header Host $host;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
        }
    }
}

总结

以上就是今天要讲的内容,本文仅仅简单介绍了Java项目中集成WebSocket的方式,具体在使用时,可根据项目需要填充具体的业务逻辑进行使用。

本文中介绍的WebSocket是一种全双工的通信技术,既可以从服务器端给客户端推送消息,客户端也可以主动给服务器端推送消息,如果要实现实时聊天的业务场景,可以使用WebSocket进行实现。

此外,如果只是要实现从服务器端给客户端推送消息的单向通信,也可使用SSE(Server Sent Event)进行实现,具体集成方式将在后续的文章中总结。

  • 18
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个使用Spring BootWebSocket后端示例代码: 1. 首先,在pom.xml文件中添加以下依赖项: ```xml <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> <version>2.5.4</version> </dependency> </dependencies> ``` 2. 创建一个WebSocket配置类,用于配置WebSocket相关的参数: ```java @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new MyWebSocketHandler(), "/my-websocket"); } } ``` 3. 创建一个WebSocket处理程序类,用于处理WebSocket消息: ```java public class MyWebSocketHandler extends TextWebSocketHandler { private List<WebSocketSession> sessions = new CopyOnWriteArrayList<>(); @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { sessions.add(session); session.sendMessage(new TextMessage("Connected")); } @Override public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { for (WebSocketSession s : sessions) { s.sendMessage(new TextMessage(session.getId() + ": " + message.getPayload())); } } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { sessions.remove(session); } } ``` 4. 运行Spring Boot应用程序,WebSocket服务器将在端口8080上启动。在浏览器中打开两个选项卡,并分别连接到`ws://localhost:8080/my-websocket`。您将看到两个选项卡之间的消息传递。 希望这个示例能够帮助您了解如何使用Spring BootWebSocket构建后端应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值