Greenfoot多人游戏服务器接收客户端数据时程序停滞问题
Greenfoot多人游戏服务器调用receiveInt()时停滞问题
我在学校项目里用Greenfoot做一款简单的多人游戏。如果删掉Player脚本里的updatePosition();方法,服务器能正常运行,客户端也能接收数据;但调用这个方法尝试获取客户端数据时,服务器执行receiveInt()就会卡住。服务器终端会输出"Works Server" "2" "Works Server" "2",之后客户端和服务器都停止运行。
相关代码
GameManager(MyWorld类)
import greenfoot.*; import java.io.*; import java.net.*; import javax.swing.JOptionPane; import java.util.ArrayList; public class MyWorld extends World { public Server server; public Client client; public boolean Server = false; public MyWorld() { super(600, 400, 1); Player player = new Player(this); addObject(player, getWidth()/2, getHeight()/2); } public void act() { if(Greenfoot.isKeyDown("q")) { if(Server) { server.close(); }else { client.close(); } } } public void init() { Object[] options = {"Server","Client"}; int i = JOptionPane.showOptionDialog(null, "Host as server or join as client?", "Selection", JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE, null, options, options[0]); if(i == 0) { server = new Server(this); Server = true; Player2 player2 = new Player2(); addObject(player2, 0, 0); }else if(i == 1) { client = new Client(this); Player2 player2 = new Player2(); addObject(player2, 0, 0); } } public class Server { MyWorld myWorld; public ServerSocket serverSocket; public Socket skt; private ObjectInputStream inServer; private ObjectOutputStream outServer; public Server(MyWorld myWorl) { this.myWorld = myWorl; try { serverSocket = new ServerSocket(8888); skt = serverSocket.accept(); inServer = new ObjectInputStream(skt.getInputStream()); outServer = new ObjectOutputStream(skt.getOutputStream()); }catch(IOException e) { close(); } } public void sendInts(int ...arr) { try { outServer.writeObject(arr); Greenfoot.delay(1); outServer.flush(); } catch(NullPointerException ex) { } catch(IOException ex) { } } public int[] receiveInts() { int[] datServer = null; try { datServer = (int[])inServer.readObject(); } catch(ClassNotFoundException ex) { ex.printStackTrace(); close(); } catch(NullPointerException ex) { close(); } catch(IOException ex) { close(); } return datServer; } public void close() { try { outServer.close(); inServer.close(); skt.close(); serverSocket.close(); } catch(IOException ex) { throw new RuntimeException("Error closing streams..."); } } } public class Client { MyWorld myWorld; private ObjectOutputStream outClient; private ObjectInputStream inClient; public Socket clientSocket; public Client(MyWorld myWorl) { this.myWorld = myWorl; try { clientSocket = new Socket("localhost",8888); outClient = new ObjectOutputStream(clientSocket.getOutputStream()); inClient = new ObjectInputStream(clientSocket.getInputStream()); }catch(IOException e) { close(); } } public void sendInts(int ...arr) { try { outClient.writeObject(arr); Greenfoot.delay(1); outClient.flush(); } catch(NullPointerException ex) { } catch(IOException ex) { } } public int[] receiveInts() { int[] datClient = null; try { datClient = (int[])inClient.readObject(); } catch(ClassNotFoundException ex) { ex.printStackTrace(); } catch(NullPointerException ex) { } catch(IOException ex) { close(); } return datClient; } public void close() { try { outClient.close(); inClient.close(); clientSocket.close(); } catch(IOException ex) { throw new RuntimeException("Error closing streams..."); } } } }
Player类
import greenfoot.*; public class Player extends Actor { private MyWorld myWorld; private Player2 player2; public Player(MyWorld myWorl) { myWorld = myWorl; } private boolean startSelectionBool = false; public void act() { if(!startSelectionBool) { if(Greenfoot.getMouseInfo() != null) { if(Greenfoot.mouseClicked(this)) { myWorld.init(); startSelectionBool = true; } } } else { if(myWorld.Server == true) { updateServer(); //System.out.println("Server"); }else { updateClient(); //System.out.println("Client"); } } } public void updateServer() { if(Greenfoot.isKeyDown("w")) { setLocation(this.getX(), this.getY()-1); } if(Greenfoot.isKeyDown("s")) { setLocation(this.getX(), this.getY()+1); } if(Greenfoot.isKeyDown("a")) { setLocation(this.getX()-1, this.getY()); } if(Greenfoot.isKeyDown("d")) { setLocation(this.getX()+1, this.getY()); } myWorld.server.sendInts(this.getX(), this.getY()); updatePosition(); } public void updatePosition() { int[] w1 = myWorld.server.receiveInts(); if (w1 != null) { Greenfoot.delay(5); player2 = getWorld().getObjects(Player2.class).get(0); System.out.println("Works Server"); player2.setLocation(w1[0], w1[1]); System.out.println("2"); } else { System.out.println("1"); } } public void updateClient() { if(Greenfoot.isKeyDown("w")) { setLocation(this.getX(), this.getY()-1); } if(Greenfoot.isKeyDown("s")) { setLocation(this.getX(), this.getY()+1); } if(Greenfoot.isKeyDown("a")) { setLocation(this.getX()-1, this.getY()); } if(Greenfoot.isKeyDown("d")) { setLocation(this.getX()+1, this.getY()); } myWorld.client.sendInts(this.getX(), this.getY()); int[] w2 = myWorld.client.receiveInts(); if (w2 == null) { System.out.println("Return Client"); return; } else { player2 = getWorld().getObjects(Player2.class).get(0); player2.setLocation(w2[0], w2[1]); } } }
问题原因
- 同步阻塞死锁:服务器和客户端都在主线程里同步执行「发送→等待接收」的操作。服务器发完自己的位置后,卡在
receiveInts()等客户端发数据;客户端发完自己的位置后,也卡在receiveInts()等服务器发数据,双方互相等待,直接死锁。 Greenfoot.delay()添乱:updatePosition()里的Greenfoot.delay(5)会暂停整个主线程,让消息处理的时序彻底混乱,加速程序崩溃。- 流初始化顺序错误:ObjectInputStream创建时会读取对方ObjectOutputStream的头部数据,代码里先创建in再创建out,容易导致初始化时就阻塞。
修复方案
1. 用独立线程处理网络接收
把接收操作放到单独线程里,不让它阻塞Greenfoot的主线程:
// 修改MyWorld里的Server类 public class Server { MyWorld myWorld; public ServerSocket serverSocket; public Socket skt; private ObjectInputStream inServer; private ObjectOutputStream outServer; private Thread receiveThread; private int[] clientPos; // 存客户端位置 public Server(MyWorld myWorl) { this.myWorld = myWorl; try { serverSocket = new ServerSocket(8888); skt = serverSocket.accept(); // 先创建并刷新out,避免头部读取阻塞 outServer = new ObjectOutputStream(skt.getOutputStream()); outServer.flush(); inServer = new ObjectInputStream(skt.getInputStream()); // 启动接收线程 receiveThread = new Thread(() -> { while(!skt.isClosed()){ try { clientPos = (int[])inServer.readObject(); } catch (IOException | ClassNotFoundException e) { close(); break; } } }); receiveThread.start(); }catch(IOException e) { close(); } } // 提供获取客户端位置的方法 public int[] getClientPos(){ return clientPos; } // 重写close方法,确保线程终止 public void close() { try { if(receiveThread != null) receiveThread.interrupt(); outServer.close(); inServer.close(); skt.close(); serverSocket.close(); } catch(IOException ex) { throw new RuntimeException("Error closing streams..."); } } // sendInts方法保留,去掉里面的Greenfoot.delay(1) public void sendInts(int ...arr) { try { outServer.writeObject(arr); outServer.flush(); } catch(NullPointerException ex) { } catch(IOException ex) { } } } // 修改MyWorld里的Client类 public class Client { MyWorld myWorld; private ObjectOutputStream outClient; private ObjectInputStream inClient; public Socket clientSocket; private Thread receiveThread; private int[] serverPos; // 存服务器位置 public Client(MyWorld myWorl) { this.myWorld = myWorl; try { clientSocket = new Socket("localhost",8888); // 先创建并刷新out outClient = new ObjectOutputStream(clientSocket.getOutputStream()); outClient.flush(); inClient = new ObjectInputStream(clientSocket.getInputStream()); // 启动接收线程 receiveThread = new Thread(() -> { while(!clientSocket.isClosed()){ try { serverPos = (int[])inClient.readObject(); } catch (IOException | ClassNotFoundException e) { close(); break; } } }); receiveThread.start(); }catch(IOException e) { close(); } } // 提供获取服务器位置的方法 public int[] getServerPos(){ return serverPos; } // 重写close方法 public void close() { try { if(receiveThread != null) receiveThread.interrupt(); outClient.close(); inClient.close(); clientSocket.close(); } catch(IOException ex) { throw new RuntimeException("Error closing streams..."); } } // sendInts方法去掉Greenfoot.delay(1) public void sendInts(int ...arr) { try { outClient.writeObject(arr); outClient.flush(); } catch(NullPointerException ex) { } catch(IOException ex) { } } }
2. 修改Player类的更新逻辑
去掉同步接收的代码,直接从线程存储的变量里取位置:
import greenfoot.*; public class Player extends Actor { private MyWorld myWorld; private Player2 player2; public Player(MyWorld myWorl) { myWorld = myWorl; } private boolean startSelectionBool = false; public void act() { if(!startSelectionBool) { if(Greenfoot.getMouseInfo() != null) { if(Greenfoot.mouseClicked(this)) { myWorld.init(); startSelectionBool = true; } } } else { if(myWorld.Server == true) { updateServer(); }else { updateClient(); } } } public void updateServer() { // 移动逻辑不变 if(Greenfoot.isKeyDown("w")) setLocation(getX(), getY()-1); if(Greenfoot.isKeyDown("s")) setLocation(getX(), getY()+1); if(Greenfoot.isKeyDown("a")) setLocation(getX()-1, getY()); if(Greenfoot.isKeyDown("d")) setLocation(getX()+1, getY()); myWorld.server.sendInts(getX(), getY()); // 从服务器的clientPos变量取位置 int[] clientPos = myWorld.server.getClientPos(); if (clientPos != null) { if(player2 == null) player2 = getWorld().getObjects(Player2.class).get(0); player2.setLocation(clientPos[0], clientPos[1]); } } // 删掉原来的updatePosition方法 public void updateClient() { // 移动逻辑不变 if(Greenfoot.isKeyDown("w")) setLocation(getX(), getY()-1); if(Greenfoot.isKeyDown("s")) setLocation(getX(), getY()+1); if(Greenfoot.isKeyDown("a")) setLocation(getX()-1, getY()); if(Greenfoot.isKeyDown("d")) setLocation(getX()+1, getY()); myWorld.client.sendInts(getX(), getY()); // 从客户端的serverPos变量取位置 int[] serverPos = myWorld.client.getServerPos(); if (serverPos != null) { if(player2 == null) player2 = getWorld().getObjects(Player2.class).get(0); player2.setLocation(serverPos[0], serverPos[1]); } } }
3. 移除所有Greenfoot.delay()调用
Greenfoot.delay()会暂停主线程,完全破坏异步通信的节奏,必须删掉。
测试注意事项
- 先启动服务器端,再启动客户端,确保连接正常建立。
- 按下
q键关闭连接时,线程会被中断,资源会正确释放。
内容的提问来源于stack exchange,提问作者Gianluca
相关产品推荐
相关产品推荐

