手機網遊客戶端需處理網絡請求發送、響應解析、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();
}
}
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,構建出清晰可維護的客戶端通信架構。