-1

I'm creating a multiplayer game, which creates a new thread for each client, and then listens on a socket for strings via input and output streams. On the client side I also have a thread separate from the main game thread, but this one will send the updates to the server 60 times per second with a two letter string as the key for what I want to happen. When I request my "general update" (code RP) on the client side, the server sends the size of the entity array OK, then there seems to be a very long delay for the stream to become ready again.

I'm relatively sure this is the code of the problem area, after lots of testing to debug.

On the server side:

if(inputLine.equals("RP")){ //this is the main update
            timetoupdate = System.currentTimeMillis(); //start timer
               ArrayList<Player> currentplayers =world.getPlayers(); //get the list of players
                out.println(currentplayers.size()); //send the size
                for(int pl = 0; pl<currentplayers.size();pl++){ //loop through
                    Player player = currentplayers.get(pl); //get the player object
                    out.println(""+pl);
                    out.println(player.getUsername());
                    out.println(player.getType());
                    out.println(player.getXloc());
                    out.println(player.getYloc());
                    out.println(player.getXvel());
                    out.println(player.getYvel());
                    out.println(player.getPressing());
                    out.println(player.getLookingxloc());
                    out.println(player.getLookingyloc());
                    out.println(player.getTeam());
                    out.flush();
                    /*
                     id
                     username
                     class(type)
                     xloc
                     yloc
                     lxloc
                     lyloc
                     */ 
                    // Just gaveout all info on player.getUsername()
                }

                System.out.println("Time this ("+inputLine+") part takes:"+(System.currentTimeMillis()-timetoupdate)); //stop timer

On the Client Side

out.println("RP"); //tell the server we are generally updating

    String playercountstring = in.readLine(); //get the array size
    int playercount = Integer.valueOf(playercountstring); //convert to int

    for (int i = 0; i < playercount; i++) {//loop through all players

        long timetoupdate = System.currentTimeMillis(); //start timer
        while(!in.ready()){}//wait for input to become ready
        System.out.println("time this (to ready) part takes:"+(System.currentTimeMillis()-timetoupdate));//stop timer


        String toprocess = in.readLine(); //get id of player
... and continues to get all info on this player

As you'll notice I am timing how long the offending areas take to complete: the server takes less than 1 millisecond, yet just that one area of the client takes 200 millisecs, especially weird as the previous line collecting the size of the array takes less than 1 millisecond too. I'm really at a loss here as to what the issue is, I don't necessarily even need a solution just an explanation would be great. Thanks!

  • are you flushing the output and do you have TCP_NODELAY set on the sockets? – zapl Apr 24 '16 at 11:21
  • I am flushing the output, as in the server code. the socket is indeed set to TCP_NODELAY – thetrueprime Apr 24 '16 at 11:22
  • Both sides have an outbound tcp buffer, so you need to set that everywhere. It might as well be the client buffering the "RP" for 200ms that you see – zapl Apr 24 '16 at 11:27
  • Of course-a connection is both sides, I put the no delay on the serverside and reflushed the RP, and the delay seems to be gone! (Is there a way to mark this question as solved?) – thetrueprime Apr 24 '16 at 11:44
  • If a solution provided in the comment works for you, you can then ask the person who gave you that solution to post it as an answer instead. You can then upvote it or accept it as an answer or do both. – MS Srikkanth Apr 24 '16 at 12:01
  • 1
    Just get rid of it. The `readLine()` will block until data arrives. All you're doing with the `ready()` loop is smoking the CPU. There is no benefit. – user207421 Apr 24 '16 at 12:08
  • The ready loop was just used to see what the problem was, I didn't know it was due to the connection issue until then, it is gone now though. – thetrueprime Apr 24 '16 at 12:26
  • It isn't `due to the connection issue`. If there was a connection issue you wouldn't have got to the `ready()/readLine()` part. – user207421 Apr 25 '16 at 10:18
  • wrote up something for you to accept. Ps: it's perfectly fine to write & accept your own answer but you should write a complete answer like for someone else or it attracts downvotes just like that. – zapl Apr 29 '16 at 10:49

1 Answers1

0

There are 2 points in a TCP connection that often cause trouble:

  • buffers within Java that gather data before it is handed over to the system, e.g. within a BufferedOutputStream
    • Once the data needs to be sent, you need to call flush()
  • the system's outbound TCP buffer
    • The usual solution is to set the TCP_NODELAY option which disables Nagle's Algorithm that can otherwise delay sending of packets. There are other ways as well.

Those places exist on both sides of the connection and both sides.

A little example:

class Server {
    public void serve(int port) throws IOException {
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            for(;;) {
                Socket clientSocket = serverSocket.accept();
                clientSocket.setTcpNoDelay(true);
                Thread clientThread = new Thread(() -> handleClient(clientSocket));
                clientThread.start();
            }
        }
    }

    private void handleClient(Socket clientSocket) {
        try (
            Socket socket = clientSocket; // tricks try-with-resource into handling the socket
            OutputStream out = socket.getOutputStream();
            InputStream in = socket.getInputStream();
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
            BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))
             ) {

            while (true) {
                String command = reader.readLine();
                switch (command) {
                    case "quit":
                        Util.send(writer, "ok");
                        return; // ends this thread.
                    case "time":
                        Util.send(writer, String.valueOf(System.currentTimeMillis()));
                        break;
                }
            }

        } catch (IOException e) {
            System.out.println("Client connection closed due to " + e);
            // maybe do something
        }
    }
}

And the client

class Client {
    public void connect(String host, int port) throws UnknownHostException, IOException {
        try (
            Socket socket = new Socket(host, port);
            OutputStream out = socket.getOutputStream();
            InputStream in = socket.getInputStream();
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
            BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))
            ) {

            socket.setTcpNoDelay(true);

            Util.send(writer, "time");
            String time = reader.readLine();
            System.out.println("Server time: " + time);
            Util.send(writer, "quit");
            System.out.println(reader.readLine());
            return;
        }
    }
}

Note how both sides have called socket.setTcpNoDelay(true). And finally the code to write lines and calls flush() to ensure data isn't stuck in an application level buffer.

class Util {
    public static void send(BufferedWriter writer, String command) throws IOException {
        writer.write(command);
        writer.newLine();
        writer.flush();
    }
}
Community
  • 1
  • 1
zapl
  • 63,179
  • 10
  • 123
  • 154