Java NIO:从传输开始直到流结束

发布于 12-07 18:35 字数 928 浏览 9 评论 0 原文

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

I'm playing around with the NIO library. I'm attempting to listen for a connection on port 8888 and once a connection is accepted, dump everything from that channel to somefile.

I know how to do it with ByteBuffers, but I'd like to get it working with the allegedly super efficient 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"?


Edit: Changed 1024 to BUF_SIZE, since the size of the buffer used, is irrelevant for the question.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

生死何惧 2024-12-14 18:35:10

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

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

简而言之 for (long xferBytes=0; startPos + xferBytes<="" code=""> 将适用于从文件传输 ->插座。
没有从套接字传输到文件的操作系统功能(OP 感兴趣)。由于套接字数据不在操作系统缓存中,因此无法有效完成,因此需要进行模拟。实现复制的最佳方法是通过标准循环,使用轮询的直接 ByteBuffer,其大小与套接字读取缓冲区相同。因为我只使用非阻塞 IO,也涉及选择器。

话虽这么说:我想让它与据称超级高效的“?一起工作 - 它效率不高,并且在所有操作系统上进行模拟,因此当套接字关闭时它将结束传输无论是否优雅,只要有任何传输(如果套接字可读且打开),该函数都不会抛出继承的 IOException。

我希望答案很明确:File.transferFrom 当源是文件时发生。最有效(也是有趣的情况)是 file->socket 和 file->file 是通过 filechanel.map/取消映射(!!)

There are few ways to handle the case. Some background info how trasnferTo/From is implemented internally and when it can be superior.

  • 1st and foremost you should know how many bytes you have to xfer, i.e. use FileChannel.size() to determine the max available and sum the result. The case refers to FileChannel.trasnferTo(socketChanel)
  • The method does not return -1
  • The method is emulated on 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.
  • On Linux the standard sendfile (or sendfile64) is used, on Solaris it's called sendfilev64.

in short for (long xferBytes=0; startPos + xferBytes<fchannel.size();) doXfer() will work for transfer from file -> socket.
There is no OS function that transfers from socket to file (which the OP is interested in). 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. Since I use only non-blocking IO that involves a selector as well.

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).

I hope the answer is clear: the only interesting use of File.transferFrom happens when the source is a file. The most efficient (and interesting case) is file->socket and file->file is implemented via filechanel.map/unmap(!!).

你怎么敢 2024-12-14 18:35:10

直接回答你的问题:

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

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

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

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

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. 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 (i.e. count == 0) you can switch to other channel (that belongs to other client connection).

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.

天冷不及心凉 2024-12-14 18:35:10

我不确定,但 JavaDoc 说:

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

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

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

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

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

I'm not sure, but the JavaDoc says:

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.

But as I already said: I'm not sure.

傾城如夢未必闌珊 2024-12-14 18:35:10

建立在其他人所写的基础上,这里有一个简单的帮助方法可以实现目标:

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

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);
    }
}
断桥再见 2024-12-14 18:35:10

据称超高效的 FileChannel.transferFrom。

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

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

allegedly super efficient 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.

But that requires that you preallocate the file.

橘虞初梦 2024-12-14 18:35:10

这边走:

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

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 ;)
}
柏林苍穹下 2024-12-14 18:35:10

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

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

编辑2

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

transferFrom() returns a count. 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.

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

The transfer methods are certainly mis-designed. They should have been designed to return -1 at end of stream, like all the read() methods.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文