我正在摆弄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,因为所使用的缓冲区大小与问题无关。
处理这种情况有几种方法。一些背景信息如何在内部实现transferto/From以及何时可以更优。
- 首先,你应该知道有多少字节你必须传递,即使用
FileChannel.size()
来确定最大可用和结果的总和。本案例参照FileChannel.trasnferTo(socketChanel)
- 方法不返回-1
- 该方法在Windows上进行了仿真。Windows没有从文件描述符传递到套接字的API函数,它确实有一(两个)从名称指定的文件传递-但这与java API不兼容。
- 在Linux上使用标准sendfile(或sendfile64),在Solaris上称为
sendfilev64
。
简而言之,for (long xferBytes=0; startPos + xferBytes<fchannel.size();) doXfer()
将用于从file -> socket传输。没有从套接字到文件(OP感兴趣的)传输的操作系统函数。由于套接字数据不在操作系统缓存中,因此无法有效地做到这一点,因此进行了模拟。实现复制的最佳方法是通过标准循环使用与套接字读缓冲区大小相同的轮询直接ByteBuffer。因为我只使用涉及选择器的非阻塞IO。
话虽如此:我想让它与据称超级高效的"?" -它效率不高,并且在所有操作系统上都是模拟的,因此当套接字是否正常关闭时,它将结束传输。如果存在任何传输(如果套接字是可读且打开的),该函数甚至不会抛出继承的IOException。
我希望答案是清楚的:File.transferFrom
的唯一有趣的使用发生在源是一个文件时。最有效(和有趣的情况)是file->socket和file->file是通过filechanel.map
/unmap(!!)实现的。
直接回答你的问题:
while( (count = socketChannel.read(this.readBuffer) ) >= 0) {
/// do something
}
但是如果你这样做,你就没有使用非阻塞IO的任何好处,因为你实际上把它当作阻塞IO使用。非阻塞IO的要点是一个网络线程可以同时服务多个客户端:如果没有从一个通道(即count == 0
)读取,您可以切换到其他通道(属于其他客户端连接)。
因此,循环实际上应该迭代不同的通道,而不是从一个通道读取直到结束。
看看这个教程:http://rox-xmlrpc.sourceforge.net/niotut/我相信这会帮助你理解这个问题。
我不确定,但是JavaDoc说:
一个尝试从源通道读取到count字节并从给定位置开始将它们写入通道文件。此方法的调用可能会也可能不会传输所有的所请求的字节数;是否这样做取决于的性质和通道的状态。小于请求的字节数如果源信道的字节数少于count,会被传输吗剩余,或者如果源通道非阻塞并且小于计算输入缓冲区中立即可用的字节数。
我想你可能会说告诉它复制无限字节(当然不是在循环中)会完成工作:
out.transferFrom(sChannel, out.position(), Integer.MAX_VALUE);
所以,我猜当套接字连接关闭时,状态将被改变,这将停止transferFrom
方法。
但正如我已经说过的:我不确定。
据称超级高效的filecchannel . transferfrom .
如果你想要DMA访问和非阻塞IO的好处,最好的方法是内存映射文件,然后从套接字读取到内存映射的缓冲区。
但是这需要预先分配文件。
这样
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 ;)
}
transferFrom()
返回一个计数。只要继续调用它,推进位置/偏移量,直到它返回零。但是从比1024大得多的计数开始,更像是1或2兆字节,否则您不会从这种方法中获得多少好处。
EDIT为了解决下面的所有注释,文档说"如果源通道剩余的字节数少于count,或者源通道非阻塞并且其输入缓冲区中立即可用的字节数少于count,则传输的字节数将少于请求的字节数。"因此,如果您处于阻塞模式,它将不会返回零,直到源中没有任何剩余。所以循环直到返回0是有效的。
编辑2
传递方法肯定是设计错误的。它们应该被设计成在流结束时返回-1,就像所有read()
方法一样。
在这里其他人写的东西的基础上,这里有一个简单的帮助方法来实现这个目标:
public static void transferFully(FileChannel fileChannel, ReadableByteChannel sourceChannel, long totalSize) {
for (long bytesWritten = 0; bytesWritten < totalSize;) {
bytesWritten += fileChannel.transferFrom(sourceChannel, bytesWritten, totalSize - bytesWritten);
}
}