4

I'm new to StackOverflow lol, but I've been relying on this website for awhile. I have a question regarding a Java socket server that I created. Upon connection (client and server), my application creates a thread for that client. This is an MMORPG game server... at least trying to be. With one player, it doesn't lag that bad. With two, however, it began to show some lags...

If I was to spam left-right-left-right on one of the client, and move around normally with the other, the other would feel glitchy. I'm hoping to get some assistant since I've spent over a week and a half tangled up =) It's about time I ask for help.

The code is simple:

public static void main(String[] args) throws IOException{
    serverRooms.put(roomNumber, new Room());

    try {
        System.out.println("Starting Server...");
        serverSocket = new ServerSocket(9595, 20);

        System.out.println("Server Started");
        while(run){
            Socket socket = serverSocket.accept();      // Check if we have a connection, otherwise wait

            Player player = new Player(playerCount++, socket, roomNumber);
            new Thread(player).start();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

That's how it is all started! On the Player object, it looks like :

public void run() {
    while(playerIsConnected) {
        try {
            int msgid = input.readUnsignedByte();
            messageHandler(this, msgid);

        } catch (IOException e) {
            System.err.println("Player have signed off");
            playerIsConnected = false;
        }
    }

    // If Player leaves, close socket, and end thread
    try {
        socket.close();
    } catch (IOException e) {
        System.out.println("We got an error while closing a socket on player " + pid + ".");
    }
}

messageHandler happens to be a static method from a Final Static class. It is a global method that can be called by every thread (Can this be the cause of the lag??)

public final class MessageControl {

public static void messageHandler(Player player, int msgid) throws IOException{
    DataInputStream input = player.getInputStream();
    switch (msgid) {
        case 10:
            byte hspd = (byte) Math.signum(input.readByte());
            byte vspd = (byte) Math.signum(input.readByte());
            byte dir = input.readByte();

            updatePlayerPosition(player);
            byte spd = (byte) (hspd != 0 && vspd != 0 ? player.spd-1 : player.spd);

            // Prepare packet and send to clients
            ByteBuffer buffer = ByteBuffer.allocate(11);
            buffer.put((byte) 10);
            buffer.put(shortToByte_U16(player.pid));
            buffer.put(shortToByte_U16(player.x));
            buffer.put(shortToByte_U16(player.y));
            buffer.put((byte)(hspd*spd));
            buffer.put((byte)(vspd*spd));
            buffer.put((byte)(dir));
            sendPacketToAllClients(player, buffer, true);

            // Update Player info
            player.hspd = (byte) hspd;
            player.vspd = (byte) vspd;
            player.dir = dir;
            player.lastUpdate = System.currentTimeMillis();
        break;
    }
private static void sendPacketToAllClients(Player player, ByteBuffer buffer, boolean includeMe){
    for (Player otherPlayer : player.room.getPlayersInRoom()){
        if (otherPlayer.pid != player.pid || includeMe){
            sendPacketToClient(otherPlayer, buffer);
        }
    }
}
}

Regarding the shortToByte_U16(), I just created a simple method that conerts shorts to bytes (sending buffer packets via bytes to client). Example, I have about 5 of these conversions, which would include conversion for unsigned u16

public static byte[] shortToByte_16(int x){
    short s = (short) x;
    byte[] ret = new byte[2];
    ret[0] = (byte)(s & 0xff);
    ret[1] = (byte)((s >> 8) & 0xff);
    return ret;
}

Looking at the following structure, any ideas why I be lagging?

EDIT : I think I improved it a lot by setting the setTcpNoDelay to true. The lag seems to still be there when I spam left/right on my end... the other player on my screen seems glitchy.

            Socket socket = serverSocket.accept();      // Check if we have a connection, otherwise wait
            socket.setTcpNoDelay(true);  // This helped a lot!!!
            Player player = new Player(playerCount++, socket, roomNumber);
            new Thread(player).start();

From what I am seeing... my "spamming left/right" end seems to be missing the packet sent by the server.

AlbertGeno
  • 59
  • 3
  • I notice that messageHandler is within a while loop. How often does it get called when you spam left-right? If your Server is receiving a ton of messages every second that it must read and process, that could be resource intensive – faraza Dec 10 '15 at 09:23
  • It calls once per LEFT or RIGHT. The "int msgid = input.readUnsignedByte()" pauses the while loop, so I can put a print statement inside and it only prints once before stopping. Once user press LEFT or RIGHT, it prints again per key pressed/message received – AlbertGeno Dec 10 '15 at 09:26
  • Can you put a println in sendPacketToAllClients to see how often that gets called? – faraza Dec 10 '15 at 09:28
  • I just did it. It prints once when I pressed left/right key down... and prints again when i release. That's it. No more until i press another key down or release it. Thanks for taking the time to assist Faraza =) – AlbertGeno Dec 10 '15 at 09:30
  • How often does it print when the lag occurs – faraza Dec 10 '15 at 09:31
  • Testing it on my PC and my laptop, it lags when both user press key down at same time (or within the same second). It's as if when i send the server my direction and speed, it lags while it performs the other client's action before sending me back my packet. Going to bed soon. Have work in 6 hours. Hopefully this get solved. So curious what's going on lol – AlbertGeno Dec 10 '15 at 09:43
  • There's a lot of fluff here. You're aware of `readUnsignedByte()` but not of `readUnsignedShort()`? You're using `Math.signum()` and then casting the result back to the byte that it was originally? You're doing byte-swapping. Fr. no apparent reason at all? You're mixing java.net and java.nio? You're not aware of the entire `Buffer`API? You seem t be doing everything the hardest way possible. I suggest you have a good look at the Data input and output stream APIs and stop using everything else, and use Buffered streams between those and the socket streams. – user207421 Dec 10 '15 at 12:00
  • Of course I'm aware of readUnsignedShort. It just doesn't give me the correct value whenever I use it, which is extremely strange, right? The Math.signum() is used for server-side movement calculation. Server knows the speed that the player should have (spd), so the client cannot tell server it's running hspd of 100 when their maxSpd is currently 5. Byte swapping is currently used to create 2 bytes of a short value. All values in Java are signed, so I have to manually make it unsigned. Regarding your advice, I will definitely try it out! Thank you for the suggestions!! =) – AlbertGeno Dec 10 '15 at 16:37
  • EJB, any idea why this is happening?? output = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream())); output.writeShort(player.x); Server print: 591 Client print: 20226 – AlbertGeno Dec 11 '15 at 02:19
  • Can we see the code for `sendPacketToClient`? That's probably where the problem is. (Also, please don't use the term "packet" to refer to anything other than actual packets. Call your application-level protocol units "messages".) – David Schwartz Dec 11 '15 at 11:29
  • private static void sendPacketToClient(Player player, ByteBuffer buffer){ try { player.getOutputStream().write(buffer.array()); player.getOutputStream().flush(); } catch (IOException e) { e.printStackTrace(); System.err.println("Failed to send packet to client"); } } – AlbertGeno Dec 12 '15 at 02:52
  • Why the call to flush? What if the very next thing you're going to do is send another message to the very same endpoint? – David Schwartz Dec 12 '15 at 04:54

1 Answers1

1

Problem solved. =) setTcpNoDelay true did the trick. Regarding the part when I said I was missing packets, I actually didn't. The two messages merged and came in as one message. My program only read the first few bytes and ignored the rest. Had to put a byte in front to indicate the size of message. Once that was in place, I set a while loop to read through it til it can't read anymore. =) thanks everyone for helping me. My first post and it was a grand experience.

AlbertGeno
  • 59
  • 3
  • Please don't do that. Please fix the real problem rather than hiding the symptoms by disabling Nagling. This makes network utilization less efficient and will cause poor behavior should your program encounter the edge cases Nagling fixes. The TCP no delay function exists to allow pre-TCP protocols to work better over TCP and should not be needed for something designed to use TCP. – David Schwartz Dec 11 '15 at 11:28
  • I guess you're right... should I delete this answer? I mean it does indeed work and the performance are good. But I guess I should look for another answer. Any suggestions?? =( – AlbertGeno Dec 12 '15 at 02:54
  • 1
    I was looking around for awhile, and there are people who says for low latency, disabling it is good while for high bandwith large file transfer, enabling it is good since the intention of nagle's algorithm is to only send full size packets, which would cause a delay for small messages due to the waiting time. The message I am transferring are relatively small, most common are 5 bytes. – AlbertGeno Dec 12 '15 at 03:19
  • The smaller the messages, the worse it is if you send each one in its own packet and the harder it will be to catch up if you fall behind. – David Schwartz Dec 12 '15 at 04:52