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

手機網遊客戶端網絡通信:HTTP協議封裝與監聽器模式解耦

     編輯:Java 相關知識

手機網遊客戶端需處理網絡請求發送、響應解析、UI更新、斷線重連等複雜邏輯。本文展示基於HTTP的通信層設計,採用監聽器模式解耦網絡層與UI層。

通信架構設計


UI層 (Canvas/Screen) 
    ↓ 實現 HttpListener
    ├─ onResponse(byte[] data)  // 處理響應
    └─ onError(String msg)      // 處理錯誤
    
網絡層 (HttpThread)
    ├─ 發送請求 (非阻塞)
    ├─ 解析響應
    └─ 回調 Listener

協議指令定義


// 客戶端發送指令
public interface RequestCommand {
    byte READY      = 0x00;  // 就緒/登錄
    byte START      = 0x01;  // 開始匹配
    byte MOVE       = 0x02;  // 移動
    byte LAY_BOMB   = 0x03;  // 放炸彈
    byte GET_DATA   = 0x04;  // 獲取幀數據
    byte QUIT       = 0x05;  // 退出
}

// 服務端響應指令
public interface ResponseCommand {
    byte READY_OK       = 0x00;  // 就緒成功,攜帶userId
    byte CONTINUE_WAIT  = 0x01;  // 繼續等待
    byte INIT_DATA      = 0x02;  // 初始化數據
    byte GAME_DATA      = 0x03;  // 遊戲幀數據
    byte ERROR          = 0xFF;  // 錯誤信息
}

監聽器接口定義


public interface HttpListener {
    /** 處理服務端響應數據 */
    void onResponse(byte[] data);
    
    /** 處理網絡/協議錯誤 */
    void onError(String errorMessage);
}

網絡線程實現


public class HttpThread extends Thread {
    private static final String SERVER_URL = "https://game.example.com/api";
    private final byte[] requestData;
    private final HttpListener listener;
    
    public HttpThread(byte[] requestData, HttpListener listener) {
        this.requestData = requestData;
        this.listener = listener;
        start(); // 立即啟動異步執行
    }
    
    @Override
    public void run() {
        HttpConnection hc = null;
        InputStream is = null;
        OutputStream os = null;
        
        try {
            // 1. 建立連接
            hc = (HttpConnection) Connector.open(SERVER_URL);
            hc.setRequestMethod(HttpConnection.POST);
            hc.setRequestProperty("Content-Type", "application/octet-stream");
            hc.setRequestProperty("Connection", "Keep-Alive");
            
            // 2. 發送請求
            os = hc.openOutputStream();
            os.write(requestData);
            os.flush();
            
            // 3. 讀取響應
            int rc = hc.getResponseCode();
            if (rc == HttpConnection.HTTP_OK) {
                is = hc.openInputStream();
                byte[] buffer = new byte[1024];
                int len = is.read(buffer);
                if (len > 0) {
                    byte[] response = Arrays.copyOf(buffer, len);
                    // 4. 回調處理(在網絡線程中執行,UI更新需切換到事件線程)
                    listener.onResponse(response);
                }
            } else {
                listener.onError("服務器錯誤: " + rc);
            }
            
        } catch (IOException e) {
            listener.onError("網絡異常: " + e.getMessage());
        } finally {
            closeQuietly(is, os, hc);
        }
    }
    
    private void closeQuietly(Closeable... closeables) {
        for (Closeable c : closeables) {
            if (c != null) try { c.close(); } catch (IOException ignored) {}
        }
    }
}

請求數據構建工具


public class RequestBuilder {
    private final ByteArrayOutputStream bos = new ByteArrayOutputStream();
    private final DataOutputStream dos = new DataOutputStream(bos);
    
    public RequestBuilder(byte command) {
        writeByte(command);
    }
    
    public RequestBuilder addInt(int value) {
        writeInt(value);
        return this;
    }
    
    public RequestBuilder addShort(short value) {
        writeShort(value);
        return this;
    }
    
