简体   繁体   English

Java NIO:transferFrom直到流结束

[英]Java NIO: transferFrom until end of stream

I'm playing around with the NIO library. 我正在玩NIO库。 I'm attempting to listen for a connection on port 8888 and once a connection is accepted, dump everything from that channel to somefile . 我正在尝试在端口8888上侦听连接,一旦接受连接,就将所有内容从该通道转储到somefile

I know how to do it with ByteBuffers , but I'd like to get it working with the allegedly super efficient FileChannel.transferFrom . 我知道如何使用ByteBuffers ,但我想让它与所谓的超高效FileChannel.transferFrom

This is what I got: 这就是我得到的:

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

So, my question is: How do I express " transferFrom some channel until end-of-stream is reached" ? 所以,我的问题是:我如何表达“从一些频道transferFrom直到达到流尾”


Edit: Changed 1024 to BUF_SIZE, since the size of the buffer used, is irrelevant for the question. 编辑:将1024更改为BUF_SIZE,因为所使用的缓冲区大小与问题无关。

There are few ways to handle the case. 处理案件的方法很少。 Some background info how trasnferTo/From is implemented internally and when it can be superior. 一些背景信息如何在内部实现trasnferTo / From以及何时可以实现。

  • 1st and foremost you should know how many bytes you have to xfer, ie use FileChannel.size() to determine the max available and sum the result. 首先,您应该知道xfer需要多少字节,即使用FileChannel.size()来确定可用的最大值并对结果求和。 The case refers to FileChannel.trasnferTo(socketChanel) 案例指的是FileChannel.trasnferTo(socketChanel)
  • The method does not return -1 该方法不返回-1
  • The method is emulated on Windows. 该方法在Windows上进行模拟。 Windows doesn't have an API function to xfer from filedescriptor to socket, it does have one (two) to xfer from the file designated by name - but that's incompatible with java API. Windows没有从filedescriptor到socket xfer的API函数,它确实从name指定的文件中有一(2)到xfer - 但这与java API不兼容。
  • On Linux the standard sendfile (or sendfile64) is used, on Solaris it's called sendfilev64 . 在Linux上,使用标准的sendfile (或sendfile64),在Solaris上称为sendfilev64

in short for (long xferBytes=0; startPos + xferBytes<fchannel.size();) doXfer() will work for transfer from file -> socket. 简而言之for (long xferBytes=0; startPos + xferBytes<fchannel.size();) doXfer()将用于从文件 - >套接字传输。 There is no OS function that transfers from socket to file (which the OP is interested in). 没有OS函数从socket转移到文件(OP感兴趣)。 Since the socket data is not int he OS cache it can't be done so effectively, it's emulated. 由于套接字数据不是操作系统缓存,因此无法有效地完成,它是模拟的。 The best way to implement the copy is via standard loop using a polled direct ByteBuffer sized with the socket read buffer. 实现副本的最佳方法是使用带有套接字读缓冲区的轮询直接ByteBuffer标准循环。 Since I use only non-blocking IO that involves a selector as well. 因为我只使用涉及选择器的非阻塞IO。

That being said: I'd like to get it working with the allegedly super efficient "? - it is not efficient and it's emulated on all OSes, hence it will end up the transfer when the socket is closed gracefully or not. The function will not even throw the inherited IOException, provided there was ANY transfer (If the socket was readable and open). 话虽这么说: 我想让它与所谓的超高效“? - 它效率不高并且它在所有操作系统上模拟,因此当套接字正常关闭时它将最终转移。该功能将如果有任何转移(如果套接字是可读和打开的),甚至不抛出继承的IOException。

I hope the answer is clear: the only interesting use of File.transferFrom happens when the source is a file. 我希望答案很清楚:当源是文件时,会发生File.transferFrom唯一有趣的用法。 The most efficient (and interesting case) is file->socket and file->file is implemented via filechanel.map / unmap(!!) . 最有效(也是最有趣的情况)是file-> socket和file-> file是通过filechanel.map / unmap(!!)实现的

I'm not sure, but the JavaDoc says: 我不确定,但JavaDoc说:

