首页/新闻资讯/正文详情

WebSocket基本知识(附带@ServerEndpoint注解)

发布时间:2026/11/6 7:51:32 来源:云帆数科 栏目:资讯中心
WebSocket基本知识(附带@ServerEndpoint注解)
WebSocket是一种在单个 TCP 连接上进行全双工通讯的协议。什么意思呢我们之前学过html协议它是一个单工/半双工协议他只能由客户端发起请求服务端没有办法主动给客户端发送消息。如果出现服务器需要主动发送消息的情况比如实时语音转写需要服务端不断的往客户端传送语音转换之后的文字那么处理起来就会很麻烦。用客户端的Ajax轮询技术不仅浪费资源而且实时性也不强。html还有一个缺点就是每次响应结束之后连接都会断开下次还要再次建立TCP协议html连接很多时候又带有很长的头部可能有效的信息并不多就会很浪费资源。WebSocket就解决了这个问题服务端可以主动给客户端传送消息底层TCP连接可以复用连接时不受浏览器同源策略的限制。服务端和客户端只要完成一次握手就可以实现平等双向长连接。一、基本概念1生命周期WebSocket的生命周期主要分为三部分建立连接也就是所谓的“握手”数据传输服务端和客户端都可以进行双向的通信结束连接任意一方断开都可以。2基本用法我们这里主要介绍Java Spring后端的基本用法。首先我们需要一个配置文件Configuration EnableWebSocket public class RawWebSocketConfig implements WebSocketConfigurer { private final ChatWebSocketHandler chatHandler; public RawWebSocketConfig(ChatWebSocketHandler chatHandler) { this.chatHandler chatHandler; } Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(chatHandler, /ws/chat) .addInterceptors(new AuthHandshakeInterceptor()) .setAllowedOriginPatterns(https://example.com, https://*.example.com); } }注意不加任何 origin 配置时默认只允许同源。一个 handler 可以注册到多个路径。编写handlerComponent public class ChatWebSocketHandler extends TextWebSocketHandler { private static final Logger log LoggerFactory.getLogger(ChatWebSocketHandler.class); /** sessionId - session注意线程安全 */ private final MapString, WebSocketSession sessions new ConcurrentHashMap(); Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { sessions.put(session.getId(), session); log.info(连接建立 id{}, remote{}, session.getId(), session.getRemoteAddress()); session.sendMessage(new TextMessage({\type\:\welcome\,\id\:\ session.getId() \})); } Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { String payload message.getPayload(); log.info(收到 {} - {}, session.getId(), payload); // 简单回显 广播 for (WebSocketSession s : sessions.values()) { if (s.isOpen()) { s.sendMessage(new TextMessage(payload)); } } } Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { log.error(传输异常 id{}, session.getId(), exception); if (session.isOpen()) { session.close(CloseStatus.SERVER_ERROR); } } Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { sessions.remove(session.getId()); log.info(连接关闭 id{}, status{}, session.getId(), status); } /** 需要处理分片消息时打开 */ Override public boolean supportsPartialMessages() { return false; } }这里需要注意并发发送必须包装因为WebSocketSession不是线程安全的。多个线程同时sendMessage会抛IllegalStateException: The remote endpoint was in state [TEXT_FULL_WRITING]。正确做法用ConcurrentWebSocketSessionDecorator包装。Override public void afterConnectionEstablished(WebSocketSession session) { // 发送超时 5s缓冲区上限 512KB超出则关闭连接 WebSocketSession safeSession new ConcurrentWebSocketSessionDecorator(session, 5000, 512 * 1024); sessions.put(session.getId(), safeSession); }3心跳机制STOMP 心跳是应用层的\n字节与 WebSocket 协议层 PING/PONG 不同。协商过程客户端 CONNECT: heart-beat:10000,10000 我愿每 10s 发也要求你每 10s 发 服务端 CONNECTED: heart-beat:10000,10000客户端未按时发心跳服务端在heartbeat * 2后关闭连接。Nginx 默认proxy_read_timeout 60s如果心跳间隔 30s 就会被代理掐断—— 心跳设 10s 是安全的。二、ServerEndpoint注解ServerEndpoint是实例模型。它并不像 Spring 的 Controller 那样是一个单例一个连接就会创建一个端点实例ServerEndpoint是JSR-356Java API for WebSocket​ 的标准注解包路径jakarta.websocket.server.ServerEndpointSpring Boot 3或javax.websocket.server.ServerEndpointBoot 2。它不属于 Spring实现由 Servlet 容器提供Tomcat 的WsServerContainer、Jetty、Undertow。一个类加了这个注解容器在启动时就会把它注册成一个 WebSocket 端点替代了 Spring 里的WebSocketConfigurerWebSocketHandler那套。四个生命周期注解ServerEndpoint(/chat/{roomId}) public class ChatEndpoint { OnOpen public void onOpen(Session session, PathParam(roomId) String roomId, EndpointConfig config) { } OnMessage public String onMessage(String text, Session session) { return echo: text; } OnMessage public void onBinary(ByteBuffer data, Session session, boolean last) { } OnError public void onError(Session session, Throwable error) { } OnClose public void onClose(Session session, CloseReason reason) { } }OnMessage可以写多个一个收String、一个收byte[]、一个收PongMessage容器按消息类型分派。这是 JSR-356 比 Spring 原生 API 方便的地方。接下来是 Spring Boot 里最容易出问题的地方ServerEndpoint类默认不由 Spring 管理所以Autowired的字段全是null。关键问题问题一端点不会被注册内嵌容器jar 方式运行时Tomcat 的WsSci扫描器不会自动执行必须显式声明Configuration public class WsConfig { Bean ConditionalOnProperty(name ws.jsr356.enabled, havingValue true) public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }ServerEndpointExporter会扫描 Spring 上下文中所有带ServerEndpoint的类并注册到ServerContainer。war 部署到外部 Tomcat 时不要声明这个 Bean否则容器自己扫一遍、Exporter 再注册一遍启动直接报Duplicate endpoint。问题二Autowired全为 null端点实例由容器反射new出来完全绕过 Spring所以依赖注入失效。有三种解决办法1SpringConfiguratorServerEndpoint(value /ws/{userId}, configurator SpringConfigurator.class) Component // 必须注册成 Bean否则 getBean 失败 public class ChatEndpoint { Autowired private ChatService chatService; // 可以注入了 }SpringConfiguratororg.springframework.web.socket.server.standard.SpringConfigurator重写了getEndpointInstance改成从WebApplicationContext里getBean(clazz)。副作用getBean返回的是单例于是所有连接共享同一个端点实例实例字段不再隔离。这违背了 JSR-356 的「一连接一实例」语义并发写实例字段会出错。只在端点确实无状态时使用。2自定义 Configuratorpublic class SpringBeanConfigurator extends ServerEndpointConfig.Configurator implements ApplicationContextAware { private static ApplicationContext ctx; Override public void setApplicationContext(ApplicationContext context) { SpringBeanConfigurator.ctx context; } Override public T T getEndpointInstance(ClassT clazz) { // 每次连接新建实例但走 Spring 的创建流程Autowired 依然生效 return ctx.getAutowireCapableBeanFactory().createBean(clazz); } }ServerEndpoint(value /ws/{userId}, configurator SpringBeanConfigurator.class) public class ChatEndpoint { // 注意不要加 Component Autowired private ChatService chatService; }createBean而非getBean兼顾了「依赖注入」和「每连接独立实例」两个需求。这是生产环境的正确做法。3静态注入Component public class SpringContextHolder implements ApplicationContextAware { private static ApplicationContext ctx; Override public void setApplicationContext(ApplicationContext c) { ctx c; } public static T T getBean(ClassT t) { return ctx.getBean(t); } } // 端点里 private final ChatService chatService SpringContextHolder.getBean(ChatService.class);简单直接适合小项目但把容器依赖硬编码进业务类可测性差。问题三并发发送异常Session.getBasicRemote()不是线程安全的多线程同时sendText会抛IllegalStateException: The remote endpoint was in state [TEXT_FULL_WRITING]JSR-356 没有ConcurrentWebSocketSessionDecorator需要手动包装public final class SafeSession { private final Session session; private final Object lock new Object(); public SafeSession(Session session) { this.session session; } public void sendText(String text) throws IOException { synchronized (lock) { if (session.isOpen()) { session.getBasicRemote().sendText(text); } } } }或者改用异步发送 队列避免阻塞业务线程session.getAsyncRemote().sendText(text, result - { if (!result.isOK()) { log.warn(发送失败, result.getException()); } });异步 API 同样需要串行化且要自己处理发送结果。

相关推荐

学习笔记(策略梯度)
学习笔记(策略梯度)

前文所述方法都是基于value进行评估,现在我们转向policy,并有值近似转向策略近似。Basic idea of policy gradient在先前,策略是用表格来表示的,我们可以直观地取出或改变表格的值。现在,策略可以用参数方程表示&#… · 2026/11/6 7:51:32

诚迈科技萤火Pi AI开发套件,基于高通跃龙IQ系列,原型到量产一步到位!
诚迈科技萤火Pi AI开发套件,基于高通跃龙IQ系列,原型到量产一步到位!

边缘AI全面爆发,具身智能机器人、工业视觉质检、智能穿戴……AI正从云端快速下沉到设备终端。但算力有了,怎么快速变成能量产的产品?硬件集成、BSP适配、系统裁剪、驱动开发、成像调优、模型部署,这一整套工程链耗时耗人。诚迈科技… · 2026/11/6 7:51:32

解锁稠油高效分离新机制!难题突破:碳酸盐沥青岩稠油 - 固分离效率大幅提升
解锁稠油高效分离新机制!难题突破:碳酸盐沥青岩稠油 - 固分离效率大幅提升

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 7:51:32

企业元宇宙治理框架:技术实现与合规路径
企业元宇宙治理框架:技术实现与合规路径

1. 企业元宇宙治理框架的核心逻辑 企业元宇宙作为数字化转型的新阶段,其治理框架需要兼顾技术创新与合规要求。从技术架构角度看,企业元宇宙本质上是一个由数字孪生、区块链、人工智能等技术构成的复杂系统。这个系统需要解决三个核心问题:如… · 2026/11/6 14:59:51

基于CDC的MySQL实时数据同步方案实践
基于CDC的MySQL实时数据同步方案实践

1. 项目背景与核心价值 在数据驱动的业务场景中,数据库表间数据同步是个高频需求。我最近在电商订单系统中就遇到了这个问题:订单主表数据变更后,需要实时同步到统计分析表和风控表。传统的手动同步或定时任务方式存在延迟高、易出错的问题&a… · 2026/11/6 14:00:34

JSP农贸交易系统开发与智能撮合算法实践
JSP农贸交易系统开发与智能撮合算法实践

## 1. 项目概述与核心价值农贸产品交易系统是连接农产品生产端与消费端的重要数字化桥梁。这个基于JSP技术的交易平台(项目编号g3608)完整包含了程序源码、数据库设计、部署指南和开发环境配置,是一套开箱即用的农产品B2B解决方案。我在参与某… · 2026/11/6 14:00:34

2025年3D建模工具选型实战指南:按工作流断点匹配生产力
2025年3D建模工具选型实战指南:按工作流断点匹配生产力

1. 这不是选软件,是选你未来三年的“数字手”——为什么2025年建模工具选择比以往任何时候都更关键 2025年做3D建模,你面对的早已不是“用哪个软件画个模型”这么简单的事。它直接决定你能不能接单、能不能按时交付、能不能把想法一秒变现实、甚至决定你… · 2026/11/6 14:00:34

Qt飞机大战源码解析:QGraphicsView游戏循环与碰撞检测实战
Qt飞机大战源码解析:QGraphicsView游戏循环与碰撞检测实战

简介:这份资源是面向Qt初学者与游戏开发入门者的「飞机大战」完整项目源码包,围绕Qt框架在经典空战游戏中的实际应用展开,可用于课程设计、自学练手或二次开发参考。压缩包共223个文件,约6.21MB,以105个C头文件与32个c… · 2026/11/6 14:00:34

老旧CMS的XSS与getshell风险防护及应急治理
老旧CMS的XSS与getshell风险防护及应急治理

项目中遇到过不少次类似的场面:群里忽然冒出一条消息,标题写得耸人听闻,核心关键词是 XSS 、 0day 、 通杀所有版本 ,还带着一个"可 getshell"的尾巴,配图往往是半个命令行窗口。消息传得飞快&#xf… · 2026/11/6 13:56:30

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:27:59

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:03

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 10:29:23

三菱PLC数据采集到数据库的时序设计:Python+MC协议实战
三菱PLC数据采集到数据库的时序设计:Python+MC协议实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 1:02:11

ASIC主模式RTL设计:状态机与三态驱动的流片级实现
ASIC主模式RTL设计:状态机与三态驱动的流片级实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 1:02:11

STM32嵌入式C++开发:GDB调试与Renode仿真实战指南
STM32嵌入式C++开发:GDB调试与Renode仿真实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 1:02:11

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:27:59

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:03

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 10:29:23

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码