SpringBoot项目中使用WebSocket实现服务端推送消息

SpringBoot项目中使用WebSocket实现服务端推送消息

  之前了解过使用webSocket实现服务端消息推送,一直没有真正实践过,上周公司有一个后端新消息推送的业务需要处理,立即想到使用WebSocke做,由于项目大体结构采用SpringBoot+SpringSecurity+solr做的,看到网上有好多相关的示例不能满足需要,自己总结一波,关于webSocket的基本了解,请自行百度,本文重点贴出如何使用,以及业务处理。

一、使用场景

  • 数据流状态: 比如说上传下载文件,文件进度,文件是否上传成功。
  • 协同编辑文档: 同一份文档,编辑状态得同步到所有参与的用户界面上。
  • 多玩家游戏: 很多游戏都是协同作战的,玩家的操作和状态肯定需要及时同步到所有玩家。
  • 多人聊天: 很多场景下都需要多人参与讨论聊天,用户发送的消息得第一时间同步到所有用户。
  • 社交订阅: 有时候我们需要及时收到订阅消息,比如说开奖通知,比如说在线邀请,支付结果等。
  • 股票虚拟货币价格: 股票和虚拟货币的价格都是实时波动的,价格跟用户的操作息息相关,及时推送对用户跟盘有很大的帮助。

二、WebSocket与http区别

  • WebSocket是HTML5出的东西(协议),也就是说HTTP协议没有变化,或者说没关系,但HTTP是不支持持久连接的(长连接,循环连接的不算)
  • 首先HTTP有 1.1 和 1.0 之说,也就是所谓的 keep-alive ,把多个HTTP请求合并为一个,但是 Websocket 其实是一个新协议,跟HTTP协议基本没有关系,只是为了兼容现有浏览器的握手规范而已,有交集,但是并不是全部。
  • 另外Html5是指的一系列新的API,或者说新规范,新技术。
  • Websocket协议解决了服务器与客户端全双工通信的问题。

三、WebSocket与Socket区别

1.WebSocket:

    • websocket通讯的建立阶段是依赖于http协议的。最初的握手阶段是http协议,握手完成后就切换到websocket协议,并完全与http协议脱离了。
    • 建立通讯时,也是由客户端主动发起连接请求,服务端被动监听。
    • 通讯一旦建立连接后,通讯就是“全双工”模式了。也就是说服务端和客户端都能在任何时间自由得发送数据,非常适合服务端要主动推送实时数据的业务场景。
    • 交互模式不再是“请求-应答”模式,完全由开发者自行设计通讯协议。
    • 通信的数据是基于“帧(frame)”的,可以传输文本数据,也可以直接传输二进制数据,效率高。当然,开发者也就要考虑封包、拆包、编号等技术细节。

2.Socket:

    • 服务端监听通讯,被动提供服务;客户端主动向服务端发起连接请求,建立起通讯。
    • 每一次交互都是:客户端主动发起请求(request),服务端被动应答(response)。
    • 服务端不能主动向客户端推送数据。
    • 通信的数据是基于文本格式的。二进制数据(比如图片等)要利用base64等手段转换为文本后才能传输。

三、SpringBoot项目中使用WebSocket

  1. 导入依赖
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-websocket</artifactId>
			<version>4.3.8.RELEASE</version>
		</dependency>
  1. 新建WebSocket配置类
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer{
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
        webSocketHandlerRegistry.addHandler(myHandler(),"/sendMessage/{ID}")
                .setAllowedOrigins("*")
                .addInterceptors(new WebSocketInterceptor());
    }
    private WebSocketServer myHandler(){
        return new WebSocketServer();
    }
}
  1. 新建WebSocket拦截器
public class WebSocketInterceptor implements HandshakeInterceptor{

    private final static Logger LOG= LoggerFactory.getLogger(WebSocketInterceptor.class);
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
        if (request instanceof ServletServerHttpRequest) {
            String ID = request.getURI().toString().split("ID=")[1];
            if(LOG.isInfoEnabled()){
                LOG.info("当前session的ID="+ID);
            }
            ServletServerHttpRequest serverHttpRequest = (ServletServerHttpRequest) request;
            HttpSession session = serverHttpRequest.getServletRequest().getSession();
            map.put(Constant.WEBSOCKET_USERID,ID);
          }
        return true;
    }

    @Override
    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
        if(LOG.isInfoEnabled()){
            LOG.info("webSocket拦截器的afterHandshake方法执行了");
        }
    }
}
  1. 新建WebSocket的Hanlder服务类
@Component
public class WebSocketServer implements WebSocketHandler{

    private final static Logger LOG= LoggerFactory.getLogger(WebSocketServer.class);

