Java - 简单的网络游戏非常滞后

时间:2013-04-22 13:04:27

标签: java networking lag

我前一天已经在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);  
        }
    }
}

我摆脱了一堆代码 - 我希望现在不要混淆。谢谢你的帮助!

3 个答案:

答案 0 :(得分:4)

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

也许您应该使用适用于UDP协议的DatagramSocket。不同之处在于,UDP只是在没有保持连接存在的情况下发送内容,甚至试图知道消息是否到达。

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

编辑:为什么不在服务器中的位置发生变化时才尝试发送该int?可能服务器发送的内容太多了,你的客户端有一个充满相同值的缓冲区,当你用int读取int而不是清空缓冲区时,你会有一种迟钝的假感觉。

答案 1 :(得分:2)

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

这有三个问题:

  • 如果客户端(或服务器)没有更改,则客户端(或服务器)无需告知另一端当前位置。

  • 因为客户端和服务器都严格“写入然后读取然后写入...”两个线程进入锁定步骤,​​并且每个写入/读取周期都需要网络往返。

    < / LI>
  • 你正在做一部分工作,同时拿着一把锁,还有另一个线程抓住同一个锁并进行屏幕更新。

所以你需要安排:

  • 位置更新仅在实际位置发生变化时发送,
  • 阅读和写作发生在不同的线程上。

@cyroxx发现了另一个也会导致懒散的问题。

答案 2 :(得分:2)

代码中的问题是while(true)循环:

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

这样,您发送的更新太多(没有人按任何键)或更新太少(因为无论发生什么情况,您总是等待15毫秒)。如果你听了键盘事件会更好,如果有的话,将它传播到另一侧 - 然后另一方可以更新为对这个“更改”事件的反应。您可能会发现Observer pattern对实现此功能很有用。