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_bitsHTTP/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=15Sec-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 {@Beanpublic 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;@OnOpenpublic void onOpen(@PathParam("userId") String userId, Session session) {this.session = session;this.userId = userId;webSocketSet.add(this);     // 加入set中addOnlineCount();           // 在线数加1System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());try {sendMessage("欢迎连接:" + userId);} catch (IOException e) {System.out.println("IO异常");}}@OnClosepublic void onClose() {webSocketSet.remove(this);  // 从set中删除subOnlineCount();           // 在线数减1System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());}@OnMessagepublic void onMessage(String message, Session session) {System.out.println("来自客户端的消息:" + message);// 群发消息for (ChatWebSocketServer item : webSocketSet) {try {item.sendMessage(message);} catch (IOException e) {e.printStackTrace();}}}@OnErrorpublic 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 {@Overridepublic void configureMessageBroker(MessageBrokerRegistry config) {// 启用简单的内存消息代理,用于广播消息到所有订阅者config.enableSimpleBroker("/topic", "/queue");// 设置应用程序全局目标前缀,确保所有目的地以“/app”开头的消息都会被路由到带有@MessageMapping注解的方法中。---就是个路由转发config.setApplicationDestinationPrefixes("/app", "/chat");}@Overridepublic 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 {@Autowiredprivate SimpMessagingTemplate simpMessagingTemplate;@MessageMapping("/hello")@SendTo("/topic/greetings")public Greeting greeting(HelloMessage message) throws Exception {Thread.sleep(1000); // simulated delayreturn 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) {// 获取所有在线用户的IDSet<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 {@Overridepublic 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 {@Overridepublic void configureMessageBroker(MessageBrokerRegistry config) {// 启用简单的内存消息代理,用于广播消息到所有订阅者config.enableSimpleBroker("/topic", "/queue");// 设置应用程序目的地前缀,以便区分来自应用的消息和其他类型的消息config.setApplicationDestinationPrefixes("/app");}@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {// 注册STOMP端点,允许使用SockJS作为回退机制registry.addEndpoint("ws").setAllowedOriginPatterns("*") // 明确列出允许的源.withSockJS();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/63576.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux上的C语言编程实践

说明&#xff1a; 这是个人对该在Linux平台上的C语言学习网站笨办法学C上的每一个练习章节附加题的解析和回答 ex1: 在你的文本编辑器中打开ex1文件&#xff0c;随机修改或删除一部分&#xff0c;之后运行它看看发生了什么。 vim ex1.c打开 ex1.c 文件。假如我们删除 return 0…

UE5 关于获取引用和获取引用消息

在 UE5&#xff08;Unreal Engine 5&#xff09;中&#xff0c;获取引用&#xff08;Get Reference&#xff09;和获取引用消息&#xff08;Get Reference Message&#xff09;通常是在处理对象、蓝图通信或数据流时涉及的概念。尽管这两个术语听起来相似&#xff0c;但它们在实…

Elasticsearch vs 向量数据库:寻找最佳混合检索方案

图片来自Shutterstock上的Bakhtiar Zein 多年来&#xff0c;以Elasticsearch为代表的基于全文检索的搜索方案&#xff0c;一直是搜索和推荐引擎等信息检索系统的默认选择。但传统的全文搜索只能提供基于关键字匹配的精确结果&#xff0c;例如找到包含特殊名词“Python3.9”的文…

SpringCloudAlibaba学习路线:全面掌握微服务核心组件

大家好&#xff0c;我是袁庭新。 星友给我留言说&#xff1a;“新哥&#xff0c;我最近准备开始学Spring Cloud Alibaba技术栈&#xff0c;计划冲刺明年的春招&#xff0c;想全面掌握微服务核心组件。但不知从何学起&#xff0c;没有一个有效的学习路线&#xff0c;我需要学习…

Scala的隐式转换(1)

package hfd //需求&#xff1a; //完成一个功能&#xff0c;让所有的字符串都能调用isPhone方法&#xff0c;来校验自己是不是一个手机号 object Test37_1 {class StrongString(val str: String) {//开始你的代码def isPhone(): Boolean {val reg "1[3-9]\\d{9}".…

Java阶段三06

第3章-第6节 一、知识点 理解MVC三层模型、理解什么是SpringMVC、理解SpringMVC的工作流程、了解springMVC和Struts2的区别、学会使用SpringMVC封装不同请求、接收参数 二、目标 理解MVC三层模型 理解什么是SpringMVC 理解SpringMVC的工作流程 学会使用SpringMVC封装请求…

租赁系统|租赁小程序|租赁小程序成品

租赁系统是现代企业管理中不可缺少的数字化工具&#xff0c;它通过高效的信息整合与流程管理&#xff0c;为企业带来极大的便利和效益。一个完善的租赁系统开发应具备以下必备功能&#xff1a; 一、用户管理 用户管理模块负责系统的访问控制&#xff0c;包括用户注册、登录验证…

product/admin/list?page=0size=10field=jancodevalue=4562249292272

文章目录 1、ProductController2、AdminCommonService3、ProductApiService4、ProductCommonService5、ProductSqlService https://api.crossbiog.com/product/admin/list?page0&size10&fieldjancode&value45622492922721、ProductController GetMapping("ad…

C++ 列表初始化(initializer_list)

列表初始化(initializer_list) 列表初始化是C中的一种语法特性&#xff0c;引入于C11&#xff0c;用于更简单直观地初始化变量、对象或容器。它使用花括号 {}&#xff0c;提供了更安全的初始化方式&#xff0c;避免了类型窄化转换等潜在错误。 定义 列表初始化是用花括号 {}…

java+ssm+mysql美妆论坛

项目介绍&#xff1a; 使用javassmmysql开发的美妆论坛&#xff0c;系统包含超级管理员&#xff0c;系统管理员、用户角色&#xff0c;功能如下&#xff1a; 用户&#xff1a;主要是前台功能使用&#xff0c;包括注册、登录&#xff1b;查看论坛板块和板块下帖子&#xff1b;…

Java-21 深入浅出 MyBatis - 手写ORM框架2 手写Resources、MappedStatment、XMLBuilder等

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 大数据篇正在更新&#xff01;https://blog.csdn.net/w776341482/category_12713819.html 目前已经更新到了&#xff1a; MyBatis&#xff…

关于Python的常用模块

Python拥有丰富的标准库和第三方库&#xff0c;这些库提供了大量的模块&#xff0c;使得Python能够广泛应用于各个领域。以下是一些Python的常用模块&#xff1a; 一、系统交互与文件操作模块 os模块&#xff1a;用于与操作系统进行交互&#xff0c;如获取当前工作目录、创建…

专业135+总分400+华中科技大学824信号与系统考研经验华科电子信息与通信工程,真题,大纲,参考书。

考研成功逆袭985&#xff0c;上岸华科电子信息&#xff0c;初试专业课824信号与系统135&#xff0c;总分400&#xff0c;成绩还是很满意&#xff0c;但是也有很多遗憾&#xff0c;总结一下自己的复习&#xff0c;对于大家复习给些参考借鉴&#xff0c;对自己考研画个句号&#…

ElementUI:el-tabs 切换之前判断是否满足条件

<div class"table-card"><div class"card-steps-class"><el-tabsv-model"activeTabsIndex":before-leave"beforeHandleTabsClick"><el-tab-pane name"1" label"基础设置"><span slot&…

java中的数组(2)

大家好&#xff0c;我们今天继续来看java中数组这方面的知识点&#xff0c;那么话不多说&#xff0c;我们直接开始。 一.数组的使用 1.数组中元素访问 数组在内存中是一段连续的空间,空间的编号都是从0开始的,依次递增,数组可以通过下标访问其任意位置的元素. 也可以进行修改…

在 OAuth 2.0 中,refreshToken(刷新令牌)存在的意义

在 OAuth 2.0 中&#xff0c;refreshToken&#xff08;刷新令牌&#xff09; 的主要目的是为了提升用户体验和安全性&#xff0c;同时确保访问令牌的有效性。以下是需要使用 refreshToken 的原因&#xff1a; 1. 访问令牌的有限生命周期 访问令牌&#xff08;accessToken&…

#渗透测试#红蓝对抗#SRC漏洞挖掘# Yakit(6)进阶模式-Web Fuzzer(下)

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…

Oracle 19c RAC到单机ADG部署及Broker管理配置-最佳实践

一、概述 前面文章列举了几种ADG常见的搭建方式&#xff0c;此处我以最佳的方式作为实践过程演示&#xff1b;架构为RAC到单机&#xff0c;通常这种架构大家用得比较多&#xff1b;这里实践的案例ADG全程是Broker进行管理&#xff0c;Broker其实是ADG非常简单易用的工具&#x…

每日一题 LCR 097. 不同的子序列

LCR 097. 不同的子序列 使用动态规划就可以解决&#xff0c;重点是知道 动态规划的状态是如何转移的 class Solution { public:int numDistinct(string s, string t) {int ns s.size();int nt t.size();vector<vector<long>> dp(ns1,vector<long>(nt1,0)…

如何在 JavaScript 中进行深度克隆?

在 JavaScript 中进行深度克隆&#xff08;deep clone&#xff09;是指创建一个对象的完整副本&#xff0c;并且副本中所有的嵌套对象也被复制&#xff0c;而不是只是引用原始对象中的嵌套对象。深度克隆与浅克隆的主要区别在于&#xff0c;浅克隆只复制对象的引用&#xff0c;…