Мне нужен ваш совет по пакету 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, поэтому не слишком сильно меня обвиняю в том, что я не вижу ничего очевидного :)
У кого-нибудь есть идеи?
Спасибо!
Определите «довольно большие задержки» и укажите, почему вы думаете, что это создает «так много сетевой нагрузки». Я бы сказал, что вы создаете слишком много объектов здесь и особенно слишком много «ByteBuffers». Попробуйте использовать один канал для жизни канала, возможно, два, один для чтения и один для записи. – EJP
Когда я нажимаю «ПРОСТРАНСТВО», я вижу сообщение о принятии пакета на стороне сервера примерно через 2/3 секунды. Я попытаюсь сделать меньше объектов буфера. –
Можно ли увидеть 'PacketReaderWriter'? –