Skip to content

Spring Boot WebSocket 技术学习笔记 🚀

🎯 什么是 WebSocket?为什么需要它?

在传统的 HTTP 通信中,客户端和服务器之间的交互遵循"请求-响应"模式:客户端发送请求,服务器返回响应,然后连接关闭。这种模式在处理实时通信场景时就显得力不从心了。

NOTE

想象一下聊天应用、股票价格实时更新、在线游戏等场景,如果使用传统 HTTP,客户端需要不断地轮询服务器获取最新数据,这不仅效率低下,还会造成大量无意义的网络请求。

WebSocket 的核心价值

  • 🔄 全双工通信:客户端和服务器可以同时向对方发送数据
  • 低延迟:建立连接后,数据传输无需 HTTP 头部开销
  • 💾 持久连接:一次握手,长期通信
  • 🎯 实时性:服务器可以主动推送数据给客户端

🏗️ Spring Boot WebSocket 架构原理

Spring Boot 为 WebSocket 提供了开箱即用的自动配置支持,让开发者能够快速构建实时通信应用。

📦 Spring Boot WebSocket 核心组件

1. 自动配置支持

Spring Boot 为以下嵌入式服务器提供了 WebSocket 自动配置:

  • Tomcat - 默认嵌入式服务器
  • Jetty - 轻量级选择
  • Undertow - 高性能服务器

TIP

如果你部署 WAR 文件到独立容器,Spring Boot 会假设容器负责 WebSocket 配置。这种设计体现了 Spring Boot "约定优于配置" 的哲学。

2. 依赖配置

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
xml
<!-- 基础WebFlux依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

<!-- WebSocket API支持 -->
<dependency>
    <groupId>jakarta.websocket</groupId>
    <artifactId>jakarta.websocket-api</artifactId>
</dependency>

💻 实战代码示例

基础 WebSocket 配置

kotlin
@Configuration
@EnableWebSocket
class WebSocketConfig : WebSocketConfigurer {
    
    override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) {
        // 注册WebSocket处理器,支持跨域
        registry.addHandler(ChatWebSocketHandler(), "/chat")
            .setAllowedOrigins("*") 
    }
}

WARNING

生产环境中不要使用 setAllowedOrigins("*"),应该明确指定允许的域名以确保安全性。

WebSocket 消息处理器

kotlin
@Component
class ChatWebSocketHandler : TextWebSocketHandler() {
    
    // 存储活跃的WebSocket会话
    private val sessions = mutableSetOf<WebSocketSession>()
    
    override fun afterConnectionEstablished(session: WebSocketSession) {
        sessions.add(session) 
        println("新用户连接: ${session.id}")
        
        // 向新用户发送欢迎消息
        session.sendMessage(TextMessage("欢迎加入聊天室!"))
    }
    
    override fun handleTextMessage(session: WebSocketSession, message: TextMessage) {
        val payload = message.payload
        println("收到消息: $payload")
        
        // 广播消息给所有连接的用户
        broadcastMessage("用户${session.id}: $payload") 
    }
    
    override fun afterConnectionClosed(session: WebSocketSession, status: CloseStatus) {
        sessions.remove(session) 
        println("用户断开连接: ${session.id}")
    }
    
    private fun broadcastMessage(message: String) {
        val textMessage = TextMessage(message)
        sessions.forEach { session ->
            try {
                if (session.isOpen) {
                    session.sendMessage(textMessage)
                }
            } catch (e: Exception) {
                println("发送消息失败: ${e.message}") 
            }
        }
    }
}

STOMP 协议支持(推荐方式)

kotlin
@Configuration
@EnableWebSocketMessageBroker
class StompWebSocketConfig : WebSocketMessageBrokerConfigurer {
    
    override fun configureMessageBroker(config: MessageBrokerRegistry) {
        // 启用简单消息代理,处理以"/topic"开头的消息
        config.enableSimpleBroker("/topic", "/queue") 
        // 设置应用程序消息前缀
        config.setApplicationDestinationPrefixes("/app")
    }
    
