Почему записи SocketChannel всегда завершаются на полную сумму даже на неблокирующих сокетах?

StackOverflow https://stackoverflow.com/questions/158121

Вопрос

Используя Sun Java VM 1.5 или 1.6 в Windows, я подключаю неблокирующий сокет.Затем я заполняю ByteBuffer с сообщением для вывода и попытайтесь write() к сетевому каналу.

Я ожидаю, что запись завершится только частично, если подлежащий записи объем больше, чем объем пространства в выходном буфере TCP сокета (это то, чего я ожидаю интуитивно, это также в значительной степени соответствует моему пониманию Документы), но это не то, что происходит.Тот Самый write() всегда кажется, возвращает отчет о полном записанном объеме, даже если это несколько мегабайт (SO_SNDBUF сокета составляет 8 КБ, что намного, намного меньше, чем мое многомегабайтное выходное сообщение).

Проблема здесь в том, что я не могу протестировать код, который обрабатывает случай, когда выходные данные частично записаны (регистрируя интересующий набор WRITE к селектору и выполнение select() подождать, пока можно будет записать оставшуюся часть), поскольку такого случая, похоже, никогда не произойдет.Чего я не понимаю?

Это было полезно?

Решение

Мне удалось воспроизвести ситуацию, которая может быть похожа на вашу.Я думаю, что, по иронии судьбы, ваш получатель использует данные быстрее, чем вы их записываете.

import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class MyServer {
  public static void main(String[] args) throws Exception {
    final ServerSocket ss = new ServerSocket(12345);
    final Socket cs = ss.accept();
    System.out.println("Accepted connection");

    final InputStream in = cs.getInputStream();
    final byte[] tmp = new byte[64 * 1024];
    while (in.read(tmp) != -1);

    Thread.sleep(100000);
  }
}



import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class MyNioClient {
  public static void main(String[] args) throws Exception {
    final SocketChannel s = SocketChannel.open();
    s.configureBlocking(false);
    s.connect(new InetSocketAddress("localhost", 12345));
    s.finishConnect();

    final ByteBuffer buf = ByteBuffer.allocate(128 * 1024);
    for (int i = 0; i < 10; i++) {
      System.out.println("to write: " + buf.remaining() + ", written: " + s.write(buf));
      buf.position(0);
    }
    Thread.sleep(100000);
  }
}

Если вы запустите вышеупомянутый сервер, а затем заставите вышеупомянутый клиент попытаться записать 10 фрагментов данных по 128 кБ, вы увидите, что каждая операция записи записывает весь буфер без блокировки.Однако, если вы измените указанный выше сервер, чтобы он ничего не считывал из соединения, вы увидите, что только первая операция записи на клиенте запишет 128 кБ, тогда как все последующие операции записи будут возвращать 0.

Выводится, когда сервер считывает данные из соединения:

to write: 131072, written:  131072
to write: 131072, written:  131072
to write: 131072, written:  131072
...

Вывод, когда сервер не выполняет чтение из соединения:

to write: 131072, written:  131072
to write: 131072, written:  0
to write: 131072, written:  0
...  

Другие советы

Я работал с UDP на Java и видел действительно "интересное" и полностью недокументированное поведение в Java NIO в целом.Лучший способ определить, что происходит, - это посмотреть на исходный код, который поставляется вместе с Java.

Я также готов поспорить, что вы могли бы найти лучшую реализацию того, что ищете, в любой другой реализации JVM, такой как IBM, но я не могу гарантировать этого, не взглянув на них лично.

Я нигде не могу найти это документированным, но IIRC [1], send() гарантированно либо а) отправит предоставленный буфер полностью, либо б) завершится сбоем.Это никогда не завершит отправку частично.

[1] Я написал несколько реализаций Winsock (для Win 3.0, Win 95, Win NT и т.д.), Так что это может быть поведение, специфичное для Winsock (а не для общих сокетов).

Я совершу большой скачок веры и предположу, что базовый сетевой провайдер для Java такой же, как и для C ... O / S выделяет больше, чем просто SO_SNDBUF для каждого сокета.Бьюсь об заклад, если вы поместите свой код отправки в цикл for (1,100000), вы в конечном итоге получите успешную запись со значением, меньшим запрошенного.

Вам действительно следует взглянуть на фреймворк NIO, такой как МИНА или Гризли.Я с большим успехом использовал MINA на корпоративном сервере чатов.Он также используется в Открытый Огонь сервер чата.Grizzly используется в реализации Sun на JavaEE.

Куда вы отправляете данные?Имейте в виду, что сеть действует как буфер, размер которого, по крайней мере, равен вашему SO_SNDBUF плюс SO_RCVBUF получателя.Добавьте это к активности считывания получателем, как упоминал Александр, и вы сможете получить большое количество впитанных данных.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top