SpringBoot+Vue+WebSocket实现全双工通信实践。

        在浏览器(客户端)和服务器交互过程中,大部分是浏览器向服务器发送请求然后服务器响应数据给浏览器。但是在一些特定场景中(实时通信系统)需要服务器在数据更新或特定事件发生时,立即将信息推送给客户端,而无需客户端轮询(即定期请求)服务器。比如我现在就有一个需要在前端实时展示用户未读消息的需求。

        要实现服务器实时推送数据给前端常用的解决方案就是使用WebSocket通信。

        WebSocket 协议是一种在单个 TCP 连接上提供全双工通信的协议,它允许客户端和服务器之间进行实时的双向数据传输。WebSocket 协议通过 HTTP/HTTPS 协议的握手阶段建立连接,之后在相同的连接上进行数据传输,避免了 HTTP 的请求-响应模型的限制。

WebSocket 协议的特点

  • 双向通信: WebSocket 允许客户端和服务器之间在同一个连接上进行双向通信,客户端和服务器均可发送和接收数据,实现实时交互和推送功能。

  • 低延迟: 由于WebSocket建立在TCP上,而不是HTTP,因此它减少了每个消息的开销,提供了更低的延迟。

  • 较少的数据传输开销: WebSocket 协议采用二进制传输,相较于基于文本的 HTTP 请求,传输效率更高,减少了数据传输时的开销。

  • 跨域支持: WebSocket 协议支持跨域通信,通过 HTTP 头部的 Upgrade 机制升级到 WebSocket 连接,从而解决了浏览器的同源策略限制。

  • 持久连接: WebSocket 连接一旦建立,可以持久存在,服务器和客户端可以随时发送或接收数据,而无需重新建立连接。

  • 适用于实时应用: WebSocket 协议特别适用于实时性要求较高的应用,如在线游戏、即时聊天、实时协作工具等。

WebSocket使用场景

  • 实时通信应用

    • 即时聊天和通讯:如在线客服、即时消息应用等。
    • 协作工具:如实时协同编辑、在线白板等。
    • 实时数据更新:如股票市场、实时报警系统等。
  • 实时游戏

    • 多人在线游戏(MMOG):WebSocket 提供了低延迟的双向通信,非常适合实时多人游戏的数据传输和状态同步。
  • 实时数据展示和监控

    • 实时数据展示:如实时图表、实时监控数据展示。
    • 实时事件通知:如系统告警、实时事件发布和订阅。
  • 推送服务

    • 广播通知和推送:服务器端可以实时向多个客户端推送信息,如新闻推送、社交网络更新等。
  • 在线会议和视频聊天

    • WebSocket 可以支持实时的音视频传输和会议控制。

不适用场景

        在一些不需要实时通信,或者客户端和服务端的交互只是偶尔性的数据传输场景不适合使用WebSocket通信。WebSocket 连接是持久的全双工连接,服务器端和客户端需要消耗更多的资源来维护连接状态。

代码实操

后端

1.添加依赖

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

2.配置 WebSocket

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS();
    }
}

3. 业务逻辑

@Resource
    private SimpMessagingTemplate simpMessagingTemplate;

public void saveMsg(Messages messages) {
        this.save(messages);
        long count = getUnreadCount(messages.getRecvId());
        if (count > 0) {
            // 发送通知
            simpMessagingTemplate.convertAndSend("/topic/message/" + messages.getRecvId(), count);
        }
    }

前端

1.安装依赖

npm install sockjs-client @stomp/stompjs

2.配置websocketService

import SockJS from "sockjs-client";
import { Client } from "@stomp/stompjs";

const WebSocketService = {
    stompClient: null,
    messageCallback: null, // 回调函数用来处理接收到的消息

    connect(roomId) {
        const socket = new SockJS("http://localhost:8080/ws");
        this.stompClient = new Client({
            webSocketFactory: () => socket,
            debug: (str) => {
                console.log(str);
            },
            onConnect: (frame) => {
                console.log("Connected: " + frame);
                this.stompClient.subscribe("/topic/message/" + roomId, (message) => {
                    console.log("Received: " + message.body);
                    if (this.messageCallback) {
                        this.messageCallback(message.body); // 调用回调函数处理消息
                    }
                });
            },
            onStompError: (frame) => {
                console.error("Broker reported error: " + frame.headers['message']);
                console.error("Additional details: " + frame.body);
            },
        });

        this.stompClient.activate();
    },

    sendMessage(roomId, message) {
        if (this.stompClient && this.stompClient.connected) {
            this.stompClient.publish({
                destination: "/app/sendMessage/" + roomId,
                body: message
            });
        }
    },

    // 设置回调函数来处理接收到的消息
    setMessageCallback(callback) {
        this.messageCallback = callback;
    }
};

export default WebSocketService;

3.在组件中使用

<template>
<!-- 消息按钮,带未读消息数量 -->
      <div class="message-button-container" v-if="userInfo">
        <button class="message-button" @click="goToMessages">未读消息:{{ this.unreadMessagesCount }}</button>
      </div>
</template>

<script>
import WebSocketService from "@/WebSocketService";

export default {
  name: "IndexPage",
  data() {
    return {
      unreadMessagesCount: 0, // 初始未读消息数
    };
  },

mounted() {
      WebSocketService.connect(this.userInfo.id);
      // 设置接收消息的回调函数
      WebSocketService.setMessageCallback(this.handleReceivedMessage);
  },

methods: {
    handleReceivedMessage(message) {
      this.unreadMessagesCount = message;
    },
}

}
</script>

其他方式

除了使用 WebSocket 技术之外,还有几种实现服务器向客户端推送消息的常见方式:

1. Server-Sent Events (SSE)

Server-Sent Events 是一种轻量级的服务器推送技术,它允许服务器单向推送消息到客户端,客户端通过 EventSource API 接收推送的消息。与 WebSocket 不同的是,SSE 是基于 HTTP 的,只支持服务器到客户端的单向通信。

@RestController
public class SSEController {

    @GetMapping("/sse")
    public SseEmitter serverSentEvents() {
        SseEmitter emitter = new SseEmitter();

        // 异步处理发送消息
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    emitter.send(SseEmitter.event().data("Message " + i));
                    Thread.sleep(1000);
                }
                emitter.complete();
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        }).start();

        return emitter;
    }
}
const eventSource = new EventSource('http://localhost:8080/sse');

eventSource.onmessage = function(event) {
    console.log('Received: ' + event.data);
    // 处理接收到的消息
};

eventSource.onerror = function(error) {
    console.error('Error: ' + error);
};

2. Long Polling

Long Polling 是一种通过客户端定期向服务器发送请求,服务器保持请求打开直到有新消息或超时才响应的技术。虽然 Long Polling 不是真正的推送技术,但在某些情况下可以模拟实时通信效果。

@RestController
public class LongPollingController {

    @GetMapping("/poll")
    public ResponseEntity<String> pollForMessage() throws InterruptedException {
        // 模拟异步获取消息
        Thread.sleep(1000);
        return ResponseEntity.ok("New message");
    }
}
async function pollForMessage() {
    try {
        const response = await axios.get('http://localhost:8080/poll');
        console.log('Received: ' + response.data);
        // 处理接收到的消息
    } catch (error) {
        console.error('Error: ' + error);
    } finally {
        // 定时轮询
        setTimeout(pollForMessage, 1000);
    }
}

pollForMessage();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值