02. Springboot实现websocket

1. 问题引入

服务器如何将消息主动推送给客户端(浏览器)呢?

2. 常见的消息推送方式

2.1 轮询

短轮询

浏览器以指定时间间隔向后端服务器发出 HTTP 请求,服务器实时返回数据给前端。

缺点:

  • 数据更新存在延迟(不一定每次查询都能获取到最新数据);
  • 假设轮询的时间间隔为 1s,也就是说服务器每秒钟都要处理一次客户端发来的请求,给服务器增加压力。

长轮询

浏览器发出 ajax 请求,服务端收到请求后,会阻塞请求直到查询到最新数据或者超时才返回。

2.2 SSE(sever-sent event) - 服务器发送事件

  • SSE 在服务器和客户端打开一个单向通道(服务器 -> 客户端);
  • 服务器响应的不是一次性的数据包,而是 text/event-stream 类型的数据流信息;
  • 服务器有数据变更时将数据流式的传给客户端。

2.3 websocket 方式

3. websocket

WebSocket 是一种基于 TCP 连接上进行全双工通信的协议。

  • 全双工:允许数据在两个方向上同时传输;
  • 半双工:允许数据在两个方向上传输,但同一时间段内只允许一个方向上传输。

3.1 websocket API

3.1.1 客户端(浏览器)API

websocket 对象创建:

let ws = new WebSocket(URL);

URL 格式说明:

  • 格式:协议://ip地址//访问路径;
  • 协议:协议名称为 ws;
  • 端口默认为 80,不写。

websocket 对象相关事件:

事件 事件处理程序 描述
open ws.onopen 连接建立时触发
message ws.onmessage 客户端接收到服务器发送的数据时触发
close ws.onclose 连接关闭时触发

websocket 对象提供的方法:

方法名称 描述
send() 通过websocket对象调用该方法发送数据给服务端

3.1.2 服务端 API

Tomcat 从 7.0.5 版本开始支持 WebSocket,并且实现了 Java WebSocket 规范。

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

两种方式定义 Endpoint:

  • 继承 javax.websocket.Endpoint 类并实现其方法;
  • 定义一个 POJO,使用 @ServerEndpoint 注解。

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

注解 描述
@OnOpen 开启一个新的会话时调用(客户端与服务端握手成功时调用)
@OnClose 会话关闭时调用
@OnError 连接过程中异常时调用

服务端如何接收客户端发送的数据呢?

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

服务端如何发送数据给客户端呢?

发送消息由 RemoteEndpoint 完成,其实例由 Session 维护

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

4. 实现

引入依赖

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

编写 WebSocketConfig 配置类,扫描添加了 @ServerEndpoint 注解的Bean。

package com.zte.rdcloud.iproject.infra.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {
   
    /**
     * 自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
     *
     * @return
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
   
        return new ServerEndpointExporter();
    }
}

4.1 聊天室

编写 controller:


编写获取 HttpSession 的配置类:


import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;

public class GetHttpSessionConfig extends ServerEndpointConfig.Configurator {

    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
        // 获取 HttpSession 对象
        HttpSession httpSession = (HttpSession) request.getHttpSession();
        // 将 HttpSession 对象保存起来
        sec.getUserProperties().put(HttpSession.class.getName(), httpSession);
    }
    
}

再将该配置类添加到 @ServerEndpoint 中:

@ServerEndpoint(value = "/chat", configurator = GetHttpSessionConfig.class) 

编写 聊天Endpoint 的具体实现类:

package com.zte.rdcloud.iproject.domain.common.websocket;

import org.springframework.stereotype.Component;

import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@ServerEndpoint(value = "/chat", configurator = GetHttpSessionConfig.class) // 声明访问路径
@Component
public class ChatEndpoint {
   

    private static final Map<String, Session> onlineUsers = new ConcurrentHashMap<>();

    private HttpSession httpSession;

    /**
     * 建立websocket连接后调用该方法
     *
     * @param session
     */
    @OnOpen
    public void onOpen(Session session, EndpointConfig endpointConfig){
   
        // 1. 将session保存(endpoint是和浏览器的连接是一一对应的,每个人的聊天有每个人的endpoint)
        // onlineUsers.put("用户名", session); // key需要一个唯一标识,能够区分不同的用户
        this.httpSession = (HttpSession) endpointConfig
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值