    override fun registerStompEndpoints(registry: StompEndpointRegistry) {
        // 注册STOMP端点,启用SockJS支持
        registry.addEndpoint("/ws")
            .setAllowedOriginPatterns("*")
            .withSockJS() 
    }
}

消息控制器

kotlin
@Controller
class ChatController {
    
    @MessageMapping("/chat.send") // 处理客户端发送到 /app/chat.send 的消息
    @SendTo("/topic/public") // 将返回值广播到 /topic/public
    fun sendMessage(chatMessage: ChatMessage): ChatMessage {
        return chatMessage.copy(
            timestamp = System.currentTimeMillis(),
            content = "处理后的消息: ${chatMessage.content}"
        ) 
    }
    
    @MessageMapping("/chat.join")
    @SendTo("/topic/public")
    fun joinChat(chatMessage: ChatMessage): ChatMessage {
        return chatMessage.copy(
            type = MessageType.JOIN,
            content = "${chatMessage.sender} 加入了聊天室"
        )
    }
}

data class ChatMessage(
    val type: MessageType = MessageType.CHAT,
    val content: String = "",
    val sender: String = "",
    val timestamp: Long = 0L
)

enum class MessageType {
    CHAT, JOIN, LEAVE
}

🔄 WebSocket vs 传统 HTTP 对比

kotlin
@RestController
class TraditionalChatController {
    
    private val messages = mutableListOf<ChatMessage>()
    
    @GetMapping("/messages")
    fun getMessages(@RequestParam lastId: Long?): List<ChatMessage> {
        // 客户端需要不断轮询获取新消息
        return messages.filter { it.timestamp > (lastId ?: 0) } 
    }
    
    @PostMapping("/messages")
    fun sendMessage(@RequestBody message: ChatMessage) {
        messages.add(message.copy(timestamp = System.currentTimeMillis()))
        // 无法主动推送给其他客户端
    }
}
kotlin
@Controller
class WebSocketChatController {
    
    @MessageMapping("/chat.send")
    @SendTo("/topic/public")
    fun sendMessage(message: ChatMessage): ChatMessage {
        // 消息立即广播给所有订阅者
        return message.copy(timestamp = System.currentTimeMillis()) 
    }
    
    // 服务器可以主动推送消息
    @Scheduled(fixedRate = 30000)
    fun sendHeartbeat() {
        messagingTemplate.convertAndSend("/topic/heartbeat", "ping") 
    }
}

🌟 实际业务场景应用

1. 实时聊天系统

kotlin
@Service
class ChatService(
    private val messagingTemplate: SimpMessagingTemplate
) {
    
    fun sendPrivateMessage(fromUser: String, toUser: String, content: String) {
        val message = ChatMessage(
            sender = fromUser,
            content = content,
            timestamp = System.currentTimeMillis()
        )
        
        // 发送私聊消息到特定用户
        messagingTemplate.convertAndSendToUser(
            toUser, 
            "/queue/private", 
            message
        ) 
    }
    
    fun broadcastSystemMessage(content: String) {
        val systemMessage = ChatMessage(
            sender = "系统",
            content = content,
            type = MessageType.SYSTEM
        )
        
        // 广播系统消息
        messagingTemplate.convertAndSend("/topic/system", systemMessage)
    }
}

2. 实时数据推送

kotlin
@Service
class StockPriceService(
    private val messagingTemplate: SimpMessagingTemplate
) {
    
    @Scheduled(fixedRate = 1000) // 每秒更新一次
    fun pushStockPrices() {
        val prices = generateRandomStockPrices()
        
        prices.forEach { (symbol, price) ->
            messagingTemplate.convertAndSend(
                "/topic/stock/$symbol", 
                StockPrice(symbol, price, System.currentTimeMillis())
            ) 
        }
    }
    
    private fun generateRandomStockPrices(): Map<String, Double> {
        return mapOf(
            "AAPL" to (150.0 + Random.nextDouble(-5.0, 5.0)),
            "GOOGL" to (2800.0 + Random.nextDouble(-50.0, 50.0))
        )
    }
}

