2011-10-04 98 views
16

我正在玩NIO库。我试图在端口8888上监听连接,一旦连接被接受,将该通道中的所有内容转储到somefileJava NIO:transferFrom直到流结束

我知道如何使用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

我不知道,但JavaDoc中说:

尝试读取的内容,将其从源通道 数字节,并将其写入到此通道的文件从给定的位置。 调用此方法可能会或可能不会传输所有请求的字节;不管它是否取决于的性质 和状态的渠道。如果源通道的剩余数量少于计数字节 ,或源通道是非阻塞的,并且其输入缓冲区中立即可用的计数字节数少于 ,则将传输比要求的字节数 少的字节数。

我想你可能会说,告诉它(不是在循环过程中)复制无限字节将做的工作:当插座连接关闭

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

所以,我想,在状态将会改变,这将停止transferFrom方法。

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

+0

你打我吧。但是,您可以使用“Long.MAX_VALUE”而不是“Integer.MAX_VALUE”。 –

+0

没错,但是如果我在第一次调用transferFrom时只得到10个字节中的3个字节呢? – aioobe

+0

也许测试你的源频道:它有一个['int read(ByteBuffer)'](http://download.oracle.com/javase/6/docs/api/java/nio/channels/ReadableByteChannel.html#read% 28java.nio.ByteBuffer%29)方法。如果返回'-1',那么它没有任何内容,因此您将所有内容都传输到'FileChannel'中。如果你想要更健壮的东西,那么你比'ByteBuffer'好得多(**重新使用**这些效率非常高)。 –

4

直接回答你的问题:

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

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

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

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

+1

但是该呼叫可以返回0字节而没有达到流结束,对吧? – aioobe

+0

@aioobe只有(a)在非阻塞模式下,无论如何您应该使用Selector而不是循环,或者(b)如果缓冲区长度为零,这是编程错误。 – EJP

0

transferFrom()返回一个计数。只要继续调用它,推进位置/偏移,直到它返回零。但是从一个比1024更大的计数开始,更像是一两个兆字节,否则你从这种方法中获益不大。

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

编辑2

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

+1

该方法的文档说它返回'实际传输的字节数,可能为零。这并不意味着它不能在第一次调用之后立即以'0'返回而不会传输任何内容。 (另一个问题是:何时以及为何会这样做?)然而,OP不能依赖这一点。我开始认为确保这种方法将一切从一个通道传输到一个文件的唯一方法是事先知道将传输多少个字节。 –

+0

这已经在[这个答案]的评论部分讨论过(http://stackoverflow.com/questions/7651528/java-nio-transferfrom-until-end-of-stream/7651704#7651704)。然而,OP希望得到一个最简单的解决方案(我可以与此相关,因为它会很棒)。 –

+0

@EJP,你是否有任何争论或好的参考资料,说明退回到一个普通的'read'(并且可能不得不手动写入这样的字节)是这个问题的最佳解决方案? – aioobe

1

据称超高效的FileChannel.transferFrom。

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

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

9

有几种方法来处理案件。一些背景信息trasnferTo/From是如何在内部实现的,以及何时可以优越。

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

总之for (long xferBytes=0; startPos + xferBytes<fchannel.size();) doXfer()将工作从文件 - >套接字传输。 没有从套接字传输到文件的操作系统功能(OP感兴趣)。由于套接字数据不是int,所以OS高速缓存不能如此有效地完成,因此它是模拟的。实现副本的最佳方式是通过标准循环使用与套接字读取缓冲区大小一致的轮询直接ByteBuffer。由于我只使用涉及选择器的非阻塞IO。

话虽这么说:我想获得它涉嫌超高效工作” - 它是没有效率,它的模拟上所有的操作系​​统,因此它会当插座正常关闭最终转移如果有任何传输(如果套接字是可读和打开的),函数甚至不会抛出继承的IOException。一个文件,最有效的(有趣的情况)是文件 - >套接字和文件 - >文件通过filechanel.map/unmap(!!)实现。

+1

”一旦连接被接受,将所有从该通道转储到某个文件。“所以输入是一个套接字通道,所以他不可能知道它里面有多少数据,除非发送者先发送长度字。我不知道什么“使用FileChannel.size()来确定最大可用和求和结果”的含义。 – EJP

+0

@EJP - 进一步阅读,它确实告诉套接字 - >文件是徒劳的和模拟的。使用大小与套接字读缓冲区大小相对应的专用直接ByteBuffer是执行任务的方式。 1024是一个非常不好的读取大小(分配的内存总是> pageSize [4k],所以1024只会浪费内存并涉及更多的内核行程)。前面的子弹显示了如何在内部传输方法。 – bestsss

+1

当然,但是关于file-> socket的第一部分答案仍然与OP的问题无关。 – EJP

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 ;) 
} 
0

大厦,这里是其实现目标的一个简单的辅助方法:

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