Skip to content

常见的消息推送

轮询(Polling):在轮询中,客户端定期向服务器发送请求以获取更新。这种方法会导致频繁的请求和响应,即使数据没有变化,也会消耗带宽和服务器资源。

长轮询(Long Polling):长轮询是一种改进的轮询技术,客户端发送请求到服务器,服务器保持连接打开直到有新数据可用。一旦有新数据,服务器会立即响应客户端,然后客户端立即发起新请求。这种方法减少了不必要的轮询,但仍然会消耗资源。

SSE(Server-Sent Events):SSE 是一种基于 HTTP 的实时通信技术,允许服务器向客户端推送数据。通过单向连接,服务器可以异步地向客户端发送数据。SSE 是一种轻量级的实时通信方式,适用于需要服务器向客户端推送实时数据的场景。

WebSocket:WebSocket 是一种全双工通信协议,允许客户端和服务器之间建立持久连接,并进行实时、双向通信。与HTTP不同,WebSocket连接一旦建立,双方可以直接发送消息而无需每次请求。WebSocket适用于需要实时、双向通信的应用,如在线聊天、实时游戏等。

半双工(Full Duplex):允许数据在两个方向上传输,但是同一时间段内只允许一个方向上传输

全双工(Half Duplex):允许数据在两个方向上同时传输

示例图

image-20240401190033836

image-20240401190113600

WebSocket API

客户端(游览器)API

基于HTML5

javascript
// 创建对象
let ws = new WebSocket(URL);

格式:协议://ip地址/访问路径

协议:名为ws

事件

事件处理程序描述
openws.onopen连接建立时触发
messagews.onmessage接收到服务器消息时触发
closews.onclose连接关闭时触发
errorws.onerror发生错误时触发

方法

方法描述
send()通过ws对象条用方法向服务端发送数据

示例

javascript
// 创建websocket
let ws = new WebSocket("ws://127.0.0.1:8080");

// 建立连接
ws.onopen = () => {
    // 连接成功做出的事情
};

// 接收消息
ws.onmessage = (event) => {
    // 通过 event.data 接收消息
};

// 断开连接
ws.onclose = () => {
    // 断开连接做出的事情
};

// 发生错误
ws.onerror = () => {
    // 发生错误做出的事情
};

// 发送信息
ws.send("hello");

服务端 API

Tomcat 从7.5版本开始支持WebSocket,并实现了Java webSocket规范

Java WebSocket应用由一系列的Endpoint组成;Endpoint 是一个java对象,代表WebSocket链接的一端,对于服务端,可以视为处理具体WebSocket消息的接口

两种方法实现Endpoint:继承和注解

继承:javax.websocket.Endpoint

注解:定义一个POJO,添加@ServerEndpoint

Endpoint实例在WebSocket握手时创建,并在客户端与服务端链接过程中有效,最后在链接关闭时结束

生命周期方法

方法注解描述
onOpen()@OnOpen当开启一个新会话时调用
该方法是客户端与服务端握手成功后掉用
onClose()@OnClose会话关闭时调用
onMessage()@OnMessage前端传过来消息调用
onError()@OnError连接异常时调用

接收客户端消息

实现:实现MessageHandler接口来接收消息

注解:在定义Endpoint时,通过@OnMessage注解指定接收消息的方法

向客户端发送消息

发送消息则由 RemoteEndpoint 完成,其实例由 Session 维护,两种发送方式

  • 通过session.getBasicRemote 获取同步消息发送的实例,然后调用其 sendXxx() 方法发送消息
  • 通过session.getAsyncRemote 获取异步消息发送的实例,然后调用其 sendXxx() 方法发送消息

示例

java
import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Component;

@ServerEndpoint("/myEndpoint")
@Component
public class MyEndpoint {
    // 初始化时调用
    @OnOpen
    public void onOpen(Session session, EndpointConfig config) {

    }

    // 接收客户端消息
    @OnMessage
    public void onMessage(String message) {

    }

    // 关闭连接时调用
    @OnClose
    public void onClose(Session session) {

    }
}

SpringBoot整合配置

xml
<!-- 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

配置类:

java
@Configuration // 配置类
public class WebSocketConfig {
    @Bean
    // 注入ServerEndpointExporter,该Bean会自动注册使用了@ServerEndpoint注解声明的websocket endpoint
    public ServerEndpointExporter myServerEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

每一个前端建立连接都会有一个session

注入Bean

使用注解

因为Spring管理的Bean是单例的,而WebSocket是多个对象,所以想要注入就得把被注入的实例变成静态的,让这个实例属于类如:private static ObjectMapper json;

这让只需要通过set方式注入即可

java
@Resource
public void setObjectMapper(ObjectMapper json) {
    WS.json = json;
}

使用类

除了以上的注入方式之外还可以使用配置类进行注册

java
import jakarta.annotation.Nonnull;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @description: 用于在不是Spring管理的类中使用IOC容器数据
 * @author: 曦暮流年
 */
@Component
public class SpringBeanContext implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(@Nonnull ApplicationContext applicationContext) throws BeansException {
        SpringBeanContext.applicationContext = applicationContext;
    }

    /**
     * 根据名称获取数据
     *
     * @param clazz bean类型
     * @param <T>   bean类型
     * @return bean对象
     */
    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }
}

使用这种方法就可以直接容spring容器中获取实例注入