    public RequestBuilder addByte(byte value) {
        writeByte(value);
        return this;
    }
    
    public RequestBuilder addString(String str) {
        if (str != null) {
            byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
            writeShort((short) bytes.length);
            writeBytes(bytes);
        } else {
            writeShort(0);
        }
        return this;
    }
    
    public byte[] build() {
        return bos.toByteArray();
    }
    
    // 使用示例
    public static byte[] buildMove(short x, short y, byte dir) {
        return new RequestBuilder(RequestCommand.MOVE)
            .addShort(x).addShort(y).addByte(dir).build();
    }
    
    public static byte[] buildReady(String deviceId) {
        return new RequestBuilder(RequestCommand.READY)
            .addString(deviceId).build();
    }
}

UI層接入示例


public class GameCanvas extends Canvas implements HttpListener, Runnable {
    private int status = STATUS_WAITING;
    private long userId = -1;
    private long roomId = -1;
    
    // 按鍵處理 → 發送網絡請求
    protected void keyPressed(int keyCode) {
        if (status == STATUS_PLAYING) {
            switch (getGameAction(keyCode)) {
                case UP:
                    if (canMoveUp()) new HttpThread(RequestBuilder.buildMove(x, y-1, DIR_UP), this);
                    break;
                case DOWN:
                    if (canMoveDown()) new HttpThread(RequestBuilder.buildMove(x, y+1, DIR_DOWN), this);
                    break;
                case LEFT:
                    if (canMoveLeft()) new HttpThread(RequestBuilder.buildMove(x-1, y, DIR_LEFT), this);
                    break;
                case RIGHT:
                    if (canMoveRight()) new HttpThread(RequestBuilder.buildMove(x+1, y, DIR_RIGHT), this);
                    break;
                case FIRE:
                    new HttpThread(RequestBuilder.buildLayBomb(), this);
                    break;
            }
        }
    }
    
    // 網絡響應回調 → 更新遊戲狀態
    public void onResponse(byte[] data) {
        ByteBuffer buf = ByteBuffer.wrap(data);
        byte cmd = buf.get();
        
        switch (cmd) {
            case ResponseCommand.READY_OK:
                userId = buf.getInt();
                sendStartMatch();
                break;
            case ResponseCommand.CONTINUE_WAIT:
                // 顯示等待動畫
                break;
            case ResponseCommand.INIT_DATA:
                roomId = buf.getInt();
                decodeMap(buf);
                decodePlayers(buf);
                status = STATUS_PLAYING;
                startGameLoop(); // 啟動定時GET_DATA
                break;
            case ResponseCommand.GAME_DATA:
                decodeFrame(buf);
                repaint();
                break;
            case ResponseCommand.ERROR:
                showError(buf.getString());
                break;
        }
    }
    
    public void onError(String msg) {
        showNetworkError(msg);
    }
    
    // 遊戲循環:每秒請求幀數據
    private void startGameLoop() {
        new Thread(() -> {
            while (status == STATUS_PLAYING) {
                new HttpThread(RequestBuilder.buildGetData(roomId, userId), this);
                try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
            }
        }).start();
    }
}

關鍵優化點

問題方案
頻繁創建線程使用線程池/單線程循環發送
UI線程阻塞網絡回調中只更新模型,repaint()拋給事件線程
弱網重連指數退避重試+心跳檢測
請求超時設置Connection/Read超時,取消長連接
數據解析錯誤協議版本號+長度校驗+異常熔斷

現代化升級建議

  • 替換HttpConnection為OkHttp/Retrofit(支持連接池/攔截器/協程)
  • 二進制協議升級為Protobuf(自動編解碼/演進兼容)
  • 引入WebSocket替代輪詢(服務端主動推送幀數據)
  • 網絡層接入RxJava/Flow API實現響應式流控

監聽器模式實現了「網絡層不依賴UI層」,配合請求構建器鏈式API,構建出清晰可維護的客戶端通信架構。

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