首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Spring 4 WebSocket 實戰:從協議原理到聊天室完整實現

     編輯:Java 綜合教學

Spring 4 WebSocket 實戰:從協議原理到聊天室完整實現

WebSocket 解決 HTTP 輪詢高延遲、高資源消耗痛點,實現真雙向長連接。本文涵蓋協議握手原理、Spring 4 整合架構、聊天室核心功能(群發/私聊)、SockJS 降級方案與前端完整代碼。

為什麼需要 WebSocket?

方案機制缺點資源消耗
Ajax 輪詢客戶端定時發 Request延遲高、大量無效請求、HTTP Header 重複開銷極高(頻繁建連/斷連)
Long Polling請求掛起直到有數據返回佔用連接數、服務端並發壓力大、仍基於 HTTP 無狀態高(長連接佔用線程)
WebSocket一次 HTTP 握手升級為 TCP 長連接瀏覽器兼容性(IE10+)、代理/防火牆可能攔截極低(單連接雙向通訊)

WebSocket 握手核心流程

// 客戶端發起升級請求
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 服務端驗證回應。

Spring 4 整合架構

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>

核心組件實現

1. 握手攔截器:綁定用戶身份

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(...) {}
}

2. WebSocket 配置類

@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();
    }
}

3. 業務處理器:群發與私聊

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;
            }
        }
    }
}

4. 前端聊天室頁面(chat.jsp 關鍵邏輯)

<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
  • 自動降級至 SockJS,兼容老舊瀏覽器

生產環境優化建議

  1. 心跳機制:@Scheduled 定時發送 ping,檢測僵屍連接
  2. 消息確認:引入 ack 機制,保證關鍵消息送達
  3. 集群擴展:整合 Redis Pub/Sub 或 RabbitMQ 實現多實例消息廣播
  4. 安全加固:握手階段驗證 JWT/Session,防止未授權連接
  5. 流控保護:Token Bucket 限制單用戶發送頻率

源碼倉庫

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

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved