2014-11-06 3 views
1

Мне нужен ваш совет по пакету Java NIO. У меня проблема с задержками при отправке пакетов по сети. Исходный код на самом деле мой port of the SFML book source code to Java, но здесь я покажу вам только минимальный рабочий пример, где проблема воспроизводится. Хотя этот код содержит некоторые фрагменты из библиотеки SFML (фактически создавая окно и цикл событий), я считаю, что это не влияет на проблему.Задержки при отправке данных с помощью Java NIO

Здесь я покажу только части кода, полная версия доступна here.

Итак, программа имеет два объекта: сервер и клиент. Если вы запускаете приложение в режиме сервера, тогда создается Сервер, начинает прослушивать новые подключения, а новый Клиент автоматически создается и пытается подключиться к Серверу. В клиентском режиме создается только Клиент и подключается к Серверу.

Приложение также создает новое базовое окно GUI и запускает цикл событий, где все происходит.

Клиент отправляет пакеты на Сервер. Он обрабатывает их, просто регистрируя факт принятия. Существует два типа пакетов, которые Клиент может отправить: периодический пакет (с инкрементным идентификатором) и пакет событий (приложение реагирует на нажатие кнопок SPACE или M).

Клиент посылает пакеты:

public void update(Time dt) throws IOException { 
    if (!isConnected) return; 

    if (tickClock.getElapsedTime().compareTo(Time.getSeconds(1.f/20.f)) > 0) { 
     Packet intervalUpdatePacket = new Packet(); 
     intervalUpdatePacket.append(PacketType.INTERVAL_UPDATE); 
     intervalUpdatePacket.append(intervalCounter++); 

     PacketReaderWriter.send(socketChannel, intervalUpdatePacket); 

     tickClock.restart(); 
    } 
} 

public void handleEvent(Event event) throws IOException { 
    if (isConnected && (event.type == Event.Type.KEY_PRESSED)) { 
     KeyEvent keyEvent = event.asKeyEvent(); 

     if (keyEvent.key == Keyboard.Key.SPACE) { 
      LOGGER.info("press SPACE"); 
      Packet spacePacket = new Packet(); 
      spacePacket.append(PacketType.SPACE_BUTTON); 
      PacketReaderWriter.send(socketChannel, spacePacket); 
     } 

     if (keyEvent.key == Keyboard.Key.M) { 
      LOGGER.info("press M"); 
      Packet mPacket = new Packet(); 
      mPacket.append(PacketType.M_BUTTON); 
      PacketReaderWriter.send(socketChannel, mPacket); 
     } 
    } 
} 

сервер принимает пакеты:

private void handleIncomingPackets() throws IOException { 
    readSelector.selectNow(); 

    Set<SelectionKey> readKeys = readSelector.selectedKeys(); 
    Iterator<SelectionKey> it = readKeys.iterator(); 

    while (it.hasNext()) { 
     SelectionKey key = it.next(); 
     it.remove(); 

     SocketChannel channel = (SocketChannel) key.channel(); 

     Packet packet = null; 
     try { 
      packet = PacketReaderWriter.receive(channel); 
     } catch (NothingToReadException e) { 
      e.printStackTrace(); 
     } 

     if (packet != null) { 
      // Interpret packet and react to it 
      handleIncomingPacket(packet, channel); 
     } 
    } 
} 

private void handleIncomingPacket(Packet packet, SocketChannel channel) { 
    PacketType packetType = (PacketType) packet.get(); 

    switch (packetType) { 
     case INTERVAL_UPDATE: 
      int intervalId = (int) packet.get(); 
      break; 
     case SPACE_BUTTON: 
      LOGGER.info("handling SPACE button"); 
      break; 
     case M_BUTTON: 
      LOGGER.info("handling M button"); 
      break; 
    } 
} 

Вот PacketReaderWriter объект:

package server; 

import java.io.*; 
import java.nio.ByteBuffer; 
import java.nio.channels.SocketChannel; 

public class PacketReaderWriter { 
    private static final int PACKET_SIZE_LENGTH = 4; 
    private static final ByteBuffer packetSizeReadBuffer = ByteBuffer.allocate(PACKET_SIZE_LENGTH); 
    private static ByteBuffer clientReadBuffer; 

    private static byte[] encode(Packet packet) throws IOException { 
     try (
      ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
      ObjectOutputStream oos = new ObjectOutputStream(baos) 
     ) { 
      oos.writeObject(packet); 
      return baos.toByteArray(); 
     } 
    } 

    private static Packet decode(byte[] encodedPacket) throws IOException, ClassNotFoundException { 
     try (ObjectInputStream oi = new ObjectInputStream(new ByteArrayInputStream(encodedPacket))) { 
      return (Packet) oi.readObject(); 
     } 
    } 

    public static void send(SocketChannel channel, Packet packet) throws IOException { 
     byte[] encodedPacket = encode(packet); 

     ByteBuffer packetSizeBuffer = ByteBuffer.allocate(PACKET_SIZE_LENGTH).putInt(encodedPacket.length); 
     packetSizeBuffer.flip(); 

     // Send packet size 
     channel.write(packetSizeBuffer); 

     // Send packet content 
     ByteBuffer packetBuffer = ByteBuffer.wrap(encodedPacket); 
     channel.write(packetBuffer); 
    } 

    public static Packet receive(SocketChannel channel) throws IOException, NothingToReadException { 
     int bytesRead; 

     // Read packet size 
     packetSizeReadBuffer.clear(); 
     bytesRead = channel.read(packetSizeReadBuffer); 

     if (bytesRead == -1) { 
      channel.close(); 
      throw new NothingToReadException(); 
     } 

     if (bytesRead == 0) return null; 

     packetSizeReadBuffer.flip(); 
     int packetSize = packetSizeReadBuffer.getInt(); 

     // Read packet 
     clientReadBuffer = ByteBuffer.allocate(packetSize); 
     bytesRead = channel.read(clientReadBuffer); 

     if (bytesRead == -1) { 
      channel.close(); 
      throw new NothingToReadException(); 
     } 

     if (bytesRead == 0) return null; 

     clientReadBuffer.flip(); 
     ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
     baos.write(clientReadBuffer.array(), 0, bytesRead); 
     clientReadBuffer.clear(); 

     try { 
      return decode(baos.toByteArray()); 
     } catch (ClassNotFoundException e) { 
      e.printStackTrace(); 
      return null; 
     } 
    } 
} 

И вот проблема: у меня есть довольно большие задержки между нажатием кнопки (и отправкой соответствующего пакета из Клиента) и принимать используя этот пакет на сервере. Если я запускаю новый экземпляр приложения в клиентском режиме (просто добавьте новый клиент короче), задержки становятся еще большими.

Я не вижу причин, почему эти периодические пакеты создают так много сетевой нагрузки, что другие пакеты просто не могут пройти, но, возможно, я просто что-то пропустил. Здесь я должен сказать, что я не эксперт по Java, поэтому не слишком сильно меня обвиняю в том, что я не вижу ничего очевидного :)

У кого-нибудь есть идеи?

Спасибо!

+0

Определите «довольно большие задержки» и укажите, почему вы думаете, что это создает «так много сетевой нагрузки». Я бы сказал, что вы создаете слишком много объектов здесь и особенно слишком много «ByteBuffers». Попробуйте использовать один канал для жизни канала, возможно, два, один для чтения и один для записи. – EJP

+0

Когда я нажимаю «ПРОСТРАНСТВО», я вижу сообщение о принятии пакета на стороне сервера примерно через 2/3 секунды. Я попытаюсь сделать меньше объектов буфера. –

+0

Можно ли увидеть 'PacketReaderWriter'? –

ответ

1

Я решил взглянуть на репозиторий Github.

Ваш Server.run() выглядит так.

public void run() { 
    while (isRunning) { 
     try { 
      handleIncomingConnections(); 
      handleIncomingPackets(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 

     try { 
      // Sleep to prevent server from consuming 100% CPU 
      sleep(100); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
    } 
} 

Сон (100) приведет к приблизительно 10 вызовам handleIncomingPackets() в секунду. handleIncomingPackets(), в свою очередь, выберет клиентский канал и вызовет handleIncomingPacket() в одном полученном пакете. В общем случае сервер сможет обрабатывать 10 пакетов/секунду на одного клиента, если я его правильно понимаю.

Клиент, с другой стороны, пытается отправить 20 пакетов в секунду типа PacketType.INTERVAL_UPDATE. Либо клиент должен отправлять меньше пакетов в секунду, либо сервер должен иметь возможность обрабатывать больше пакетов в секунду.

Текущий сон (100) означает, что всегда будет время ожидания до 100 мс, прежде чем сервер сможет ответить на один пакет даже в ситуации, не связанной с перегрузкой. Это может быть хорошо, если вы убедитесь, что действительно прочитали все пакеты, доступные на канале, а не только один раз каждый раз.

Вкратце: минимальное изменение, которое вам нужно будет сделать для улучшения времени отклика, заключается в уменьшении времени сна(). 10 мс будет нормально. Но я также предлагаю попробовать проверить, доступно ли на каждой итерации более одного пакета.

Обновление: В файле C++, с которым я связался, я подозреваю, что он читает более одного пакета на итерацию.

<snip> 
while (peer->socket.receive(packet) == sf::Socket::Done) 
     { 
      // Interpret packet and react to it 
      handleIncomingPacket(packet, *peer, detectedTimeout); 
</snip> 

Цикл, пока будет считывать все доступные пакеты. По сравнению с вашей версией Java, где вы читаете один пакет на каждого клиента на итерацию сервера.

if (packet != null) { 
    // Interpret packet and react to it 
    handleIncomingPacket(packet, channel); 
} 

Вам необходимо убедиться, что вы также прочитали все доступные пакеты в версии Java.

Если вы просто хотите убедиться, что клиентский код отправляет больше пакетов, чем может обрабатывать серверный код, это быстро выполняется, временно назначая sleep() на 10 мс.

+0

Спасибо за ответ, но я не уверен, что это проблема, потому что мой код - просто простой порт исходного кода на C++, который вы можете найти здесь (https://github.com/) LaurentGomila/SFML-Game-Разработка-Book/BLOB/Master/10_Network/Source/GameServer.cpp). И он отлично работает :) –

+1

Обновлен ответ после сканирования файла C++. – Rikard

+0

Спасибо, я проверю. –

Смежные вопросы