2011-10-04 41 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); 
    } 
}