WebSocket 解決 HTTP 輪詢高延遲、高資源消耗痛點,實現真雙向長連接。本文涵蓋協議握手原理、Spring 4 整合架構、聊天室核心功能(群發/私聊)、SockJS 降級方案與前端完整代碼。
| 方案 | 機制 | 缺點 | 資源消耗 |
|---|---|---|---|
| Ajax 輪詢 | 客戶端定時發 Request | 延遲高、大量無效請求、HTTP Header 重複開銷 | 極高(頻繁建連/斷連) |
| Long Polling | 請求掛起直到有數據返回 | 佔用連接數、服務端並發壓力大、仍基於 HTTP 無狀態 | 高(長連接佔用線程) |
| WebSocket | 一次 HTTP 握手升級為 TCP 長連接 | 瀏覽器兼容性(IE10+)、代理/防火牆可能攔截 | 極低(單連接雙向通訊) |
// 客戶端發起升級請求
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: https://example.com
// 服務端響應 101 協議切換
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
關鍵 Header:Upgrade: websocket 宣告協議升級,Sec-WebSocket-Key 防劫持,Sec-WebSocket-Accept 服務端驗證回應。
pom.xml 關鍵依賴
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.3.0</version>
</dependency>
public class HandshakeInterceptor implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Map<String, Object> attributes) {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
String clientName = servletRequest.getServletRequest().getParameter("name");
HttpSession session = servletRequest.getServletRequest().getSession(true);
if (session != null) {
attributes.put("WEBSOCKET_USERNAME", clientName); // 存入握手屬性
}
}
return true; // 繼續握手
}
@Override public void afterHandshake(...) {}
}
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 原生 WebSocket 端點
registry.addHandler(new WebSocketHander(), "/echo")
.addInterceptors(new HandshakeInterceptor());
// SockJS 降級端點(不支援 WebSocket 的瀏覽器)
registry.addHandler(new WebSocketHander(), "/sockjs/echo")
.addInterceptors(new HandshakeInterceptor())
.withSockJS();
}
}
public class WebSocketHander implements WebSocketHandler {
private static final Logger logger = LoggerFactory.getLogger(WebSocketHander.class);
private static final List<WebSocketSession> users = new CopyOnWriteArrayList<>(); // 線程安全
@Override
public void afterConnectionEstablished(WebSocketSession session) {
String userName = (String) session.getHandshakeAttributes().get("WEBSOCKET_USERNAME");
if (userName != null) {
users.add(session);
session.sendMessage(new TextMessage("歡迎來到聊天室,我們開始聊天吧!~"));
}
}
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) {
String sender = (String) session.getHandshakeAttributes().get("WEBSOCKET_USERNAME");
String payload = message.getPayload().toString();
String[] parts = payload.split("\\|", 2); // 格式:target|content
if (parts.length == 2) { // 私聊
sendMessageToUser(parts[0], new TextMessage(sender + " 悄悄地對你說:" + parts[1]));
} else { // 群發
sendMessageToUsers(new TextMessage(sender + " 說:" + payload));
}
}
@Override
public void handleTransportError(WebSocketSession session, Throwable ex) {
if (session.isOpen()) session.close();
users.remove(session);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
users.remove(session);
}
@Override public boolean supportsPartialMessages() { return false; }
// 群發
private void sendMessageToUsers(TextMessage msg) {
for (WebSocketSession user : users) {
if (user.isOpen()) {
try { user.sendMessage(msg); } catch (IOException e) { logger.error("發送失敗", e); }
}
}
}
// 私聊
private void sendMessageToUser(String targetUser, TextMessage msg) {
for (WebSocketSession user : users) {
String name = (String) user.getHandshakeAttributes().get("WEBSOCKET_USERNAME");
if (targetUser.equals(name) && user.isOpen()) {
try { user.sendMessage(msg); } catch (IOException e) { logger.error("私聊失敗", e); }
break;
}
}
}
}
<script src="https://cdn.sockjs.org/sockjs-0.3.min.js"></script>
<script>
var websocket, chater = '';
function connect() {
var name = $("#client_name").val();
if ("WebSocket" in window) {
websocket = new WebSocket("ws://127.0.0.1:8080/websocket/echo?name=" + name);
} else {
websocket = new SockJS("https://127.0.0.1:8080/websocket/sockjs/echo");
}
websocket.onopen = function() { $("#tou").html("連接成功!"); };
websocket.onmessage = function(e) { $("#msg").append("<br/>" + e.data); };
websocket.onclose = function() { $("#tou").html("連接斷開!"); };
}
function send() {
var msg = $("#message").val();
if (chater) msg = chater + "|" + msg; // 私聊格式
websocket.send(msg);
}
function changeChater(el) { chater = $(el).text(); alert("將與 " + chater + " 私聊"); }
</script>

target|content完整 Maven 專案:https://github.com/leechenxiang/maven-spring-websocket-01