【问题标题】:Java - Simple networked game is extremly laggyJava - 简单的网络游戏非常滞后
【发布时间】:2013-04-22 21:32:19
【问题描述】:

我一天前已经在 codereview 上问过这个问题,但我还没有得到任何回复,所以我想在这里问一下。

让我告诉你我想要做什么:

会弹出一个窗口,询问用户是要运行服务器还是客户端。选择服务器将在 LAN 上启动服务器。选择客户端将尝试连接到该服务器。一旦服务器正在运行并且客户端已连接,则会弹出一个带有两个正方形的窗口。服务器/客户端都可以使用箭头键移动它们的方块。

这是我得到的:

服务器的方块以想要的速度移动,但他的移动在客户端一侧非常不稳定。另一方面,客户端方块似乎以每秒 3 像素左右的速度移动(太慢了)。

这就是我要问的:

我想我的问题很明显。我所做的只是通过互联网发送 2 个整数。现代在线游戏发送的数据比这多,而且它们几乎没有滞后,所以很明显我做错了什么,但是什么?

Server.java:

// server class
public class Server {
    // networking objects
    private ServerSocket serverSocket;
    private Socket clientSocket;
    private DataOutputStream clientOutputStream;
    private DataInputStream clientInputStream;
    // game objects
    private Vec2D serverPos, clientPos;
    private GameManager gameManager;
    // run method
    public void run() {
        // intialization try-catch block 
        try {
            // setup sockets
            serverSocket = new ServerSocket(1111);
            clientSocket = serverSocket.accept();
            // setup I/O streams
            clientOutputStream = new DataOutputStream(clientSocket.getOutputStream());
            clientInputStream = new DataInputStream(clientSocket.getInputStream());
        } catch(IOException e) { Util.err(e); }
        // declare & intialize data exchange thread
        Thread dataExchange = new Thread( 
            new Runnable() {
                // run method
                @Override
                public void run() {
                    // I/O try-catch block
                    try {
                        // exchange-loop
                        while(true) {
                            // write x & y, flush
                            synchronized(gameManager) {
                                clientOutputStream.writeInt(serverPos.x);
                                clientOutputStream.writeInt(serverPos.y);
                                clientOutputStream.flush();
                            }
                            // read x & y
                            clientPos.x = clientInputStream.readInt();
                            clientPos.y = clientInputStream.readInt();
                        }
                    } catch(IOException e) { Util.err(e); }
                }
            }
        );
        // setup game data
        serverPos = new Vec2D(10, 10);
        clientPos = new Vec2D(300, 300);
        gameManager = new GameManager(serverPos, clientPos, serverPos);
        // start data exchange thread
        dataExchange.start();
        // start main loop
        while(true) {
            // get keyboard input
            synchronized(gameManager) {    
                gameManager.update();
            }
            // repaint, sleep
            gameManager.repaint();
            Util.sleep(15);        
        }
    }
}

Client.java:

// client class
public class Client {
    // networking objects
    private Socket serverConnection;
    private DataOutputStream serverOutputStream;
    private DataInputStream serverInputStream;
    // game objects
    private Vec2D serverPos, clientPos;
    private GameManager gameManager;
    // run method
    public void run() {
        // intialization try-catch block 
        try {
            // setup socket
            serverConnection = new Socket(InetAddress.getByName("192.168.0.19"), 1111);
            // setup I/O streams
            serverOutputStream = new DataOutputStream(serverConnection.getOutputStream());
            serverInputStream = new DataInputStream(serverConnection.getInputStream());
        } catch(IOException e) { Util.err(e); }
        // declare & intialize data exchange thread
        Thread dataExchange = new Thread( 
            new Runnable() {
                // run method
                @Override
                public void run() {
                    // I/O try-catch block
                    try {
                        // exchange-loop
                        while(true) {
                            // read x & y
                            synchronized(gameManager) {
                                serverPos.x = serverInputStream.readInt();
                                serverPos.y = serverInputStream.readInt();
                            }
                            // write x & y, flush
                            serverOutputStream.writeInt(clientPos.x);
                            serverOutputStream.writeInt(clientPos.y);
                            serverOutputStream.flush();
                        }
                    } catch(IOException e) { Util.err(e); }
                }
            }
        );
        // setup game data
        serverPos = new Vec2D(10, 10);
        clientPos = new Vec2D(300, 300);
        gameManager = new GameManager(serverPos, clientPos, clientPos);
        // start data exchange thread
        dataExchange.start();
        // start main loop
        while(true) {
            // get keyboard input
            synchronized(gameManager) {    
                gameManager.update();
            }
            // repaint, sleep
            gameManager.repaint();
            Util.sleep(15);  
        }
    }
}

