当前位置: 首页 > article >正文

Java-WebSocket

文章目录

  • WebSocket概念
  • SpringBoot实现一个WebSocket示例
  • STOMP消息订阅和发布
      • 后端主动发送消息
  • 跨域

WebSocket概念

应用层协议,底层采用TCP,特点:持续连接,有状态,双向通信

当客户端想要与服务器建立WebSocket连接时,它会首先发送一个特殊的HTTP请求(WebSocket握手请求)给服务器,这个请求包含了升级到WebSocket协议的愿望。如果服务器同意,则会返回一个HTTP 101状态码表示切换协议,并完成握手过程。之后,原本的HTTP连接就变成了WebSocket连接

HTTP请求和响应示例

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Protocol: chat
Sec-WebSocket-Version: 13
Origin: http://example.com
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15

Sec-WebSocket-Protocol (可选): 允许客户端指定子协议列表,以便服务器选择支持的协议
Sec-WebSocket-Extensions (可选): 如果有扩展机制被启用,则可以通过此字段告知服务器客户端支持的扩展类型。例如压缩算法等

在实际的应用场景中,除了上述的基本头部信息之外,还可以根据具体需求添加其他自定义头部信息,如认证令牌、用户身份验证信息等

WebSocket连接url示例
测试连接可用wscat命令行工具

ws://echo.websocket.org/    端口同http 80
wss://api.example.com/socketserver  端口同https 443

SpringBoot实现一个WebSocket示例

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
@Configuration
public class WebSocketConfig {

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

WebSocket中的概念端点

涉及注解:

@ServerEndpoint

@OnOpen

@OnClose

@OnMessage

@OnError

@ServerEndpoint("/websocket/{userId}")
@Component
public class ChatWebSocketServer {

    // 静态变量用于记录当前在线连接数,设计成线程安全的方式。
    private static int onlineCount = 0;

    // 存放每个客户端对应的ChatWebSocketServer对象,保证线程安全。
    private static CopyOnWriteArraySet<ChatWebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();

    // 与某个客户端的连接会话,需要通过它给客户端发送数据。
    private Session session;

    // 用户ID,从路径参数获取
    private String userId;

    @OnOpen
    public void onOpen(@PathParam("userId") String userId, Session session) {
        this.session = session;
        this.userId = userId;
        webSocketSet.add(this);     // 加入set中
        addOnlineCount();           // 在线数加1
        System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
        try {
            sendMessage("欢迎连接:" + userId);
        } catch (IOException e) {
            System.out.println("IO异常");
        }
    }

    @OnClose
    public void onClose() {
        webSocketSet.remove(this);  // 从set中删除
        subOnlineCount();           // 在线数减1
        System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("来自客户端的消息:" + message);
        // 群发消息
        for (ChatWebSocketServer item : webSocketSet) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("发生错误");
        error.printStackTrace();
    }

    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        MyWebSocketServer.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        MyWebSocketServer.onlineCount--;
    }
}

STOMP消息订阅和发布

Spring WebSocket 原生包含对 STOMP 消息传递的支持

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
对于WebSocket,URL应以ws://开始;对于SockJS,它会自动处理不同类型的传输

1.STOMP配置

在这里也可以不用STOMP ,也可以配置RabbitMQ等消息队列

package com.example.demo.config;

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", "/queue");
        // 设置应用程序全局目标前缀,确保所有目的地以“/app”开头的消息都会被路由到带有@MessageMapping注解的方法中。---就是个路由转发
        config.setApplicationDestinationPrefixes("/app", "/chat");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 注册STOMP端点,允许使用SockJS作为回退机制
        registry.addEndpoint("/ws")
                .setAllowedOriginPatterns("*") // 明确列出允许的源
                .withSockJS();
    }
}

2.创建消息控制器

package com.example.demo.controller;

import com.example.demo.message.Greeting;
import com.example.demo.message.HelloMessage;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import org.springframework.web.util.HtmlUtils;

@Controller
public class GreetingController {
	@Autowired
    private SimpMessagingTemplate simpMessagingTemplate;
    
