从零构建Spring Boot WebSocket双端通信在线协作白板实战与自动重连机制想象一下这样的场景你和团队成员正在远程协作设计一个产品原型每个人都能实时看到其他人的修改光标移动、图形绘制同步呈现就像在同一块物理白板前工作。这种实时协作体验的核心正是基于WebSocket的双向通信技术。本文将带你从零开始用Spring Boot构建一个完整的在线协作白板系统重点解决实际开发中最棘手的自动重连问题。1. 项目初始化与环境搭建1.1 创建Spring Boot项目首先使用Spring Initializr创建项目选择以下依赖dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-websocket/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies对于客户端项目额外添加Java-WebSocket客户端库dependency groupIdorg.java-websocket/groupId artifactIdJava-WebSocket/artifactId version1.5.3/version /dependency1.2 基础配置服务端需要启用WebSocket支持在启动类添加注解SpringBootApplication EnableWebSocket public class CollaborativeWhiteboardApplication { public static void main(String[] args) { SpringApplication.run(CollaborativeWhiteboardApplication.class, args); } }创建WebSocket配置类注册端点Configuration public class WebSocketConfig { Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }2. 服务端实现协作白板核心逻辑2.1 WebSocket端点设计我们设计一个白板端点处理绘图指令和用户连接ServerEndpoint(/whiteboard) Component public class WhiteboardEndpoint { private static final Logger logger LoggerFactory.getLogger(WhiteboardEndpoint.class); private Session session; private static SetSession sessions Collections.synchronizedSet(new HashSet()); OnOpen public void onOpen(Session session) { this.session session; sessions.add(session); logger.info(新连接加入: {}, session.getId()); } OnClose public void onClose() { sessions.remove(session); logger.info(连接关闭: {}, session.getId()); } OnMessage public void onMessage(String message, Session session) { // 广播绘图指令给所有客户端 broadcast(message); } private void broadcast(String message) { sessions.forEach(s - { try { s.getBasicRemote().sendText(message); } catch (IOException e) { logger.error(消息发送失败, e); } }); } }2.2 白板指令协议设计定义简单的JSON协议表示绘图动作{ type: draw, data: { x1: 100, y1: 200, x2: 150, y2: 250, color: #FF0000, width: 2 } }3. 客户端实现Spring Boot整合WebSocket3.1 客户端连接管理创建带自动重连的客户端管理器public class WhiteboardClient extends WebSocketClient { private static final Logger logger LoggerFactory.getLogger(WhiteboardClient.class); private final AtomicBoolean shouldReconnect new AtomicBoolean(true); private final ScheduledExecutorService executor Executors.newSingleThreadScheduledExecutor(); public WhiteboardClient(URI serverUri) { super(serverUri); } Override public void onOpen(ServerHandshake handshakedata) { logger.info(连接已建立); // 初始化白板UI initWhiteboardUI(); } Override public void onMessage(String message) { // 处理服务端发来的绘图指令 handleDrawingInstruction(message); } Override public void onClose(int code, String reason, boolean remote) { logger.warn(连接关闭: {}, reason); if (shouldReconnect.get()) { executor.schedule(this::reconnect, 3, TimeUnit.SECONDS); } } Override public void onError(Exception ex) { logger.error(连接错误, ex); } private void reconnect() { try { logger.info(尝试重新连接...); this.reconnectBlocking(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }3.2 与Spring Boot集成将客户端作为Spring Bean管理Component public class WhiteboardClientInitializer { private static final String WS_URI ws://localhost:8080/whiteboard; PostConstruct public void init() { try { WhiteboardClient client new WhiteboardClient(new URI(WS_URI)); client.connect(); } catch (URISyntaxException e) { throw new RuntimeException(无效的WebSocket URI, e); } } }4. 高级功能增强型自动重连机制4.1 指数退避重连策略改进重连逻辑避免频繁重试private void scheduleReconnect() { long delay (long) Math.min(30, Math.pow(2, reconnectAttempts.get())) * 1000; logger.info(将在{}秒后尝试重连, delay/1000); executor.schedule(this::doReconnect, delay, TimeUnit.MILLISECONDS); reconnectAttempts.incrementAndGet(); }4.2 心跳检测机制添加心跳保持连接活跃private void startHeartbeat() { executor.scheduleAtFixedRate(() - { if (isOpen()) { try { send({\type\:\heartbeat\}); } catch (Exception e) { logger.warn(心跳发送失败, e); } } }, 0, 30, TimeUnit.SECONDS); }4.3 网络状态感知监听网络变化触发重连private void setupNetworkListener() { Runtime.getRuntime().addShutdownHook(new Thread(() - { shouldReconnect.set(false); executor.shutdown(); })); // 简单模拟网络状态检测 executor.scheduleAtFixedRate(() - { if (!isOpen() !isConnecting() shouldReconnect.get()) { scheduleReconnect(); } }, 1, 1, TimeUnit.MINUTES); }5. 前端集成与完整协作体验5.1 简单HTML5白板界面!DOCTYPE html html head title协作白板/title style #whiteboard { border: 1px solid #000; cursor: crosshair; } /style /head body canvas idwhiteboard width800 height600/canvas script const canvas document.getElementById(whiteboard); const ctx canvas.getContext(2d); const socket new WebSocket(ws://localhost:8080/whiteboard); // 绘图逻辑和WebSocket事件处理 /script /body /html5.2 消息处理与同步socket.onmessage function(event) { const instruction JSON.parse(event.data); if (instruction.type draw) { const {x1, y1, x2, y2, color, width} instruction.data; ctx.strokeStyle color; ctx.lineWidth width; ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke(); } };6. 部署与性能考量6.1 服务端配置优化调整WebSocket相关参数# application.properties server.servlet.context-path/api spring.websocket.max-text-message-buffer-size512KB spring.websocket.max-binary-message-buffer-size512KB6.2 负载均衡与集群在多个实例间同步白板状态Configuration EnableScheduling public class WhiteboardSyncConfig { Autowired private SimpMessagingTemplate messagingTemplate; Scheduled(fixedRate 5000) public void syncSessions() { messagingTemplate.convertAndSend(/topic/sessionSync, getActiveSessions()); } }6.3 监控与日志添加WebSocket事件监听Bean public ServletServerContainerFactoryBean createWebSocketContainer() { ServletServerContainerFactoryBean container new ServletServerContainerFactoryBean(); container.setMaxSessionIdleTimeout(600000L); container.setAsyncSendTimeout(5000L); return container; }在实际项目中我们还需要考虑消息压缩、二进制传输优化等问题。对于大规模协作场景可以采用分层广播策略只将变更发送给相关用户而非全量广播。
别再只写服务端了!Spring Boot WebSocket 完整双端通信与自动重连保姆级教程
从零构建Spring Boot WebSocket双端通信在线协作白板实战与自动重连机制想象一下这样的场景你和团队成员正在远程协作设计一个产品原型每个人都能实时看到其他人的修改光标移动、图形绘制同步呈现就像在同一块物理白板前工作。这种实时协作体验的核心正是基于WebSocket的双向通信技术。本文将带你从零开始用Spring Boot构建一个完整的在线协作白板系统重点解决实际开发中最棘手的自动重连问题。1. 项目初始化与环境搭建1.1 创建Spring Boot项目首先使用Spring Initializr创建项目选择以下依赖dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-websocket/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies对于客户端项目额外添加Java-WebSocket客户端库dependency groupIdorg.java-websocket/groupId artifactIdJava-WebSocket/artifactId version1.5.3/version /dependency1.2 基础配置服务端需要启用WebSocket支持在启动类添加注解SpringBootApplication EnableWebSocket public class CollaborativeWhiteboardApplication { public static void main(String[] args) { SpringApplication.run(CollaborativeWhiteboardApplication.class, args); } }创建WebSocket配置类注册端点Configuration public class WebSocketConfig { Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }2. 服务端实现协作白板核心逻辑2.1 WebSocket端点设计我们设计一个白板端点处理绘图指令和用户连接ServerEndpoint(/whiteboard) Component public class WhiteboardEndpoint { private static final Logger logger LoggerFactory.getLogger(WhiteboardEndpoint.class); private Session session; private static SetSession sessions Collections.synchronizedSet(new HashSet()); OnOpen public void onOpen(Session session) { this.session session; sessions.add(session); logger.info(新连接加入: {}, session.getId()); } OnClose public void onClose() { sessions.remove(session); logger.info(连接关闭: {}, session.getId()); } OnMessage public void onMessage(String message, Session session) { // 广播绘图指令给所有客户端 broadcast(message); } private void broadcast(String message) { sessions.forEach(s - { try { s.getBasicRemote().sendText(message); } catch (IOException e) { logger.error(消息发送失败, e); } }); } }2.2 白板指令协议设计定义简单的JSON协议表示绘图动作{ type: draw, data: { x1: 100, y1: 200, x2: 150, y2: 250, color: #FF0000, width: 2 } }3. 客户端实现Spring Boot整合WebSocket3.1 客户端连接管理创建带自动重连的客户端管理器public class WhiteboardClient extends WebSocketClient { private static final Logger logger LoggerFactory.getLogger(WhiteboardClient.class); private final AtomicBoolean shouldReconnect new AtomicBoolean(true); private final ScheduledExecutorService executor Executors.newSingleThreadScheduledExecutor(); public WhiteboardClient(URI serverUri) { super(serverUri); } Override public void onOpen(ServerHandshake handshakedata) { logger.info(连接已建立); // 初始化白板UI initWhiteboardUI(); } Override public void onMessage(String message) { // 处理服务端发来的绘图指令 handleDrawingInstruction(message); } Override public void onClose(int code, String reason, boolean remote) { logger.warn(连接关闭: {}, reason); if (shouldReconnect.get()) { executor.schedule(this::reconnect, 3, TimeUnit.SECONDS); } } Override public void onError(Exception ex) { logger.error(连接错误, ex); } private void reconnect() { try { logger.info(尝试重新连接...); this.reconnectBlocking(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }3.2 与Spring Boot集成将客户端作为Spring Bean管理Component public class WhiteboardClientInitializer { private static final String WS_URI ws://localhost:8080/whiteboard; PostConstruct public void init() { try { WhiteboardClient client new WhiteboardClient(new URI(WS_URI)); client.connect(); } catch (URISyntaxException e) { throw new RuntimeException(无效的WebSocket URI, e); } } }4. 高级功能增强型自动重连机制4.1 指数退避重连策略改进重连逻辑避免频繁重试private void scheduleReconnect() { long delay (long) Math.min(30, Math.pow(2, reconnectAttempts.get())) * 1000; logger.info(将在{}秒后尝试重连, delay/1000); executor.schedule(this::doReconnect, delay, TimeUnit.MILLISECONDS); reconnectAttempts.incrementAndGet(); }4.2 心跳检测机制添加心跳保持连接活跃private void startHeartbeat() { executor.scheduleAtFixedRate(() - { if (isOpen()) { try { send({\type\:\heartbeat\}); } catch (Exception e) { logger.warn(心跳发送失败, e); } } }, 0, 30, TimeUnit.SECONDS); }4.3 网络状态感知监听网络变化触发重连private void setupNetworkListener() { Runtime.getRuntime().addShutdownHook(new Thread(() - { shouldReconnect.set(false); executor.shutdown(); })); // 简单模拟网络状态检测 executor.scheduleAtFixedRate(() - { if (!isOpen() !isConnecting() shouldReconnect.get()) { scheduleReconnect(); } }, 1, 1, TimeUnit.MINUTES); }5. 前端集成与完整协作体验5.1 简单HTML5白板界面!DOCTYPE html html head title协作白板/title style #whiteboard { border: 1px solid #000; cursor: crosshair; } /style /head body canvas idwhiteboard width800 height600/canvas script const canvas document.getElementById(whiteboard); const ctx canvas.getContext(2d); const socket new WebSocket(ws://localhost:8080/whiteboard); // 绘图逻辑和WebSocket事件处理 /script /body /html5.2 消息处理与同步socket.onmessage function(event) { const instruction JSON.parse(event.data); if (instruction.type draw) { const {x1, y1, x2, y2, color, width} instruction.data; ctx.strokeStyle color; ctx.lineWidth width; ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke(); } };6. 部署与性能考量6.1 服务端配置优化调整WebSocket相关参数# application.properties server.servlet.context-path/api spring.websocket.max-text-message-buffer-size512KB spring.websocket.max-binary-message-buffer-size512KB6.2 负载均衡与集群在多个实例间同步白板状态Configuration EnableScheduling public class WhiteboardSyncConfig { Autowired private SimpMessagingTemplate messagingTemplate; Scheduled(fixedRate 5000) public void syncSessions() { messagingTemplate.convertAndSend(/topic/sessionSync, getActiveSessions()); } }6.3 监控与日志添加WebSocket事件监听Bean public ServletServerContainerFactoryBean createWebSocketContainer() { ServletServerContainerFactoryBean container new ServletServerContainerFactoryBean(); container.setMaxSessionIdleTimeout(600000L); container.setAsyncSendTimeout(5000L); return container; }在实际项目中我们还需要考虑消息压缩、二进制传输优化等问题。对于大规模协作场景可以采用分层广播策略只将变更发送给相关用户而非全量广播。