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

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]);
        }
    }
}

问题原因

  1. 同步阻塞死锁:服务器和客户端都在主线程里同步执行「发送→等待接收」的操作。服务器发完自己的位置后,卡在receiveInts()等客户端发数据;客户端发完自己的位置后,也卡在receiveInts()等服务器发数据,双方互相等待,直接死锁。
  2. Greenfoot.delay()添乱:updatePosition()里的Greenfoot.delay(5)会暂停整个主线程,让消息处理的时序彻底混乱,加速程序崩溃。
  3. 流初始化顺序错误: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 08:55:54