    @MessageMapping("/hello")
    @SendTo("/topic/greetings")
    public Greeting greeting(HelloMessage message) throws Exception {
        Thread.sleep(1000); // simulated delay
        return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
    }

   
}

@MessageMapping

@SendTo

@EnableWebSocketMessageBroker 启动WebSocket消息代理Broker

/topic 前缀通常用于广播消息,意味着发送到此类目的地的消息会被分发给所有订阅了相同主题的客户端;而 /queue 前缀则常用来表示点对点的消息传递,即消息只会被发送给一个特定的订阅者,即使有多个订阅者存在也是如此

简单点说STOMP配置中 config.enableSimpleBroker(“/topic”, “/queue”); 开启了 /topic ,/queue为前缀的小型内存消息代理服务, 前端可以用SockJS订阅该路径下主题,当触发对应WebSocket 控制器路径方法时会发消息到指定目的地主题,广播给所有订阅者或者单个订阅者

后端主动发送消息

@Autowired
private SimpMessagingTemplate messagingTemplate;

@Autowired
private SimpUserRegistry userRegistry;

public void notifyOnlineUsersAboutEvent(String eventName) {
    // 获取所有在线用户的ID
    Set<String> onlineUserIds = userRegistry.getUsers().stream()
                                            .map(user -> user.getName())
                                            .collect(Collectors.toSet());

    // 遍历所有在线用户并向他们发送通知
    onlineUserIds.forEach(userId -> 
        messagingTemplate.convertAndSendToUser(userId, "/queue/events", eventName)
    );
}

跨域

全局配置跨域

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
               .allowedOriginPatterns("*") // 注意这里使用了allowedOriginPatterns而不是allowedOrigins
               .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
               .allowedHeaders("*")
               .exposedHeaders("Authorization", "Link")
               .allowCredentials(true)
               .maxAge(3600);
    }
}

注意:在设置了 allowCredentials(true) 的情况下,不能同时设置 allowedOrigins("*"),而应该使用 allowedOriginPatterns("*") 或者明确列出允许的源地址。这是因为浏览器的安全机制不允许携带凭证的同时接受来自任意来源的请求

针对WebSocket端点跨域配置

package com.example.demo.config;

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", "/queue");
        // 设置应用程序目的地前缀,以便区分来自应用的消息和其他类型的消息
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 注册STOMP端点,允许使用SockJS作为回退机制
        registry.addEndpoint("ws")
                .setAllowedOriginPatterns("*") // 明确列出允许的源
                .withSockJS();
    }
}

http://www.kler.cn/a/430690.html

相关文章:

  • 最新的强大的文生视频模型Pyramid Flow 论文阅读及复现
  • LabVIEW软件开发的未来趋势
  • IIC驱动EEPROM
  • Centos7中使用yum命令时候报错 “Could not resolve host: mirrorlist.centos.org; 未知的错误“
  • Shion(时间追踪工具) v0.13.2
  • GTID下复制问题和解决
  • Day2——需求分析与设计
  • [工具和软件]查询在用软件是否为最新版本
  • 虚幻引擎Actor类生命周期
  • Rust快速入门(五)
  • uni-app H5端使用注意事项 【跨端开发系列】
  • 面试题(仅供参考)
  • 深入理解代理模式(Proxy):静态代理、动态代理与AOP
  • 基于SpringBoot的养老院管理系统的设计与实现
  • PS学习第一天
  • 记录 idea 启动 tomcat 控制台输出乱码问题解决
  • maven报错“找不到符号“
  • 将路径转换为短路径形式(8.3格式)解决 `CFile::Open` 无法打开长路径问题
  • 从零搭建网站(第三天)
  • 连续大涨,汉王科技跑步进入AI应用舒适区
  • Vue.js的生命周期
  • go使用闭包处理数据
  • List与Set、数组与ArrayList、ArrayList与LinkedList的区别
  • 【kafka】常用基础命令使用案例
  • ViT学习笔记(三) RepViT和TransNext简介
  • 【定时任务】定时任务技术实现原理和选型分析