傳統 HTTP 輪詢、Comet 技術在即時性、頻寬、伺服器資源上均有明顯短板。HTML5 WebSocket 提供真正的全雙工長連接,Java EE 7 (JSR-356) 標準化 API,Tomcat 7.0.47+ 完整支援。本文從原理到實戰,手把手教你構建高併發即時通訊系統。
101 Switching Protocols,協定升級為 WebSocket。<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
<scope>provided</scope> <!-- Tomcat 內建實作 -->
</dependency>
package com.example.websocket;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
@ServerEndpoint("/websocket")
public class ChatWebSocket {
// 線程安全集合管理所有連接
private static final CopyOnWriteArraySet<Session> sessions = new CopyOnWriteArraySet<>();
@OnOpen
public void onOpen(Session session) {
sessions.add(session);
broadcast("系統:新用戶加入,當前在線 " + sessions.size() + " 人");
}
@OnMessage
public void onMessage(String message, Session sender) {
broadcast("用戶 " + sender.getId() + ":" + message);
}
@OnClose
public void onClose(Session session) {
sessions.remove(session);
broadcast("系統:用戶離開,當前在線 " + sessions.size() + " 人");
}
@OnError
public void onError(Session session, Throwable error) {
error.printStackTrace();
}
private void broadcast(String msg) {
for (Session s : sessions) {
if (s.isOpen()) {
try {
s.getBasicRemote().sendText(msg); // 同步發送
} catch (IOException e) {
sessions.remove(s); // 發送失敗移除
}
}
}
}
}
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>WebSocket Chat</title></head>
<body>
<div id="log" style="height:300px;overflow:auto;border:1px solid #ccc;padding:10px"></div>
<input id="msg" type="text" style="width:300px" placeholder="輸入訊息..." />
<button onclick="send()">發送</button>
<button onclick="closeWs()">斷開</button>
<script>
const log = document.getElementById('log');
const ws = new WebSocket('ws://localhost:8080/your-app/websocket');
ws.onopen = () => append('已連線');
ws.onmessage = e => append(e.data);
ws.onclose = () => append('連線關閉');
ws.onerror = e => append('錯誤:' + e);
function append(text) {
log.innerHTML += '<div>' + text + '</div>';
log.scrollTop = log.scrollHeight;
}
function send() {
const input = document.getElementById('msg');
if (input.value.trim()) ws.send(input.value), input.value = '';
}
function closeWs() { ws.close(); }
</script>
</body>
</html>
使用 Map<String, Session> 以用戶 ID 為鍵,實現私聊:
@OnMessage
public void onMessage(String msg, Session session) {
// 格式:@userId:內容
if (msg.startsWith("@")) {
String[] parts = msg.split(":", 2);
String targetId = parts[0].substring(1);
Session target = userMap.get(targetId);
if (target != null) target.getAsyncRemote().sendText("私訊:" + parts[1]);
} else {
broadcast(msg);
}
}
自動處理 JSON ⇄ POJO 轉換:
public class MessageDecoder implements Decoder.Text<ChatMessage> {
@Override public ChatMessage decode(String s) { return new Gson().fromJson(s, ChatMessage.class); }
@Override public boolean willDecode(String s) { return true; }
@Override public void init(EndpointConfig ec) {}
@Override public void destroy() {}
}
// 註冊
@ServerEndpoint(value = "/websocket", decoders = MessageDecoder.class, encoders = MessageEncoder.class)
@ServerEndpoint 掃描:需配置 ServerEndpointExporter Bean。@Autowired 在 Endpoint 中需手動從 ApplicationContext 取得,因 Endpoint 非 Spring 管理。| 項目 | 建議 |
|---|---|
| 連接數擴展 | Nginx 反向代理 + sticky session 或 Redis Pub/Sub 跨節點廣播 |
| 心跳間隔 | 客戶端 30s 發送 Ping,伺服端 web.xml 設定 websocket.idleTimeout |
| 訊息大小限制 | @ServerEndpoint(maxTextMessageSize=65536) |
| 安全性 | 握手階段驗證 Token/Session,WSS (TLS) 加密傳輸 |
@ServerEndpoint 路徑與 JS 連接 URL 一致,且 web.xml metadata-complete="false" 或 Servlet 3.0+ 自動掃描。javax.websocket 實作。getAsyncRemote() 非同步發送,避免阻塞 I/O 執行緒。Encoder/Decoder,不可直接 sendObject。WebSocket 讓 Java 後端擁有原生即時推送能力,結合 JSR-356 標準 API 與 Tomcat 高效能 NIO 連接器,可輕鬆支撐萬級併發長連接。掌握生命週期註解、編解碼器、集群擴展與安全實務,即可構建企業級即時通訊骨幹。