WebSocket整合SpringBoot实现数据的实时推送

1,947 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 24 天,点击查看活动详情

一、简介(什么是WebSocket)

WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信,即允许服务器主动发送信息给客户端。因此,在WebSocket中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输,客户端和服务器之间的数据交换变得更加简单。

二、数据实时推送的实现方式和应用场景

1.轮询:

客户端通过代码定时向服务器发送AJAX请求,服务器接收请求并返回响应信息。

优点:代码相对简单,适用于小型应用。

缺点:在服务器数据没有更新时,会造成请求重复数据,请求无用,浪费带宽和服务器资源。

2.长连接:

在页面中嵌入一个隐藏的iframe,将这个隐藏的iframe的属性设置为一个长连接的请求或者xrh请求,服务器通过这种方式往客户端输入数据。

优点:数据实时刷新,请求不会浪费,管理较简洁。

缺点:长时间维护保持一个长连接会增加服务器开销。

3.webSocket:

websocket是HTML5开始提供的一种客户端与服务器之间进行通讯的网络技术,通过这种方式可以实现客户端和服务器的长连接,双向实时通讯。

优点:减少资源消耗;实时推送不用等待客户端的请求;减少通信量;

缺点:少部分浏览器不支持,不同浏览器支持的程度和方式都不同。

应用场景:聊天室、智慧大屏、消息提醒、股票k线图监控等。

三、实现

导入websocket依赖

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

websocket配置类

@Configuration
    @EnableWebSocket
    public class WebSocketServerConfigure implements WebSocketConfigurer {

        @Resource
        private StringWebSocketHandler webSocketHandler;

        @Override
        public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
            registry.addHandler(webSocketHandler, "/connect").withSockJS();
        }
    }

websocket连接处理器

@Slf4j
@Component
public class StringWebSocketHandler extends TextWebSocketHandler {

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        WebSocketUtils.add(session);
        log.info("与客户端建立连接");
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        session.close(CloseStatus.SERVER_ERROR);
        log.error("连接异常", exception);
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        super.afterConnectionClosed(session, status);
        WebSocketUtils.remove(session);
        log.error("与客户端断开连接");
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 获取客户端发送过来的消息
        String receiveMsg = message.getPayload();
        log.info(receiveMsg);
    }
}

websocket工具类

public class WebSocketUtils {

    /**
     * 定义存储Session的容器
     */
    private final static CopyOnWriteArraySet<WebSocketSession> SESSION_SETS = new CopyOnWriteArraySet<>();

    /**
     * 添加会话
     */
    public static void add(WebSocketSession socketSession) {
        SESSION_SETS.add(socketSession);
    }

    /**
     * 移除会话
     */
    public static void remove(WebSocketSession socketSession) {
        SESSION_SETS.remove(socketSession);
    }

    /**
     * (群)发送消息
     */
    public static void sendMessage(Object msg) {
        TextMessage textMessage = new TextMessage(JacksonUtils.writeValueAsString(msg));

        try {
            for (WebSocketSession socketSession : SESSION_SETS) {
                socketSession.sendMessage(textMessage);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

定时任务(为了给前端实时推送数据,使用了定时任务。)

@Component
@DisallowConcurrentExecution
@CronExp(id = 1, cron = "0/3 * * * * ?")
public class ItemJob implements Job {
    @Autowired
    private ItemService itemService;

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        WebSocketUtils.sendMessage(itemService.toData());
    }
}

实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Item {
    private String name;
    private Integer num;
}

发送消息服务类

@Service
public class ItemService {
    private static final ArrayList<Item> items = new ArrayList<>();
   
    static {

        items.add(new Item("Mon", 0));
        items.add(new Item("Tue", 0));
        items.add(new Item("Wed", 0));
        items.add(new Item("Thu", 0));
        items.add(new Item("Fri", 0));
        items.add(new Item("Sat", 0));
        items.add(new Item("Sun", 0));
        items.add(new Item("sun", 0));
    }

    public Map<String, ?> toData() {
        items.forEach(e -> e.setNum(e.getNum() + (int) (Math.random() * 5 + 1)));
        HashMap<String, Object> map = new HashMap<>();
        map.put("yAxisData", EntityUtils.toList(items, Item::getNum));
        return map;
    }
}

前端实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket客户端</title>
    <script src="https://cdn.bootcss.com/sockjs-client/0.3.4/sockjs.min.js"></script>
    <link href="https://cdn.bootcss.com/twitter-bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet">
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.4.1/echarts.min.js"></script>
</head>
<body>


<div id="main" style="width:600px;height:400px"></div>
<div id="radar" style="width:600px;height:400px;"></div>
<script type="text/javascript">
    let ws = null;
    var option;
    let targetUri = "/connect";
    window.onload = function () {
        option1 = {
            xAxis: {
                type: 'category',
                data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
            },
            yAxis: {
                type: 'value'
            },
            series: [
                {
                    data: [120, 200, 150, 80, 70, 110, 130],

                    type: 'bar',
                    itemStyle: {
                        normal: {
                            label: {
                                show: true,		//开启显示
                                position: 'top',	//在上方显示
                                textStyle: {	    //数值样式
                                    color: 'black',
                                    fontSize: 16
                                }
                            }
                        }
                    }
                }
            ]
        };
        ws = new SockJS(targetUri);
        ws.onmessage = function (event) {
            var chartDom = document.getElementById('main');
            var myChart = echarts.init(chartDom);
            var parse = JSON.parse(event.data);
            console.log(parse)
            option1.series[0].data = parse.yAxisData;
            console.log(parse.yAxisData)

            myChart.setOption(option1, true);
        };
    }


</script>

</body>
</html>