# SpringBoot中怎么利用WebSocket實現即時消息
## 1. WebSocket技術概述
### 1.1 WebSocket與傳統HTTP協議對比
WebSocket是一種在單個TCP連接上進行全雙工通信的協議,與傳統的HTTP協議有著本質區別:
| 特性 | WebSocket | HTTP |
|--------------------|-------------------------------|--------------------------|
| 通信模式 | 全雙工 | 半雙工 |
| 連接持續時間 | 持久連接 | 短連接(請求-響應后關閉)|
| 數據格式 | 二進制幀/文本幀 | 文本報文 |
| 頭部開銷 | 首次握手后頭部極小 | 每個請求都攜帶完整頭部 |
| 服務器推送能力 | 支持主動推送 | 僅能響應客戶端請求 |
| 適用場景 | 實時應用(聊天、游戲等) | 傳統Web應用 |
### 1.2 WebSocket協議握手過程
WebSocket建立連接需要經過標準的握手流程:
1. **HTTP升級請求**:客戶端發送包含`Upgrade: websocket`頭的HTTP請求
```http
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Spring Framework從4.0版本開始提供完整的WebSocket支持,主要包括:
<dependencies>
<!-- WebSocket支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- 前端模板引擎(可選) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- 測試依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/ws")
.setAllowedOrigins("*")
.addInterceptors(new HttpSessionHandshakeInterceptor());
}
@Bean
public WebSocketHandler myHandler() {
return new MyWebSocketHandler();
}
}
public class MyWebSocketHandler extends TextWebSocketHandler {
private static final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) {
sessions.add(session);
System.out.println("Connection established: " + session.getId());
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
String payload = message.getPayload();
System.out.println("Received: " + payload);
// 廣播消息給所有客戶端
for (WebSocketSession s : sessions) {
try {
s.sendMessage(new TextMessage("Echo: " + payload));
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
sessions.remove(session);
System.out.println("Connection closed: " + session.getId());
}
}
afterConnectionEstablished
被調用handleTextMessage
處理文本消息handleTransportError
處理傳輸錯誤afterConnectionClosed
清理資源@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(8192);
container.setMaxBinaryMessageBufferSize(8192);
return container;
}
registry.addHandler(myHandler(), "/ws")
.setAllowedOrigins("*")
.addInterceptors(new HttpSessionHandshakeInterceptor())
.withSockJS()
.setHeartbeatTime(25000); // 25秒心跳
STOMP(Simple Text Oriented Messaging Protocol)提供了基于幀的互操作格式:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketStompConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
// 啟用簡單內存消息代理
config.enableSimpleBroker("/topic", "/queue");
// 配置應用目的地前綴
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-stomp")
.setAllowedOrigins("*")
.withSockJS();
}
}
@Controller
public class StompController {
@MessageMapping("/chat.send")
@SendTo("/topic/public")
public ChatMessage sendMessage(@Payload ChatMessage chatMessage) {
return chatMessage;
}
@MessageMapping("/chat.addUser")
@SendTo("/topic/public")
public ChatMessage addUser(@Payload ChatMessage chatMessage,
SimpMessageHeaderAccessor headerAccessor) {
headerAccessor.getSessionAttributes().put("username", chatMessage.getSender());
return chatMessage;
}
}
@Configuration
@EnableWebSocketMessageBroker
public class RabbitMQWebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableStompBrokerRelay("/topic", "/queue")
.setRelayHost("localhost")
.setRelayPort(61613)
.setClientLogin("guest")
.setClientPasscode("guest");
config.setApplicationDestinationPrefixes("/app");
}
}
const socket = new WebSocket('ws://localhost:8080/ws');
socket.onopen = function(e) {
console.log('Connection established');
socket.send('Hello Server!');
};
socket.onmessage = function(event) {
console.log(`Data received: ${event.data}`);
};
socket.onclose = function(event) {
if (event.wasClean) {
console.log(`Connection closed cleanly, code=${event.code}`);
} else {
console.log('Connection died');
}
};
const socket = new SockJS('/ws-stomp');
const stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/public', function(message) {
showMessage(JSON.parse(message.body));
});
});
function sendMessage() {
const message = {
content: $('#message').val(),
sender: $('#username').val()
};
stompClient.send("/app/chat.send", {}, JSON.stringify(message));
}
let reconnectAttempts = 0;
const maxReconnectAttempts = 5;
const reconnectDelay = 5000;
function connect() {
stompClient.connect({},
function(frame) {
reconnectAttempts = 0;
// 訂閱邏輯...
},
function(error) {
if(reconnectAttempts < maxReconnectAttempts) {
setTimeout(connect, reconnectDelay);
reconnectAttempts++;
}
}
);
}
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.ignoringAntMatchers("/ws/**", "/ws-stomp/**");
}
}
@Configuration
@EnableWebSocketSecurity
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.simpDestMatchers("/app/**").authenticated()
.simpSubscribeDestMatchers("/user/**").authenticated()
.anyMessage().permitAll();
}
}
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxSessionIdleTimeout(600000L); // 10分鐘
container.setMaxSessionsPerPrincipal(5); // 每個用戶最多5個連接
return container;
}
@Configuration
@EnableRedisRepositories
public class RedisConfig {
@Bean
public RedisMessageListenerContainer redisContainer(RedisConnectionFactory factory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(factory);
return container;
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
return template;
}
}
@SpringBootTest
@WebAppConfiguration
public class WebSocketTest {
@Autowired
private WebSocketHandler handler;
@Test
void testWebSocketHandler() throws Exception {
TestWebSocketSession session = new TestWebSocketSession();
// 測試連接建立
handler.afterConnectionEstablished(session);
assertTrue(session.isOpen());
// 測試消息處理
TextMessage message = new TextMessage("test");
handler.handleMessage(session, message);
assertEquals(1, session.getSentMessages().size());
assertEquals("Echo: test", session.getSentMessages().get(0).getPayload());
}
}
# application.properties
management.endpoints.web.exposure.include=health,info,metrics,websockettrace
management.endpoint.websockettrace.enabled=true
src/main/
├── java/
│ └── com/example/chat/
│ ├── config/ # 配置類
│ ├── controller/ # MVC控制器
│ ├── dto/ # 數據傳輸對象
│ ├── handler/ # WebSocket處理器
│ └── service/ # 業務服務
└── resources/
├── static/ # 靜態資源
├── templates/ # 模板文件
└── application.yml # 配置文件
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ChatMessage {
private MessageType type;
private String content;
private String sender;
public enum MessageType {
CHAT, JOIN, LEAVE
}
}
@Controller
public class ChatController {
@MessageMapping("/chat.sendMessage")
@SendTo("/topic/public")
public ChatMessage sendMessage(@Payload ChatMessage chatMessage) {
return chatMessage;
}
@MessageMapping("/chat.addUser")
@SendTo("/topic/public")
public ChatMessage addUser(@Payload ChatMessage chatMessage,
SimpMessageHeaderAccessor headerAccessor) {
headerAccessor.getSessionAttributes().put("username", chatMessage.getSender());
return chatMessage;
}
@SubscribeMapping("/topic/activeUsers")
public List<String> getActiveUsers() {
// 返回當前活躍用戶列表
return activeUserService.getActiveUsers();
}
}
問題現象:WebSocket連接無法建立
排查步驟: 1. 檢查瀏覽器控制臺錯誤 2. 驗證服務端是否正常啟動 3. 使用Postman測試WebSocket端點 4. 檢查防火墻/網絡策略
典型場景:高并發下連接不穩定
優化方案:
1. 增加心跳間隔:setHeartbeatValue(new long[]{10000,10000})
2. 使用Nginx負載均衡:
location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
Android實現示例:
val wsUri = "ws://10.0.2.2:8080/ws"
val websocket = OkHttpClient().newWebSocket(
Request.Builder().url(wsUri).build(),
object : WebSocketListener() {
override fun onMessage(webSocket: WebSocket, text: String) {
// 處理消息
}
}
)
WebSocket技術為SpringBoot應用提供了強大的實時通信能力,本文詳細介紹了:
未來發展趨勢: - WebTransport協議替代方案 - QUIC協議支持 - 更好的移動端體驗
最佳實踐建議: - 對于簡單場景使用原生WebSocket API - 復雜消息系統推薦STOMP協議 - 生產環境務必配置消息代理集群 - 始終考慮向后兼容性(SockJS)
附錄: - 完整示例代碼倉庫 - WebSocket RFC文檔 - Spring官方文檔 “`
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。