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

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

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

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

Ответы [ 6 ]

7 голосов
/ 01 октября 2008

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

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
...  
2 голосов
/ 01 октября 2008

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

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

1 голос
/ 02 апреля 2012

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

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

0 голосов
/ 02 октября 2008

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

0 голосов
/ 02 октября 2008

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

0 голосов
/ 01 октября 2008

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

...