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

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

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

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


person jpdaigle    schedule 01.10.2008    source источник
comment
Обновление: я пробовал Sun VM 1.5, 1.6 и JRockit VM 1.5, чтобы убедиться, что это поведение сети не зависит от виртуальной машины.   -  person jpdaigle    schedule 01.10.2008


Ответы (6)


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

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
...  
person Alexander    schedule 01.10.2008

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

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

person Spencer Kormos    schedule 01.10.2008

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

person Clay    schedule 01.10.2008
comment
Спасибо за идею. Я только что сделал несколько сотен многомегабайтных операций записи, и ни одна из них не вернула значение меньше полной суммы. - person jpdaigle; 01.10.2008
comment
Есть ли шанс, что вы можете посмотреть трафик с помощью сниффера и увидеть, как пакеты отправляются? Может дать вам подсказку, пытается ли отправляющая библиотека даже разбить пакеты на фрагменты меньше MTU. - person stu; 02.10.2008

Вам действительно стоит обратить внимание на структуру NIO, такую ​​как MINA или Гризли. Я с большим успехом использовал MINA на корпоративном чат-сервере. Он также используется в чат-сервере Openfire. Grizzly используется в реализации Sun JavaEE.

person Heath Borders    schedule 02.10.2008

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

person Darron    schedule 02.10.2008
comment
Ну да, но в TCP не может быть никаких данных в полете, которых нет в буфере отправки. - person user207421; 11.11.2011

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

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

person keithmo    schedule 02.04.2012
comment
Это неправильно. В неблокирующем режиме он может возвращать 0 <= n <= length. См. Javadoc. - person user207421; 22.04.2021