Java套接字服务器与两个客户端连接时出现延迟

4
我是新手,但我已经依赖这个网站一段时间了。我有一个关于我创建的Java套接字服务器的问题。在连接时(客户端和服务器),我的应用会为该客户端创建一个线程。这是一个MMORPG游戏服务器……至少试图成为一个。对于一个玩家来说,它不会卡顿得那么厉害。然而,当有两个玩家时,它开始出现一些卡顿。
如果我在其中一个客户端上连续按下左右箭头,并在另一个客户端上正常移动,那么另一个客户端会感觉到有点卡顿。我希望能得到一些帮助,因为我已经纠缠了一个多星期了。现在是时候寻求帮助了。这里是简单的代码:
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();
    }
}

这就是一切的开始!在Player对象上,看起来像这样:
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是来自Final Static类的静态方法。它是一个全局方法,每个线程都可以调用(这可能是导致延迟的原因吗?)。

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

关于shortToByte_U16(),我刚刚创建了一个简单的方法,将short类型转换为byte类型(通过字节将缓冲区数据包发送给客户端)。例如,我有大约5个这样的转换,其中包括无符号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;
}

看下面的结构,有什么想法为什么我会出现延迟吗?
编辑:我认为将setTcpNoDelay设置为true可以大大改善它。当我在我的端口狂按左/右键时,似乎还存在延迟...屏幕上的其他玩家看起来很卡顿。
            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();

从我看到的情况来看,我的“向左/向右闪烁”端好像少了服务器发送的数据包。

每次按下左键或右键时调用一次。 "int msgid = input.readUnsignedByte()" 暂停 while 循环,因此我可以在其中放置一个打印语句,并且在停止之前仅打印一次。一旦用户按下左键或右键,它将根据按键/接收到的消息再次打印。 - AlbertGeno
你能否在sendPacketToAllClients函数中加入println语句,以查看它被调用的频率? - faraza
在我的电脑和笔记本电脑上测试时,当两个用户同时按下键(或在同一秒内)时会出现延迟。就好像当我向服务器发送我的方向和速度时,在执行其他客户端的操作之前,它会出现延迟,然后再将数据包发送回给我。我很快就要睡觉了,6小时后还要工作。希望这个问题能够解决。真的很好奇到底发生了什么lol。 - AlbertGeno
当然我知道readUnsignedShort方法。只是每当我使用它时,它都不能给我正确的值,这真的非常奇怪,对吧? Math.signum() 用于服务器端运动计算。服务器知道玩家应该拥有的速度(spd),因此当客户端的maxSpd当前为5时,客户端不能告诉服务器它正在以100的hspd运行。目前使用字节交换来创建一个short值的2个字节。Java中的所有值都是有符号的,所以我必须手动将其设为无符号。关于您的建议,我一定会尝试一下!谢谢您的建议!!=) - AlbertGeno
EJB,你知道为什么会发生这种情况吗?output = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream())); output.writeShort(player.x); 服务器输出:591 客户端输出:20226 - AlbertGeno
显示剩余7条评论
1个回答

1
问题解决了。 =) setTcpNoDelay true起了作用。关于我说我丢失了数据包的部分,实际上我没有。这两个消息合并为一条消息。我的程序只读取了前几个字节并忽略了其余部分。必须在前面放置一个字节来指示消息的大小。一旦完成这个步骤,我设置了一个while循环来读取它,直到无法再读取为止。 =)谢谢大家的帮助。这是我的第一篇帖子,是一次难忘的经历。

请不要这样做。请修复真正的问题,而不是通过禁用 Nagling 来隐藏症状。这会使网络利用率降低,并且如果您的程序遇到 Nagling 修复的边缘情况,将导致不良行为。TCP 无延迟功能存在是为了让预 TCP 协议在 TCP 上更好地工作,对于设计用于使用 TCP 的东西来说是不需要的。 - David Schwartz
我想你是对的...我应该删除这个答案吗?我的意思是它确实有效,而且性能也很好。但我想我应该寻找另一个答案。有什么建议吗?=( - AlbertGeno
1
我曾经寻找了一段时间,有些人说为了低延迟,禁用它是好的,而对于高带宽大文件传输,启用它是好的,因为Nagle算法的意图是仅发送完整大小的数据包,这会导致小消息由于等待时间而延迟。我传输的消息相对较小,最常见的是5个字节。 - AlbertGeno
短小的消息如果每个都单独发送在自己的数据包中,则情况越糟糕,如果您落后,则追赶起来会更加困难。 - David Schwartz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接