18

我在玩 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,因为使用的缓冲区大小与问题无关。

4

7 回答 7

13

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

  • 首先,您应该知道您必须 xfer 多少字节,即用于FileChannel.size()确定可用的最大值并对结果求和。该案指FileChannel.trasnferTo(socketChanel)
  • 该方法不返回 -1
  • 该方法是在 Windows 上模拟的。Windows 没有从文件描述符 xfer 到套接字的 API 函数,它确实有一个(两个)从名称指定的文件中 xfer 的功能 - 但这与 java API 不兼容。
  • 在 Linux 上使用标准的sendfile(或 sendfile64),在 Solaris 上称为sendfilev64.

简而言之for (long xferBytes=0; startPos + xferBytes<fchannel.size();) doXfer(),适用于从文件-> 套接字传输。没有从套接字传输到文件的 OS 函数(OP 对此感兴趣)。由于套接字数据不在操作系统缓存中,因此无法如此有效地完成,因此是模拟的。实现复制的最佳方式是通过标准循环,使用轮询的直接 ByteBuffer,其大小与套接字读取缓冲区一起调整。因为我只使用涉及选择器的非阻塞 IO。

话虽这么说:我想让它与据称超级高效的“?”一起工作- 它效率不高,并且在所有操作系统上都被模拟,因此当套接字正常关闭或不正常关闭时,它将结束传输。该功能将甚至不抛出继承的 IOException,只要有任何传输(如果套接字是可读和打开的)。

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

于 2012-05-08T06:00:14.613 回答
4

我不确定,但 JavaDoc 说:

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

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

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

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

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

于 2011-10-04T17:23:03.340 回答
4

直接回答你的问题:

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

但是,如果您这样做,则不会使用非阻塞 IO 的任何好处,因为您实际上将其完全用作阻塞 IO。非阻塞 IO 的要点是 1 个网络线程可以同时为多个客户端提供服务:如果从一个通道(即count == 0)没有可读取的内容,您可以切换到另一个通道(属于其他客户端连接)。

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

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

于 2011-10-04T17:25:28.877 回答
1

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

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

编辑 2

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

于 2011-10-05T02:47:09.127 回答
1

据称超级高效的 FileChannel.transferFrom。

如果您想要 DMA 访问和非阻塞 IO 的好处,最好的方法是内存映射文件,然后从套接字读取到内存映射缓冲区。

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

于 2012-05-06T23:02:04.427 回答
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 ;)
}
于 2015-06-06T23:53:30.040 回答
0

建立在其他人所写的之上,这是一个实现目标的简单辅助方法:

public static void transferFully(FileChannel fileChannel, ReadableByteChannel sourceChannel, long totalSize) {
    for (long bytesWritten = 0; bytesWritten < totalSize;) {
        bytesWritten += fileChannel.transferFrom(sourceChannel, bytesWritten, totalSize - bytesWritten);
    }
}
于 2018-01-25T03:38:01.820 回答