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

Java 實作網路中國象棋室 (一):登入驗證與事件驅動架構設計

     編輯:Java 相關知識

專案概覽

網路中國象棋室包含伺服端應用程式與客戶端 Applet。伺服端負責用戶認證、大廳/桌子狀態管理、走棋/聊天訊息路由;客戶端負責棋盤渲染(雙緩衝)、走法合法性判斷、用戶交互。本文聚焦登入子系統:ChessLogin、LoginEvent、LoginListener,展示事件驅動架構如何解耦網路 I/O 與業務邏輯。

核心類別設計

1. LoginEvent : 登入事件資料封裝 (不可變)

public class LoginEvent {
    public final int userID;      // 伺服器分配的唯一 ID
    public final String username; // 客戶端傳送的用戶名
    public final Socket socket;   // 已建立連線的 Socket
    
    public LoginEvent(int userID, String username, Socket socket) {
        this.userID = userID;
        this.username = username;
        this.socket = socket;
    }
}

採用 final 欄位確保事件發布後不可竄改,執行緒安全。

2. LoginListener : 監聽器介面 (觀察者模式核心)

public interface LoginListener {
    void onLoginEvent(LoginEvent evt);
}

業務邏輯層 (ChessServer) 實作此介面,處理用戶進入後的初始化流程。

3. ChessLogin : 登入接收執行緒 (生產者)

public class ChessLogin implements Runnable {
    private final ServerSocket ss;
    private final Vector listeners = new Vector<>();
    private int guestId = 0; // 簡易 ID 分配器
    private volatile boolean running = true;

    public ChessLogin(ChessServer server) {
        this.ss = server.ss;
        addLoginListener(server); // Server 註冊為監聽器
        new Thread(this).start();
    }

    public void run() {
        while (running) {
            try {
                Socket socket = ss.accept(); // 阻塞等待連線
                String username = chkLogin(socket.getInputStream());
                if (username != null) {
                    notifyListener(guestId++, username, socket);
                } else {
                    socket.close(); // 登入失敗關閉連線
                }
            } catch (IOException e) {
                if (running) e.printStackTrace(); // 正常關閉時會拋 SocketException
            }
        }
    }

    private String chkLogin(InputStream is) throws IOException {
        // 1. 讀取固定 12 bytes Header
        byte[] header = new byte[12];
        HttpInputStream.readBytes(is, 12, header);
        int cmdType = header[0];
        int totalSize = Convert.byteToShort(header[1], header[2]);
        if (totalSize == 0) return null;

        // 2. 讀取 Body (用戶名位元組)
        byte[] data = new byte[totalSize];
        HttpInputStream.readBytes(is, totalSize, data);
        return parseLoginData(data); // 預設直接轉字串
    }

    private String parseLoginData(byte[] data) {
        return new String(data, StandardCharsets.UTF_8); // 建議明確指定編碼
    }

    public void addLoginListener(LoginListener l) { listeners.add(l); }

    private void notifyListener(int userId, String username, Socket socket) {
        LoginEvent evt = new LoginEvent(userId, username, socket);
        for (LoginListener l : listeners) {
            try { l.onLoginEvent(evt); } catch (Exception e) { e.printStackTrace(); }
        }
    }

    public void shutdown() { running = false; try { ss.close(); } catch (IOException ignore) {} }
}

關鍵設計模式解析

模式角色優勢
觀察者模式ChessLogin (Subject) → LoginListener (Observer)網路層與業務層完全解耦,可動態增減處理器 (如日誌、審計、機器人)
單一職責ChessLogin 只負責「接收連線→讀取登入封包→發布事件」邏輯清晰,易於單元測試 (Mock InputStream 驗證解析)
不可變事件LoginEvent 所有欄位 final多執行緒環境下無需同步,避免競態條件
模板方法 (隱性)chkLogin() 定義讀取流程,parseLoginData() 可覆寫支援加密/Token 驗證擴充性強,不修改核心迴圈即可支援新認證機制

HttpInputStream 與 Convert 工具類

同前文,HttpInputStream.readBytes() 保證讀滿指定位元組;Convert.byteToShort/Int() 處理大端序網路位元組序轉換。

安全性與健壯性增強建議

  1. ID 分配器改進:guestId++ 非執行緒安全,長期運行會溢位。建議用 AtomicInteger 或 UUID。
  2. 登入逾時:socket.setSoTimeout(5000) 防止惡意連線佔用資源不發送資料。
  3. 輸入驗證:限制用戶名長度 (如 1-20 字元)、過濾控制字元、防 SQL/指令注入。
  4. 密碼認證:現版本無密碼。擴充 chkLogin 讀取加密 Token 或挑戰回應 (SCRAM/BCrypt)。
  5. 反暴力破解:記錄 IP 失敗次數,超限暫時封禁 (Guava RateLimiter / Redis)。
  6. TLS 加密:使用 SSLServerSocketFactory 建立 SSLServerSocket,保護用戶名/密碼傳輸。
  7. 優雅關閉:shutdown() 關閉 ServerSocket 觸發 accept() 拋出異常跳出迴圈,再逐個關閉已連線 Socket。

測試策略

  • 單元測試:構造含登入封包的 ByteArrayInputStream 驗證 chkLogin() 解析正確性。
  • 整合測試:啟動 ChessLogin,用 Socket 客戶端發送正確/錯誤封包,驗證 LoginListener 回呼與連線狀態。
  • 壓力測試:模擬 1000+ 並發連線登入,觀察 ID 分配、執行緒池、記憶體、GC 表現。

總結與下篇預告

登入子系統展示了事件驅動架構在網路伺服器中的經典應用:I/O 執行緒專注通訊、業務執行緒專注邏輯、事件對象傳遞上下文。這種分離讓 ChessServer 無需知道 Socket 讀取細節,只需關注「用戶進入了,做什麼事」。下一篇將深入 ChessServer 核心狀態機:大廳/桌子管理、走棋協定、廣播/單播路由、雙緩衝客戶端渲染。

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