Java NIO:transferFrom直到流结束

时间:2011-10-04 17:09:09

标签: java nio filechannel socketchannel

我正在玩NIO库。我正在尝试在端口8888上侦听连接,一旦接受连接,就将该通道中的所有内容转储到somefile

我知道如何使用ByteBuffers,但我想让它与所谓的超级效率FileChannel.transferFrom一起使用。

这就是我得到的:

ServerSocketChannel ssChannel = ServerSocketChannel.open();
ssChannel.socket().bind(new InetSocketAddress(8888));

SocketChannel sChannel = ssChannel.accept();
FileChannel out = new FileOutputStream("somefile").getChannel();

while (... sChannel has not reached the end of the stream ...)     <-- what to put here?
    out.transferFrom(sChannel, out.position(), BUF_SIZE);

out.close();

所以,我的问题是:如何在到达流末尾之前表达“ transferFrom某个频道”


编辑:将1024改为BUF_SIZE,因为所用缓冲区的大小与问题无关。

7 个答案:

答案 0 :(得分:11)

处理案件的方法很少。一些背景信息如何在内部实现trasnferTo / From以及何时可以实现优势。

  • 首先,您应该知道xfer需要多少字节,即使用FileChannel.size()确定可用的最大值并对结果求和。该案例涉及FileChannel.trasnferTo(socketChanel)
  • 该方法不返回-1
  • 该方法在Windows上进行模拟。 Windows没有从filedescriptor到socket xfer的API函数,它确实有一个(两个)xfer来自name指定的文件 - 但这与java API不兼容。
  • 在Linux上使用标准sendfile(或sendfile64),在Solaris上称为sendfilev64

简而言之for (long xferBytes=0; startPos + xferBytes<fchannel.size();) doXfer()将适用于从文件传输 - &gt;插座。 没有OS函数从socket转移到文件(OP感兴趣)。由于套接字数据不是操作系统缓存,因此无法有效地完成,它是模拟的。实现副本的最佳方法是使用带有套接字读缓冲区的轮询直接ByteBuffer标准循环。因为我只使用涉及选择器的非阻塞IO。

话虽如此:我想让它与所谓的超高效“?一起工作 - 它效率不高,并且在所有操作系统上都有效,因此它会在socket是否正常关闭。如果有任何传输(如果套接字是可读和打开的话),该函数甚至不会抛出继承的IOException。

我希望答案很明确:当源是文件时,File.transferFrom的唯一有趣用途就发生了。最有效(也是最有趣的情况)是file-&gt; socket和file-&gt;文件是通过filechanel.map / unmap(!!)实现的。

答案 1 :(得分:4)

我不确定,但JavaDoc说:

  

尝试可以读取源通道中的字节数   并将它们写入从该给定位置开始的该通道的文件。   调用此方法可能会也可能不会转移所有   请求的字节;是否这样做取决于取决于性质   和状态的渠道。少于请求的字节数   如果源通道的字节数少于count,则将传输   剩余,或者源通道是非阻塞且少于   计数在其输入缓冲区中立即可用的字节。

我想你可能会说它要复制无限字节(当然不是在循环中)会完成这项工作:

out.transferFrom(sChannel, out.position(), Integer.MAX_VALUE);

所以,我想当套接字连接关闭时,状态将会改变,这将停止transferFrom方法。

但正如我已经说过的那样:我不确定。

答案 2 :(得分:4)

直接回答您的问题:

while( (count = socketChannel.read(this.readBuffer) )  >= 0) {
   /// do something
}

但是,如果这是你做的,你不会使用非阻塞IO的任何好处,因为你实际上使用它完全阻止IO。非阻塞IO的意思是1个网络线程可以同时为多个客户端服务:如果没有任何东西可以从一个通道读取(即count == 0),您可以切换到其他通道(属于其他客户端连接)。

因此,循环应该实际迭代不同的通道,而不是从一个通道读取,直到它结束。

查看本教程:http://rox-xmlrpc.sourceforge.net/niotut/ 我相信它会帮助你理解这个问题。

答案 3 :(得分:1)

transferFrom()返回一个计数。只需继续调用它,推进位置/偏移,直到它返回零。但是从大于1024的数量开始,更像是一兆字节或两兆,否则你不会从这种方法中获得太多好处。

编辑为了解决下面的所有评论,文档说“如果源通道剩余的字节数少于计数字节,或者源通道是,则会传输少于请求的字节数非阻塞,并且在其输入缓冲区中可立即使用少于count个字节。“因此,如果您处于阻塞模式,它将不会返回零,直到源中没有任何内容。因此循环直到它返回零是有效的。

编辑2

传输方法肯定是错误设计的。它们应该被设计为在流的末尾返回-1,就像所有read()方法一样。

答案 4 :(得分:1)

  

据称是超高效的FileChannel.transferFrom。

如果你想要DMA访问和非阻塞IO的好处,最好的方法是对文件进行内存映射,然后只需从套接字读入内存映射缓冲区。

但这要求您预先分配文件。

答案 5 :(得分:1)

这样:

URLConnection connection = new URL("target").openConnection();
File file = new File(connection.getURL().getPath().substring(1));
FileChannel download = new FileOutputStream(file).getChannel();

while(download.transferFrom(Channels.newChannel(connection.getInputStream()),
        file.length(), 1024) > 0) {
    //Some calculs to get current speed ;)
}

答案 6 :(得分:0)

在此处撰写的其他人的基础上,这里有一个简单的帮助方法来实现目标:

public static void transferFully(FileChannel fileChannel, ReadableByteChannel sourceChannel, long totalSize) {
    for (long bytesWritten = 0; bytesWritten < totalSize;) {
        bytesWritten += fileChannel.transferFrom(sourceChannel, bytesWritten, totalSize - bytesWritten);
    }
}