为什么这个非阻塞IO调用失败?

时间:2017-01-30 12:25:16

标签: java sockets nio serversocket

背景

  • 我希望使用Java的非阻塞SocketChannel发送大量(30MB,但未来可能更大)数据量
    • 为什么不阻挡?因此,计算要发送的下一个字节不会等待网络
  • 当我在阻止模式下使用SocketChannel时,传输完成没有问题
  • 当我将SocketChannel设置为非阻塞时,它会更快地完成 ,但服务器并未接收到所有数据
    • 服务器确实收到部分数据,但

问题

  • 使用非阻塞Java NIO SocketChannel时,为什么我的大型(30MB)文件传输失败,我该如何解决?

文件

  • 我扼杀了程序并编写了示例,以便它可以通过javac *.java && java Main

    一次性运行
    • 它为服务器创建一个Future,一个客户端的Future,让客户端向服务器发送30MB随机字节,然后在主线程上阻塞,直到两个Futures完成(虽然服务器永远不会)
  • 注意:这主要是关于TheClient.java

    • 如果注释<CommentOutToMakeWork></CommentOutToMakeWork>之间的行被注释掉,则SocketChannel将被阻止,传输将完成

Main.java

import java.io.IOException;
import java.lang.InterruptedException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Main {
  public static void main(String[] args) throws ExecutionException, IOException, InterruptedException {
    final SocketAddress address = new InetSocketAddress("127.0.0.1", 12345);
    final int size              = 30 * 1000 * 1000;

    ExecutorService executor = Executors.newFixedThreadPool(2);
    TheServer theServer      = new TheServer(address, size);
    TheClient theClient      = new TheClient(address, size);

    Future<String> serverFuture = executor.submit(theServer);
    Thread.sleep(2000);
    Future<String> clientFuture = executor.submit(theClient);

    System.out.println("MAIN: Received from client: " + clientFuture.get());
    System.out.println("MAIN: Received from server: " + serverFuture.get());
    executor.shutdown();
  }
}

TheClient.java

import java.io.IOException;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Random;
import java.util.concurrent.Callable;

class TheClient implements Callable<String> {
  private TheClient() {}
  public TheClient(SocketAddress address, int size) {
    this.size = size;
    this.from = new byte[size];
    this.serverAddress = address;
    new Random().nextBytes(from);
  }

  private int           size;
  private byte[]        from;
  private SocketAddress serverAddress;

  public String call() throws IOException {
    SocketChannel socketChannel = SocketChannel.open();
    System.out.println("CLIENT: Attempting to connect to server...");
    socketChannel.connect(serverAddress);
    // <CommentOutToMakeWork>
    socketChannel.configureBlocking(false);
    // </CommentOutToMakeWork>
    System.out.println("CLIENT: Connection established. Sending " + size + " bytes.");

    // For this example, this is one large write, but even my actual
    // program, which uses a loop and puts smaller chunks onto the channel,
    // is too fast for the SocketChannel.

    socketChannel.write(ByteBuffer.wrap(from));

    System.out.println("CLIENT: Write completed.");
    return "CLIENT: Success!";
  }
}

TheServer.java

import java.io.IOException;
import java.io.InputStream;
import java.net.SocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.util.Random;
import java.util.concurrent.Callable;

class TheServer implements Callable<String> {
  private TheServer() {}
  public TheServer(SocketAddress address, int size) {
    this.size = size;
    this.to = new byte[size];
    this.serverAddress = address;
  }
  private int           size;
  private byte[]        to;
  private SocketAddress serverAddress;

  public String call() throws IOException {
    ServerSocketChannel serverChannel = ServerSocketChannel.open().bind(serverAddress);
    System.out.println("SERVER: Awaiting connection...");
    InputStream clientSocketInputStream = serverChannel.accept().socket().getInputStream();
    System.out.println("SERVER: Connection established. Attempting to read " + size + " bytes.");
    for (int i = 0; i < size; ++i) {
      to[i] = (byte) clientSocketInputStream.read();
    }
    System.out.println("SERVER: Read completed.");
    return "SERVER: Success!";
  }
}

1 个答案:

答案 0 :(得分:5)

我认为答案在于WritableByteChannel.write文档:

  

除非另有说明,否则只有在写入所有r请求的字节后才会返回写操作。某些类型的通道(取决于它们的状态)可能只写入一些字节或者根本不写。 例如,非阻塞模式下的套接字通道不能再写入套接字输出缓冲区中可用的字节数。

所以看起来你需要使用write的返回值来找出已经写了多少,并且在没有写完所有内容的情况下处理。从描述中可以清楚地看出如何处理这种情况 - 例如,您可能会发现需要进行一些调度以在套接字输出缓冲区耗尽时继续写入。