我删除了一堆代码 - 我希望它现在不会令人困惑。感谢您的帮助!

【问题讨论】:

  • 你的问题有相当多的代码。为了尽快获得更好的帮助,请发帖SSCCE (link)
  • @Doorknob 我删除了一些我认为不必要的东西。
  • TCP 是我认为的问题,它得到了很多确认,这就是为什么它需要太多时间,使用 UDP,阿门 :)

标签: java networking lag


【解决方案1】:

您正在使用 Sockets,也许您发现它对于实时对话来说是滞后的,因为它们是通过 TCP 构建的,它必须确认消息并继续 ping 以查看连接是否仍然存在。

也许您应该使用适用于 UDP 协议的 DatagramSocket。不同之处在于 UDP 只是吐出一些东西,而无需费心保持连接处于活动状态,甚至尝试知道消息是否到达。

使用示例:http://docs.oracle.com/javase/tutorial/networking/datagrams/clientServer.html

编辑:为什么不尝试仅在服务器中的位置更改时才发送该 int 呢?可能服务器正在发送太多整数,以至于您的客户端有一个充满相同值的缓冲区,并且当您通过 int 读取 int 而不是清空缓冲区时,您会有一种延迟的假感觉。

【讨论】:

  • 这会让一切变得不同吗?现在它真的滞后了,我很难相信我所要做的就是改变协议。
  • @JesusPlusPlus11 我没有发现任何问题,即使只是一个 TCP 连接也不应该那么慢...该 IP 是直接连接吗?此外,现代游戏通常会插入数据并为中间间隙设置动画。
  • 我想我对现代游戏如何建立网络做了一个假设。我不太清楚您所说的 该 IP 是直接连接吗?
  • 无法保证 UDP 数据包的交付,因此预计会丢失消息。
  • @JesusPlusPlus11 Here is the API> goo.gl/Am5OI 你可以循环你的 readInt 调用直到你得到一个 EOF 异常,或者你可以用一个不错的缓冲区调用 read(byte[]) 方法,创建具有该字节数组的 nio ByteBuffer 对象,调用 ByteBuffer.asIntBuffer 方法并从那里读取 int :)
【解决方案2】:

您的代码中有一个问题是 while(true) 循环:

    while(true) {
        // get keyboard input
        synchronized(gameManager) {    
            gameManager.update();
        }
        // repaint, sleep
        gameManager.repaint();
        Util.sleep(15);        
    }

这样,您发送的更新要么太多(没有人按键)要么太少(因为无论发生什么,您总是等待 15 毫秒)。如果您侦听键盘事件并且如果有,则将其传播到另一侧会更好 - 然后另一侧可以更新作为对此“更改”事件的反应。您可能会发现 Observer pattern 对实现这一点很有用。

【讨论】:

  • 我会尝试其他人建议的方法。
【解决方案3】:

我还没有阅读所有代码,但我确实注意到“客户端”和“服务器”都具有在紧密循环中读取和写入更新的线程。

这样做存在三个问题:

  • 如果没有改变,客户端(或服务器)告诉另一端当前位置是没有意义的。

  • 由于客户端和服务器都严格地“先写后读,再写...”,两个线程进入锁步,每个写/读周期都需要网络往返。

  • 您在持有锁的同时正在执行部分工作,而另一个线程正在获取相同的锁并进行屏幕更新。

所以你需要安排:

  • 只有在位置实际发生变化时才会发送位置更新,并且
  • 读取和写入发生在不同的线程上。

@cyroxx 发现了另一个会导致延迟的问题。

【讨论】:

  • 好建议,谢谢。我会做你建议的这些事情,我看看它们如何提高性能。
猜你喜欢
  • 1970-01-01
  • 2020-01-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多