You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于JavaFX MVC的TCP多人死亡匹配游戏网络模块整合咨询

核心概念澄清
  • 客户端的定义:这里的“客户端”就是每个玩家运行的完整JavaFX游戏实例。每个玩家启动App.java后,程序就作为一个独立客户端,需要通过Socket与服务器建立连接,单独开线程处理网络通信,避免阻塞JavaFX的UI主线程(Application Thread)。
  • 服务器的角色:服务器是一个独立运行的Java程序,负责接收所有客户端的连接请求,维护全局玩家状态,同步所有客户端的游戏数据(比如生命值)。
MVC与客户端-服务器模块整合方案

客户端侧改造

1. 新增ClientNetwork类

这个类封装TCP客户端逻辑,独立线程运行,避免阻塞UI:

import javafx.application.Platform;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Map;

public class ClientNetwork extends Thread {
    private Socket socket;
    private ObjectInputStream in;
    private ObjectOutputStream out;
    private GameController controller;

    public ClientNetwork(String serverIp, int port, GameController controller) {
        this.controller = controller;
        try {
            socket = new Socket(serverIp, port);
            out = new ObjectOutputStream(socket.getOutputStream());
            in = new ObjectInputStream(socket.getInputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        // 持续监听服务器同步的全局玩家状态
        while (!socket.isClosed()) {
            try {
                Map<String, Integer> allPlayerHealth = (Map<String, Integer>) in.readObject();
                // 回到JavaFX主线程更新Model和View
                Platform.runLater(() -> controller.updateGlobalPlayerHealth(allPlayerHealth));
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
                break;
            }
        }
    }

    // 发送本地玩家的生命值更新到服务器
    public void sendPlayerHealth(String playerId, int health) {
        try {
            out.writeObject(new PlayerState(playerId, health));
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2. 扩展现有Model类

添加全局玩家状态存储,支持多玩家数据管理:

import java.util.HashMap;
import java.util.Map;

public class Model {
    private int localPlayerHealth;
    private Map<String, Integer> allPlayerHealth = new HashMap<>();

    // 原有本地生命值操作方法
    public int getLocalPlayerHealth() { return localPlayerHealth; }
    public void setLocalPlayerHealth(int health) { this.localPlayerHealth = health; }

    // 新增全局玩家状态操作方法
    public Map<String, Integer> getAllPlayerHealth() { return allPlayerHealth; }
    public void setAllPlayerHealth(Map<String, Integer> healthMap) {
        this.allPlayerHealth = new HashMap<>(healthMap);
    }
}

3. 修改GameController类

关联网络模块,处理本地状态更新与服务器同步数据:

import java.util.Map;

public class GameController {
    private Model model;
    private ClientNetwork clientNetwork;
    private String playerId; // 客户端唯一标识,可由服务器分配

    public void init(Model model, ClientNetwork clientNetwork, String playerId) {
        this.model = model;
        this.clientNetwork = clientNetwork;
        this.playerId = playerId;
    }

    // 本地玩家生命值变化时调用,同步发送到服务器
    public void updateLocalHealth(int newHealth) {
        model.setLocalPlayerHealth(newHealth);
        clientNetwork.sendPlayerHealth(playerId, newHealth);
        updateLocalPlayerView(); // 原有本地UI更新逻辑
    }

    // 接收服务器同步的全局状态,更新Model和UI
    public void updateGlobalPlayerHealth(Map<String, Integer> healthMap) {
        model.setAllPlayerHealth(healthMap);
        updateAllPlayersView(); // 更新显示所有玩家状态的UI组件
    }

    // 原有UI更新方法
    private void updateLocalPlayerView() { /* ... */ }
    private void updateAllPlayersView() { /* ... */ }
}

4. 修改App.java

初始化网络模块并注入控制器依赖:

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.util.UUID;

public class App extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {
        // 加载视图与控制器
        FXMLLoader loader = new FXMLLoader(getClass().getResource("game-view.fxml"));
        Parent root = loader.load();
        GameController controller = loader.getController();

        // 初始化Model
        Model model = new Model();

        // 初始化客户端网络(本地测试用127.0.0.1,外网替换为公网IP)
        String serverIp = "127.0.0.1";
        int port = 8888;
        String playerId = "player-" + UUID.randomUUID(); // 生成临时玩家ID
        ClientNetwork clientNetwork = new ClientNetwork(serverIp, port, controller);
        clientNetwork.start();

        // 注入控制器依赖
        controller.init(model, clientNetwork, playerId);

        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

服务器侧实现

1. 新增Server类

监听客户端连接,维护全局玩家状态并广播同步:

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class Server {
    private ServerSocket serverSocket;
    private Map<String, ClientHandler> clientHandlers = new ConcurrentHashMap<>();
    private Map<String, Integer> globalPlayerHealth = new ConcurrentHashMap<>();

    public Server(int port) {
        try {
            serverSocket = new ServerSocket(port);
            System.out.println("服务器启动,监听端口:" + port);

            // 持续接收客户端连接
            while (true) {
                Socket clientSocket = serverSocket.accept();
                ClientHandler handler = new ClientHandler(clientSocket, this);
                handler.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 同步单个玩家状态并广播全局数据
    public synchronized void updatePlayerHealth(String playerId, int health) {
        globalPlayerHealth.put(playerId, health);
        broadcastGlobalHealth();
    }

    // 向所有客户端广播全局玩家状态
    private void broadcastGlobalHealth() {
        for (ClientHandler handler : clientHandlers.values()) {
            handler.sendGlobalHealth(globalPlayerHealth);
        }
    }

    // 注册新客户端
    public void registerClient(String playerId, ClientHandler handler) {
        clientHandlers.put(playerId, handler);
        globalPlayerHealth.put(playerId, 100); // 初始生命值设为100
        broadcastGlobalHealth();
    }

    // 移除断开连接的客户端
    public void removeClient(String playerId) {
        clientHandlers.remove(playerId);
        globalPlayerHealth.remove(playerId);
        broadcastGlobalHealth();
    }

    public static void main(String[] args) {
        new Server(8888);
    }
}

2. 新增ClientHandler类

每个线程对应一个客户端,处理单独的通信逻辑:

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Map;

public class ClientHandler extends Thread {
    private Socket socket;
    private ObjectInputStream in;
    private ObjectOutputStream out;
    private Server server;
    private String playerId;

    public ClientHandler(Socket socket, Server server) {
        this.socket = socket;
        this.server = server;
    }

    @Override
    public void run() {
        try {
            out = new ObjectOutputStream(socket.getOutputStream());
            in = new ObjectInputStream(socket.getInputStream());

            // 接收客户端发送的玩家ID
            playerId = (String) in.readObject();
            server.registerClient(playerId, this);

            // 持续监听客户端的状态更新
            while (!socket.isClosed()) {
                PlayerState state = (PlayerState) in.readObject();
                server.updatePlayerHealth(state.getPlayerId(), state.getHealth());
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            // 客户端断开后的清理操作
            server.removeClient(playerId);
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    // 向客户端发送全局玩家状态
    public void sendGlobalHealth(Map<String, Integer> healthMap) {
        try {
            out.writeObject(healthMap);
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3. 新增PlayerState类(序列化传输用)

import java.io.Serializable;

public class PlayerState implements Serializable {
    private String playerId;
    private int health;

    public PlayerState(String playerId, int health) {
        this.playerId = playerId;
        this.health = health;
    }

    // Get方法
    public String getPlayerId() { return playerId; }
    public int getHealth() { return health; }
}
服务器部署建议
  • 本地/局域网测试:直接在本地运行Server类,客户端使用127.0.0.1(本地)或局域网IP(如192.168.x.x)连接,适合单机多开或局域网内玩家测试。
  • 外网部署:将Server程序部署在带有公网IP的云服务器(如阿里云、腾讯云)上,在服务器的防火墙/安全组中开放监听端口(如8888),客户端使用云服务器的公网IP进行连接。

内容的提问来源于stack exchange,提问作者Chae

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 07:31:07