    /**
     * WebSocket不能注入(@Autowired),将要注入的service改成static
     *  原因:Spring管理的都是单例的,webSocket多对象冲突
     */
    private static WebSocketService webSocketService;
    @Autowired
    private void setWebSocketService(WebSocketService webSocketService){
        WebSocketServer.webSocketService=webSocketService;
    }

    /**
     * 成功建立连接,处理当前建立连接用户信息
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        String ID=session.getUri().toString().split("ID=")[1];
        if(ID!=null){
            WebSocketUtils.electricSocketMap.put(ID,session);
        }
        if(LOG.isInfoEnabled()){
            LOG.info("[WebSocket成功建立连接] 当前在线人员"+WebSocketUtils.electricSocketMap.size()+"  当前建立连接人员id="+ID);
        }
        //建立连接后具体业务实现
        webSocketService.connDataProc(session.getUri().toString());
    }

    /**
     * 接收socket信息
     * @param session
     * @param webSocketMessage
     * @throws Exception
     */
    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> webSocketMessage) throws Exception {
        JSONObject jsonObject=JSONObject.fromObject(webSocketMessage.getPayload());
        //收到消息后具体业务实现
        webSocketService.receiveMessageProc(jsonObject);
    }
    /**
     * 连接出错
     * @param session
     * @param throwable
     * @throws Exception
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable throwable) throws Exception {
        if(LOG.isErrorEnabled()){
            LOG.error("[webSocket连接发生错误]"+session.getId()+"出错信息"+throwable);
        }
        if(session.isOpen()){
            session.close();
        }
        WebSocketUtils.electricSocketMap.remove(WebSocketUtils.getClientId(session));
    }

    /**
     * 连接关闭之后的动作
     * @param webSocketSession
     * @param closeStatus
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
        if(LOG.isDebugEnabled()){
            LOG.info("[WebSocket连接关闭]"+closeStatus);
        }
        WebSocketUtils.electricSocketMap.remove(WebSocketUtils.getClientId(webSocketSession));
    }

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


}
  1. 新建WebSocket的工具类
public class WebSocketUtils {
    private final static Logger LOG= LoggerFactory.getLogger(WebSocketUtils.class);

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

    /**
     * 获取用户标识
     * @param session
     * @return
     */
    public static String getClientId(WebSocketSession session){
        String clientId = null;
        try{
            clientId= (String) session.getAttributes().get(Constant.WEBSOCKET_USERID);
        }catch (Exception e){

        }
        return clientId;
    }

    /**
     * 服务端主动推送:发送消息给指定的用户(一对一)
     * @param clientId
     * @param message
     * @return
     */
    public static boolean sendMessageToUser(String clientId, TextMessage message){
        if(WebSocketUtils.electricSocketMap.get(clientId)==null){
            return false;
        }
        WebSocketSession session=WebSocketUtils.electricSocketMap.get(clientId);
        if(!session.isOpen()){
            return false;
        }
        try {
            if(LOG.isInfoEnabled()){
                LOG.info("[服务端推送消息]:"+clientId+message);
            }
            synchronized (session){
                session.sendMessage(message);
            }
            return true;
        } catch (IOException e) {
            if(LOG.isErrorEnabled()){
                LOG.error("发送消息失败",e);
                return false;
            }
        }
        return true;
    }

    /**
     * 服务端主动推送:广播发送消息
     * @param message
     * @return
     */
    public static boolean sendMessageToAllUsers(TextMessage message){
        boolean allSendSuccess=true;
        Set<String> clientIds=WebSocketUtils.electricSocketMap.keySet();
        WebSocketSession session=null;
        for (String clientId : clientIds) {
            try{
                session=WebSocketUtils.electricSocketMap.get(clientId);
                if(session.isOpen()){
                    session.sendMessage(message);
                }
            }catch (Exception e){
                if(LOG.isErrorEnabled()){
                    LOG.error("群发消息失败"+clientId,e);
                    continue;
                }
            }
        }
        return allSendSuccess;
    }
}
  1. 新建WebSocket的业务处理服务接口
public interface WebSocketService {
    /**
     * 建立连接数据处理
     * @param uri
     */
    void connDataProc(String uri);

    /**
     * 发送数据后数据处理
     * @param jsonObject
     */
    void receiveMessageProc(JSONObject jsonObject);
}
  1. 新建WebSocket的业务处理实现类
@Service
public class WebSocketServiceImpl implements WebSocketService{

    @Override
    public void connDataProc(String uri) {
        String ID=uri.split("ID=")[1];
        WebSocketUtils.sendMessageToUser(ID,new TextMessage(ID+"已建立连接"));
    }
      /**
     * 接收socket发送的消息(业务处理)
     * @param jsonObject
     */
    @Override
    public void receiveMessageProc(JSONObject jsonObject) {
        //接收业务标识,根据业务标识判断所处理业务
        String business= (String) jsonObject.get("business");
        //具体业务处理不方便贴出,可根据自己需求定制业务。例如建立连接后每一个小时推送一次数据(使用线程处理,睡眠1个小时,关闭连接采用线程中断的方式)
          WebSocketUtils.sendMessageToUser(ID,new TextMessage("所要发送的数据"));
    }   
}

  1. 新建前端处理js
var lockReconnect=true;//避免ws重复连接
var websocket;
var closeWebSocket;
var send;
function webSocket(userId,postValue) {
    if(lockReconnect){
            if('WebSocket' in window){
                websocket = new WebSocket("ws://localhost:8080/sendMessage/ID="+userId);
            }else{
                alert("您的浏览器不支持websocket");
            }
    }else{
        send(postValue);
    }
    websocket.onerror = function(){
        setMessageInHtml("send error!"+userId);
    };
    websocket.onopen = function(){
        lockReconnect=false;
        //setMessageInHtml("connection success!"+userId)
        send(postValue);
    };
    websocket.onmessage  = function(event){
        webSocketAfter(event.data);
    };
    websocket.onclose = function(){
        lockReconnect=true;
        //setMessageInHtml("closed websocket!"+userId)
    };
    window.onbeforeunload = function(){
        closeWebSocket();
    };
    function setMessageInHtml(message){
        console.log(message);
    };
    /**
     * 建立连接后具体业务需求传参在send方法中写即可
     * 例如:
     *  var postValue={};
     *  var msg = document.getElementById('text').value+"000"+"&001";
     *  postValue.business="1";
     *  postValue.message=msg;
     */
     send=function send(postValue){
        if(websocket.readyState===1){
            websocket.send(JSON.stringify(postValue));
        }
    }
    closeWebSocket=function closeWebSocket() {
        if (websocket != null) {
            if(websocket.readyState!=3){
                var postValue = {};
                postValue.userId=userId;
                postValue.business = "0";
                send(postValue);
            }
        }
    }
至此本篇文章结束,有不懂的问题或者文中有错误的地方欢迎评论区留言。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现消息推送的关键是要使用 WebSocket 技术。WebSocket 是一种基于 TCP 的协议,它提供了双向通信的功能,使得服务器可以主动向客户端推送消息。 下面是使用 Spring Boot 整合 WebSocket 实现消息推送的步骤: 1. 添加依赖 在 pom.xml 文件添加 Spring WebSocket 的依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> ``` 2. 创建 WebSocket 配置类 创建一个 WebSocketConfig 类,注入 ServerEndpointExporter 对象,使得 Spring Boot 自动配置 WebSocket 的相关类: ```java @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } } ``` 3. 创建 WebSocket 处理类 创建一个 WebSocketHandler 类,使用 @ServerEndpoint 注解标识该类为 WebSocket 处理类,并实现 onOpen、onClose、onMessage 和 onError 四个方法: ```java @ServerEndpoint("/websocket") @Component public class WebSocketHandler { private static CopyOnWriteArraySet<WebSocketHandler> webSocketSet = new CopyOnWriteArraySet<>(); private Session session; @OnOpen public void onOpen(Session session) { this.session = session; webSocketSet.add(this); } @OnClose public void onClose() { webSocketSet.remove(this); } @OnMessage public void onMessage(String message) { // 处理客户端发来的消息 } @OnError public void onError(Throwable error) { error.printStackTrace(); } } ``` 其,@ServerEndpoint("/websocket") 注解表示 WebSocket 的访问路径。 4. 客户端连接 在客户端页面使用 JavaScript 创建 WebSocket 对象,并指定连接地址: ```javascript let websocket = new WebSocket("ws://localhost:8080/websocket"); ``` 5. 服务端推送消息WebSocketHandler 类,可以通过调用所有 WebSocketHandler 对象的 session 对象,向客户端推送消息: ```java webSocketSet.forEach(handler -> { try { handler.session.getBasicRemote().sendText(message); } catch (Exception e) { e.printStackTrace(); } }); ``` 6. 配置 Nginx 如果需要使用 Nginx 进行反向代理,需要在 Nginx 的配置文件添加如下配置: ```nginx location /websocket { proxy_pass http://localhost:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; } ``` 7. 在 Vue 接收消息 在 Vue ,可以通过监听 WebSocket 的 onmessage 事件,来接收 WebSocket 服务器推送消息: ```javascript websocket.onmessage = function(event) { let message = event.data; // 处理推送消息 }; ``` 至此,Spring Boot 和 Vue 的 WebSocket 消息推送实现了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值