data class StockPrice(
    val symbol: String,
    val price: Double,
    val timestamp: Long
)

⚡ 性能优化与最佳实践

1. 连接管理

kotlin
@Component
class WebSocketSessionManager {
    
    private val sessions = ConcurrentHashMap<String, WebSocketSession>()
    private val userSessions = ConcurrentHashMap<String, String>()
    
    fun addSession(userId: String, session: WebSocketSession) {
        sessions[session.id] = session
        userSessions[userId] = session.id 
    }
    
    fun removeSession(sessionId: String) {
        sessions.remove(sessionId)
        userSessions.values.removeIf { it == sessionId }
    }
    
    fun getActiveSessionCount(): Int = sessions.size
    
    // 清理无效连接
    @Scheduled(fixedRate = 60000)
    fun cleanupInactiveSessions() {
        sessions.entries.removeIf { (_, session) ->
            !session.isOpen.also { isOpen ->
                if (!isOpen) println("清理无效连接: ${session.id}") 
            }
        }
    }
}

2. 消息限流

kotlin
@Component
class MessageRateLimiter {
    
    private val rateLimiters = ConcurrentHashMap<String, RateLimiter>()
    
    fun isAllowed(sessionId: String): Boolean {
        val limiter = rateLimiters.computeIfAbsent(sessionId) {
            RateLimiter.create(10.0) // 每秒最多10条消息
        }
        
        return limiter.tryAcquire().also { allowed ->
            if (!allowed) {
                println("用户 $sessionId 发送消息过于频繁") 
            }
        }
    }
}

🛡️ 安全考虑

IMPORTANT

WebSocket 连接的安全性至关重要,以下是几个关键的安全实践:

1. 身份验证

kotlin
@Configuration
class WebSocketSecurityConfig {
    
    @Bean
    fun webSocketAuthenticationInterceptor(): HandshakeInterceptor {
        return object : HandshakeInterceptor {
            override fun beforeHandshake(
                request: ServerHttpRequest,
                response: ServerHttpResponse,
                wsHandler: WebSocketHandler,
                attributes: MutableMap<String, Any>
            ): Boolean {
                // 验证JWT token或其他认证信息
                val token = request.headers.getFirst("Authorization")
                return validateToken(token).also { isValid ->
                    if (!isValid) {
                        response.setStatusCode(HttpStatus.UNAUTHORIZED) 
                    }
                }
            }
            
            override fun afterHandshake(
                request: ServerHttpRequest,
                response: ServerHttpResponse,
                wsHandler: WebSocketHandler,
                exception: Exception?
            ) {
                // 握手后的处理
            }
        }
    }
    
    private fun validateToken(token: String?): Boolean {
        // 实现token验证逻辑
        return token?.startsWith("Bearer ") == true
    }
}

🚀 总结

Spring Boot WebSocket 技术为我们提供了构建实时通信应用的强大工具。通过其自动配置和丰富的功能支持,我们可以轻松实现:

实时双向通信 - 突破传统HTTP的限制
高性能消息传递 - 减少网络开销和延迟
灵活的消息路由 - 支持点对点和广播模式
企业级安全性 - 完整的认证和授权机制

TIP

在选择WebSocket还是传统HTTP时,考虑以下因素:

  • 是否需要实时性?
  • 消息频率如何?
  • 是否需要服务器主动推送?
  • 客户端复杂度是否可接受?

通过合理使用 Spring Boot WebSocket,你可以构建出用户体验优秀的实时应用,如聊天系统、实时监控、在线协作工具等。记住,技术的价值在于解决实际问题,WebSocket 正是为实时通信场景而生的完美解决方案! 🎉