An attempt is made to read up to count bytes from the source channel and write them to this channel's file starting at the given position. 尝试从源通道读取计数字节,并将它们从给定位置开始写入该通道的文件。 An invocation of this method may or may not transfer all of the requested bytes; 调用此方法可能会也可能不会传输所有请求的字节; whether or not it does so depends upon the natures and states of the channels. 是否这样做取决于渠道的性质和状态 Fewer than the requested number of bytes will be transferred if the source channel has fewer than count bytes remaining, or if the source channel is non-blocking and has fewer than count bytes immediately available in its input buffer. 如果源通道剩余的字节数少于计数字节,或者源通道非阻塞且输入缓冲区中可立即使用的字节数少于计数字节,则将传输少于请求的字节数。

I think you may say that telling it to copy infinite bytes (of course not in a loop) will do the job: 我想你可以说告诉它复制无限字节(当然不是在循环中)将完成这项工作:

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

So, I guess when the socket connection is closed, the state will get changed, which will stop the transferFrom method. 所以,我想当套接字连接关闭时,状态将会改变,这将停止transferFrom方法。

But as I already said: I'm not sure. 但正如我已经说过的那样:我不确定。

Answering your question directly: 直接回答你的问题:

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

But if this is what you do you do not use any benefits of non-blocking IO because you actually use it exactly as blocking IO. 但是,如果这是你做的,你不会使用非阻塞IO的任何好处,因为你实际上使用它完全阻止IO。 The point of non-blocking IO is that 1 network thread can serve several clients simultaneously: if there is nothing to read from one channel (ie count == 0 ) you can switch to other channel (that belongs to other client connection). 非阻塞IO的意思是1个网络线程可以同时为多个客户端服务:如果没有任何东西可以从一个通道读取(即count == 0 ),您可以切换到其他通道(属于其他客户端连接)。

So, the loop should actually iterate different channels instead of reading from one channel until it is over. 因此,循环应该实际迭代不同的通道,而不是从一个通道读取,直到它结束。

Take a look on this tutorial: http://rox-xmlrpc.sourceforge.net/niotut/ I believe it will help you to understand the issue. 看看这个教程: http//rox-xmlrpc.sourceforge.net/niotut/我相信它会帮助你理解这个问题。

allegedly super efficient FileChannel.transferFrom. 据称是超高效的FileChannel.transferFrom。

If you want both the benefits of DMA access and nonblocking IO the best way is to memory-map the file and then just read from the socket into the memory mapped buffers. 如果你想要DMA访问和非阻塞IO的好处,最好的方法是对文件进行内存映射,然后只需从套接字读入内存映射缓冲区。

But that requires that you preallocate the file. 但这需要您预先分配文件。

This way: 这条路:

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() returns a count. transferFrom()返回一个计数。 Just keep calling it, advancing the position/offset, until it returns zero. 只需继续调用它,推进位置/偏移,直到它返回零。 But start with a much larger count than 1024, more like a megabyte or two, otherwise you're not getting much benefit from this method. 但是从大于1024的计数开始,更像是一兆字节或两兆,否则你不会从这种方法中获得太多好处。

EDIT To address all the commentary below, the documentation says that "Fewer than the requested number of bytes will be transferred if the source channel has fewer than count bytes remaining, or if the source channel is non-blocking and has fewer than count bytes immediately available in its input buffer." 编辑为了解决下面的所有评论,文档说“如果源通道剩余的字节数少于计数字节,或者如果源通道是非阻塞且立即数少于字节数,则将传输少于请求的字节数在其输入缓冲区中可用。“ So provided you are in blocking mode it won't return zero until there is nothing left in the source. 因此,如果您处于阻塞模式,它将不会返回零,直到源中没有任何内容。 So looping until it returns zero is valid. 因此循环直到它返回零是有效的。

EDIT 2 编辑2

The transfer methods are certainly mis-designed. 转移方法肯定是错误设计的。 They should have been designed to return -1 at end of stream, like all the read() methods. 应该将它们设计为在流结束时返回-1,就像所有read()方法一样。

Building on top of what other people here have written, here's a simple helper method which accomplishes the goal: 建立在其他人写的基础上,这是一个简单的帮助方法,它实现了目标:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM