SpringBoot整合WebSocket

目录

WebSocket概述

SpringBoot整合WebSocket步骤

添加websocket依赖

启用WebSocket的支持

编写WebSocketServer及WebSocketUtil工具类

前端Demo界面编写

测试


WebSocket概述

Websocket是一种在单个TCP连接上进行全双工通信的协议,允许客户端和服务器之间进行实时数据传输。与传统的HTTP请求-响应模型不同,WebSocket建立了一种持久连接,可以在客户端和服务器之间实时地推送数据,非常适合需要实时更新的应用场景。Websockt特点如下:

  1. 建立在 TCP 协议之上,服务器端的实现比较容易。
  2. 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
  3. 数据格式比较轻量,性能开销小,通信高效。
  4. 可以发送文本,也可以发送二进制数据。
  5. 没有同源限制,客户端可以与任意服务器通信。
  6. 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

SpringBoot整合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.stereotype.Component;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Component
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}

编写WebSocketServer及WebSocketUtil工具类

核心:@ServerEndpoint("/webSocket/{userId}") ,里面实现@OnOpen开启连接,@onClose关闭连接,@onMessage接收消息等方法。

java 复制代码
import com.haokai.taxielder.utils.WebSocketUtil;
import jakarta.websocket.*;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * websocket接口处理类
 */
@Component
@ServerEndpoint(value = "/webSocket/{userId}")
@Slf4j
public class WebSocket {

    /**
     * 连接事件,加入注解
     * @param userId
     * @param session
     */
    @OnOpen
    public void onOpen(@PathParam(value = "userId") String userId, Session session) {
        log.info("用户[{}]加入websocket连接!", userId);
        // 添加到session的映射关系中
        WebSocketUtil.addSession(userId, session);
    }

    /**
     * 连接事件,加入注解
     * 用户断开链接
     *
     * @param userId
     * @param session
     */
    @OnClose
    public void onClose(@PathParam(value = "userId") String userId, Session session) {
        log.info("用户[{}]退出了websocket连接...", userId);
        // 删除映射关系
        WebSocketUtil.removeSession(userId);
    }

    /**
     * 当接收到用户上传的消息
     *
     * @param userId
     * @param session
     */
    @OnMessage
    public void onMessage(@PathParam(value = "userId") String userId, Session session, String message) {
        log.info("接收到用户[{}]的消息:{}", userId, message);
    }

    /**
     * 处理用户活连接异常
     *
     * @param session
     * @param throwable
     */
    @OnError
    public void onError(Session session, Throwable throwable) {
        try {
            session.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        throwable.printStackTrace();
    }

}
java 复制代码
import jakarta.websocket.RemoteEndpoint;
import jakarta.websocket.Session;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class WebSocketUtil {
    /**
     * 记录当前在线的Session
     */
    private static final Map<String, Session> ONLINE_SESSION = new ConcurrentHashMap<>();

    /**
     * 添加session
     * @param userId
     * @param session
     */
    public static void addSession(String userId, Session session){
        // 此处只允许一个用户的session链接。一个用户的多个连接,我们视为无效。
        ONLINE_SESSION.putIfAbsent ( userId, session );
    }

    /**
     * 关闭session
     * @param userId
     */
    public static void removeSession(String userId){
        ONLINE_SESSION.remove ( userId );
    }

    /**
     * 给单个用户推送消息
     * @param session
     * @param message
     */
    public static void sendMessage(Session session, String message){
        if(session == null){
            return;
        }
        // 异步发送
        RemoteEndpoint.Async async = session.getAsyncRemote ();
        async.sendText ( message );
    }

    /**
     * 向所有在线人发送消息
     * @param message
     */
    public static void sendMessageForAll(String message) {
        //jdk8 新方法
        ONLINE_SESSION.forEach((sessionId, session) -> sendMessage(session, message));
    }
}

前端Demo界面编写

html 复制代码
<!DOCTYPE HTML>
<html>
   <head>
   <meta charset="utf-8">
   <title>websocket client</title>
    
      <script type="text/javascript">
 
         var ws = null;
         function WebSocketTest()
         {
            if ("WebSocket" in window)
            {
               alert("浏览器支持 WebSocket!");
               
               // 打开一个 web socket
               var name = document.getElementById("name").value;
               ws = new WebSocket("ws://localhost:8081/elder-api/webSocket/" + name);
 
               //构件一个发送消息框
               var context = '<div class="sendMsg">\n' +
                       '           输入发送内容:<input id="sendWebMsg" />' +
                       '           <button onclick="javascript:sendMsgToWS()">发送</button>' +
                       '      </div>';
               document.getElementById("sendDiv").innerHTML = context;
 
               ws.onmessage = function (evt)
               {
                  var received_msg = evt.data;
                  alert("接收到数据:" + received_msg);
               };
			   
			   ws.onclose = function()
			   { 
                  // 关闭 websocket
                  alert("连接已关闭..."); 
               };
            }
            
            else
            {
               // 浏览器不支持 WebSocket
               alert("浏览器不支持 WebSocket!");
            }
         }
 
         function sendMsgToWS(){
 
            if(ws != null){
 
               var msg = document.getElementById("sendWebMsg").value;
               ws.send(msg);
            }
         }
 
      </script>
        
   </head>
   <body>
 
		
      <div id="sse">
         名称:<input id="name" />
         <button onclick="javascript:WebSocketTest()">连接 WebSocket</button>
      </div>
 
      <div id="sendDiv">
 
      </div>
      
   </body>
</html>

测试

当后台实时发生了变动,产生了消息,消息广播到前台所有连接用户

java 复制代码
 private void broadcastMessage(ScheduleOrderMsg scheduleOrderMsg) {
        ScheduleOrderMsgDto scheduleOrderMsgDto = new ScheduleOrderMsgDto();
        BeanUtil.copyProperties(scheduleOrderMsg, scheduleOrderMsgDto);
        //设置ID
        scheduleOrderMsgDto.setMsgId(scheduleOrderMsg.getMsgId() + "");
        // 获取订单信息
        Order scheduleOrder = orderMapper.getByOrderId(scheduleOrderMsg.getScheduleOrderId());
        if(scheduleOrder != null) {
            BeanUtil.copyProperties(scheduleOrder, scheduleOrderMsgDto);
        }
        //序列化
        JSONConfig jsonConfig = JSONConfig.create();
        jsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
        String scheduleOrderMsgJson = JSONUtil.toJsonStr(scheduleOrderMsgDto,jsonConfig);
        WebSocketUtil.sendMessageForAll(scheduleOrderMsgJson);
    }
相关推荐
子兮曰2 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰2 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
爱勇宝2 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
胡写代码2 天前
别再前后端各写一套表单校验了
java·后端
大勇前进2 天前
原生 PHP 还是 Laravel?小项目到底要不要上框架
后端
vipxieliang2 天前
ValidX 在 DDD 领域驱动设计中的实践
java·spring boot
yuzhi_liu2 天前
我用 LangGraph4j 实现 Multi-Agent Supervisor
后端
alsmile2 天前
Node-RED 之外,国产规则引擎的新方案:基于标准语法,Go 先行实现
后端·开源·go
大白802 天前
PHP 内存溢出排查思路:看懂报错日志,精准定位问题
后端
二月龙2 天前
PHP 接口返回统一响应封装,让前后端对接更省心
后端