Appearance
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 正是为实时通信场景而生的完美解决方案! 🎉