基于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
相关产品推荐
相关产品推荐

