Por que SocketChannel escreve sempre completar para o montante total mesmo em soquetes sem bloqueio?

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

Pergunta

Usando o Sun Java VM 1.5 ou 1.6 no Windows, eu conectar um soquete sem bloqueio. Eu, então, preencher uma ByteBuffer com uma mensagem de saída e tentar write() ao SocketChannel.

Eu espero que a gravação seja concluída apenas parcialmente, se o valor a ser escrito é maior do que a quantidade de espaço no buffer de saída TCP do socket (este é o que eu espero intuitivamente, também é praticamente o meu entendimento do docs ), mas isso não é o que acontece. O write() sempre parece retornar informando o valor total escrito, mesmo que seja vários megabytes (SO_SNDBUF do soquete é 8KB, muito, muito menos do que a minha mensagem de saída multi-megabyte).

Um problema aqui é que eu não posso testar o código que trata o caso em que a saída é parcialmente escrito (registrar um conjunto de juros de WRITE de um seletor e fazendo um select() que esperar até que o restante pode ser escrito), como Nesse caso, parece nunca acontecer. O que não estou entendendo?

Foi útil?

Solução

Eu consegui reproduzir uma situação que poderia ser semelhante ao seu. Eu acho que, ironicamente, o seu destinatário está consumindo os dados mais rápido do que você está escrevendo-lo.

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);
  }
}

Se você executar o servidor acima e, em seguida, fazer a tentativa cliente acima para escrever 10 pedaços de 128 KB de dados, você verá que cada operação de escrita escreve todo o tampão sem bloqueio. No entanto, se você modificar o servidor acima não ler qualquer coisa a partir da conexão, você verá que apenas a primeira operação de gravação no cliente irá escrever 128 kB, enquanto que todas as gravações subseqüentes retornará 0.

Saída quando o servidor está lendo a partir da conexão:

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

Saída quando o servidor não está a ler a partir da conexão:

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

Outras dicas

Eu tenho trabalhado com UDP em Java e tenho visto alguns comportamentos muito "interessante" e completamente sem documentos no material Java NIO em geral. A melhor maneira de determinar o que está acontecendo é olhar para a fonte que vem com Java.

Eu também seria capaz de apostar em vez altamente que você pode encontrar uma melhor implementação do que você está procurando em qualquer outra implementação JVM, como IBM, mas não pode garantir que, sem olhar para eles eu mesmo.

Eu não posso encontrá-lo documentado em qualquer lugar, mas IIRC [1], send () é garantido para qualquer um) enviar o buffer fornecido completamente, ou b) falhar. Isso nunca vai completar o envio parcialmente.

[1] Eu escritas várias implementações Winsock (para Windows 3.0, Windows 95, Windows NT, etc.), de modo que este pode ser específico do Winsock (em vez de bases genéricas) comportamento.

Eu vou fazer um grande salto de fé e assumir que o provedor de rede subjacente para Java é o mesmo que para o C ... o O / S aloca mais do que apenas SO_SNDBUF para cada soquete. Aposto que se você colocar o seu código de envio de uma for (1,100000) loop, você acabaria por ter uma gravação que sucede com um valor menor do que o solicitado.

Você realmente deve olhar para um quadro NIO como MINA ou Grizzly . Eu usei MINA com grande sucesso em um servidor corporativo chat. Ele também é usado na href="http://www.igniterealtime.org/projects/openfire/index.jsp" rel="nofollow noreferrer"> Openfire servidor

Onde você está enviando os dados? Tenha em mente que a rede atua como um tampão que é pelo menos igual em tamanho à sua SO_SNDBUF mais SO_RCVBUF do receptor. Adicione isto a atividade de leitura pelo receptor como mencionado por Alexander e você pode obter uma grande quantidade de dados absorvido.

scroll top